medium priority medium complexity testing pending testing specialist Tier 6

Acceptance Criteria

Test file compiles and all tests pass with `flutter test` with no skipped tests
Test: diff panel is not visible on initial render (Offstage, display:none equivalent, or find.nothing)
Test: tapping the toggle expands the diff panel and is visible
Test: first toggle triggers exactly one call to BufdirPreviewRepository.fetchPriorPeriodReport
Test: loading state shows CircularProgressIndicator inside the diff panel
Test: loaded state renders one BufdirDiffRowWidget per field in the fixture (exact count asserted)
Test: row with absolute delta > 25% renders warning icon (Icons.warning_amber_rounded or by key)
Test: row with absolute delta <= 25% does not render warning icon
Test: row with null prior value renders 'N/A — no prior data' text
Test: each diff row has a Semantics label matching the format defined in BufdirAccessibilityUtils
Test: second toggle-open does not call repository again (cached path)

Technical Requirements

frameworks
Flutter
flutter_test
Riverpod (ProviderScope overrides)
apis
BufdirPreviewRepository (mock)
data models
BufdirFieldDiffRow
BufdirReportData
BufdirPriorPeriodState
performance requirements
All widget tests must complete within 30 seconds total on CI
security requirements
Fixture data must not contain real personal data
ui components
BufdirPeriodDiffView
BufdirDiffRowWidget
BufdirDeltaBadge
CircularProgressIndicator
Warning icon (Icons.warning_amber_rounded)

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Create a `FakeBufdirPreviewRepository` class at the top of the test file that holds a `callCount` integer and a `Future Function()` callback so each test can inject its own response. Use `ProviderScope(overrides: [bufdirPreviewRepositoryProvider.overrideWithValue(fakeRepo)])` for clean injection. For the 'panel hidden by default' test, prefer `find.byType(BufdirPeriodDiffView).evaluate().first` and check a property, or use an Offstage wrapper — avoid relying on widget visibility which can be ambiguous in Flutter tests. The Semantics label test is the most complex: pump the widget to loaded state, then call `tester.getSemantics()` with a specific row finder and assert `.label` contains the expected plain-language string.

Testing Requirements

This task IS the testing work. Define a fixture with at least 3 fields: one with a delta above 25%, one below 25%, and one with a null prior value — this covers all three rendering branches in a single loaded-state test. Use a fake AsyncNotifier (or Riverpod StateProvider override) for BufdirPriorPeriodNotifier to control state transitions in tests. To assert exactly one repository call, use a call-counting fake rather than a mock — increment a counter in the override's load method.

For Semantics label assertions, use `tester.getSemantics(find.byType(BufdirDiffRowWidget).first)` and check `.label`. Follow Arrange-Act-Assert pattern with one assertion focus per test case.

Component
Period Comparison Diff View
ui medium
Epic Risks (3)
medium impact medium prob integration

The preview screen must pass period, scope, and aggregation session state to the Bufdir Report Export screen via navigation arguments. If the export screen's navigation argument schema is not yet finalized when this epic begins, the handoff will require rework — potentially after TestFlight is already in use by coordinators.

Mitigation & Contingency

Mitigation: Define the shared BufdirExportNavigationArgs Dart class jointly with the Bufdir Report Export feature team before this epic starts. Store it in a shared models package both features depend on. Treat the class as an API contract with a minor-version bump policy.

Contingency: If the export screen's argument schema changes after the preview screen is implemented, the BufdirPreviewService session state model can be adapted with a compatibility shim. The preview screen itself requires only a one-line navigation call change.

medium impact low prob technical

The period diff view loads prior-period data on demand and renders it inline with the current report. On a large report (many sections, many fields) combined with slow Supabase connectivity, the diff overlay could block the UI or produce a janky re-render that degrades the coordinator experience during the pre-submission review.

Mitigation & Contingency

Mitigation: Load prior-period data in a background Riverpod FutureProvider that starts prefetching when the preview screen mounts (not when the user taps the diff toggle). Show a shimmer placeholder on each field row's prior-period column while loading. Cache prior-period data in BufdirPreviewRepository using the same local cache as current-period data.

Contingency: If diff view performance is unacceptable on TestFlight devices, disable the toggle for the initial TestFlight release and ship the diff view in the following sprint after profiling the Supabase query plan and adding an appropriate Postgres index on the prior-period data table.

high impact medium prob scope

The full preview screen will be reviewed by coordinators on TestFlight who will cross-reference it against their physical Bufdir reporting forms. Any section ordering difference or label mismatch discovered during UAT will require a fix before the feature can be signed off, potentially delaying the entire Bufdir reporting pipeline.

Mitigation & Contingency

Mitigation: Conduct a pre-TestFlight alignment review with at least one coordinator from NHF and one from HLF using a static screenshot of the preview screen layout. Obtain written sign-off on section order and field labels before distributing to the full test group of 5-8 people.

Contingency: If label mismatches are found during TestFlight UAT, update BufdirReportStructureMapper constants and rebuild. Since the mapper is a pure Dart class with no persisted state, corrections are deployed in the next TestFlight build with no database migration required.