timetracker/.tasks/done/TT-013-widget-tests.md
2026-08-03 21:51:48 +02:00

93 lines
3.4 KiB
Markdown

# TT-013 — Widget Tests
**Type:** Task
**Priority:** Medium
**Labels:** testing, widget-tests
**Depends on:** TT-006, TT-007, TT-008, TT-009, TT-010, TT-011
**Blocks:**
---
## Summary
Write widget tests for each feature screen. Tests verify correct rendering
of UI states, user interactions, and provider integration. All provider
dependencies are overridden with mocks via `mocktail`.
## Scope
| Test file | Screen | Key scenarios |
|-----------|--------|---------------|
| `test/features/timer/timer_screen_test.dart` | TimerScreen | idle state, running state, start tap, stop tap |
| `test/features/entries/entries_screen_test.dart` | EntriesScreen | empty state, list renders, swipe delete, add form |
| `test/features/projects/projects_screen_test.dart` | ProjectsScreen | empty state, tile renders, create form, archive |
| `test/features/reports/reports_screen_test.dart` | ReportsScreen | week tab, period navigation, chart renders |
| `test/features/settings/settings_screen_test.dart` | SettingsScreen | theme picker, language picker, export sheet opens |
## Widget Test Pattern
```dart
Widget buildTestApp({List<Override> overrides = const []}) {
return ProviderScope(
overrides: overrides,
child: MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const TimerScreen(),
),
);
}
testWidgets('shows START button when idle', (tester) async {
when(() => mockTimerNotifier.build()).thenReturn(const TimerState.idle());
await tester.pumpWidget(buildTestApp(overrides: [
timerNotifierProvider.overrideWith(() => mockTimerNotifier),
]));
expect(find.text('Start'), findsOneWidget);
expect(find.text('Stop'), findsNothing);
});
```
## Key Widget Test Cases per Screen
### TimerScreen
- [ ] Idle: START visible, STOP hidden, elapsed shows `00:00:00`
- [ ] Running: STOP visible, DISCARD visible, project name shown
- [ ] Tap START without project → button remains disabled
- [ ] Tap STOP → `timerNotifier.stop()` called once
- [ ] Tap DISCARD → confirmation dialog appears
### EntriesScreen
- [ ] Empty: placeholder text visible
- [ ] With entries: `EntryListTile` rendered for each
- [ ] Swipe left on entry → delete action visible
- [ ] Tap "+" → `EntryFormSheet` appears
- [ ] Date filter chip "Today" → filters visible entries
### ProjectsScreen
- [ ] Empty Active tab: "No projects yet" visible
- [ ] Tile shows project name and color dot
- [ ] Tap tile → edit form opens pre-filled
- [ ] Archive action → tile moves to Archived tab
### ReportsScreen
- [ ] Week tab shows 7 bar groups
- [ ] Tap ▶ → period label advances by one week
- [ ] Total duration card shows formatted duration
### SettingsScreen
- [ ] Theme dropdown shows current selection
- [ ] Changing theme → `themeModeProvider` updated
- [ ] Tap "Export data" → `ExportSheet` bottom sheet appears
## Acceptance Criteria
- [ ] All listed widget test files exist and pass with `flutter test`
- [ ] Each screen has ≥ 3 test cases
- [ ] No test relies on real DB or real providers
- [ ] Tests run in < 30 seconds total (no `sleep` / real async delays)
- [ ] `pumpAndSettle` used after async interactions
## Notes
- Use `find.byType()`, `find.text()`, `find.byKey()` avoid pixel-exact finders
- Add semantic labels / keys to interactive widgets to simplify test selectors
- Golden tests (screenshot comparison) are optional for v1