Write widget tests for calculation preview
epic-expense-type-selection-user-interface-task-012 — Write a complete flutter_test widget test suite for ExpenseCalculationPreview covering: zero-state placeholder renders when no selection, line items render for each selected type, total row reflects correct sum, preview updates within 300 ms of stream emission, and screen-reader live-region announcement is triggered on each update.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 6 - 158 tasks
Can start after Tier 5 completes
Implementation Notes
To test SemanticsService.announce, the cleanest approach is to wrap the call behind a thin injectable function (typedef or abstract class) that can be swapped in tests. If the codebase already has a live-region-announcer pattern, use the same injection mechanism for consistency. For the 300ms SLA test, set up the BLoC mock to emit state A, call tester.pumpWidget, then use bloc_test's whenListen to emit state B, call tester.pump(Duration(milliseconds: 300)), and assert. Use a StreamController
The deduplication test requires emitting the same state twice via the mock stream and asserting the announce spy was called exactly once. Structure groups as: 'zero-state rendering', 'populated state rendering', 'stream reactivity / 300ms SLA', 'accessibility announcements'.
Testing Requirements
Full widget test suite using flutter_test and bloc_test. For the 300ms SLA test, use tester.pump(const Duration(milliseconds: 300)) after emitting a new BLoC state — do not use real async delays. For the live-region announcement test, override SemanticsService by injecting a mock or use a test double pattern: capture calls to SemanticsService.announce via a wrapper function injected as a dependency. If the app uses a live-region-announcer helper class, mock that class instead.
Use find.byType(ReimbursementLineItem) to count rendered line items. Use find.text() for total amount assertions with exact NOK formatted string. All golden references from task-011 should be kept separate — do not add new goldens here.
If the expense calculation preview subscribes to the full BLoC state stream, every unrelated state property change (e.g. a loading flag toggle) triggers a widget rebuild. With complex card animations for the disabled-state transition, this could cause frame drops on low-end Android devices used by some peer mentors.
Mitigation & Contingency
Mitigation: Use select() on the Riverpod provider to subscribe only to the specific state slice each widget needs; write a performance test asserting rebuild count on a rapid sequence of toggle events.
Contingency: If jank is detected in device testing, replace animated disabled-state transitions with instant opacity changes and defer animation polish to a follow-up sprint.
The disabled card state requires a specific contrast-safe colour combination that communicates unavailability without relying solely on colour (WCAG 1.4.1). If the current design token palette does not include a disabled-state token with sufficient contrast for text on the disabled background, the widget will either fail WCAG AA or require a last-minute design token addition that could break other components.
Mitigation & Contingency
Mitigation: Audit the existing design token manifest for disabled-state tokens at the start of the epic; if missing, raise with the design lead and add a contrast-validated token before widget implementation begins.
Contingency: If no design review is available, use the established --color-text-disabled and --color-surface-disabled tokens with an added strikethrough or lock icon to satisfy WCAG 1.4.1 non-colour requirement, and document the deviation for design review.