medium priority low complexity testing pending testing specialist Tier 5

Acceptance Criteria

Widget tests exist for ReportHistoryScreen covering: initial loading state renders a loading indicator, loaded state renders the list of report history items, empty state renders the correct empty message, and error state renders an error message with a retry option
Widget tests exist for ReportHistoryListItem covering: all data fields (period label, status, export date, metrics) render correctly from a given data model, and loading state for re-export/re-download renders correctly
Widget tests exist for ReportSummaryMetricsWidget covering: all metric values render with correct formatting, and the widget handles null/zero values without crashing
Filter control widget tests verify: tapping a filter chip updates the displayed filter state, and the active filter is visually indicated
Re-export button tap test verifies the correct callback or BLoC event is fired with the correct report period ID
Re-download button tap test verifies the correct callback or BLoC event is fired
Pagination trigger test: scrolling the list to the bottom fires the load-more callback on the mock provider
Semantics tests: tester.getSemantics() asserts all interactive elements have non-empty labels; no unlabeled interactive nodes exist
All tests use mock/fake state providers — no real Supabase connections are made in any test
Test coverage for the three component files reaches 80% or higher
All tests pass with `flutter test` in CI with no flaky failures

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
flutter_test
data models
bufdir_export_audit_log
annual_summary
performance requirements
All widget tests must complete within 30 seconds total in CI
No test may use real async delays (no Future.delayed) — use fake async or pump/pumpAndSettle
security requirements
Test data must not include real PII — use clearly fictional names, IDs, and dates
ui components
ReportHistoryScreen
ReportHistoryListItem
ReportSummaryMetricsWidget

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Create a test fixture factory (e.g., ReportHistoryTestFixtures) that produces typed fake data models for bufdir_export_audit_log and annual_summary — this avoids repetitive setup code across test files. For BLoC-based state, use the blocTest utility from bloc_test package if available, or directly emit states via a FakeBloc. For Riverpod-based state, use ProviderContainer with overrides to inject fakes. When testing scroll-to-bottom pagination, use tester.drag() on the ListView to simulate scroll, then verify the mock was called.

Accessibility tests: after pumping the widget, call tester.getSemantics() and traverse the tree asserting each interactive Finder has a non-empty label — this catches regressions from future refactors. Group tests into separate files per component to keep test files focused and maintainable.

Testing Requirements

Write all tests using flutter_test. Use ProviderScope/BlocProvider with mock implementations to inject fake state. Structure tests in describe/group blocks per component. Cover: (1) rendering correctness for all states (loading, loaded, empty, error), (2) interaction correctness (tap callbacks, filter changes, scroll-to-load), (3) accessibility semantics tree validation using tester.getSemantics().

Use FakeAsync or tester.pump() to control async state transitions without real timers. Do not use real Supabase client — mock all data sources at the repository or service layer.

Component
Report History Screen
ui low
Epic Risks (2)
high impact medium prob integration

The ReportSummaryMetricsWidget must display metrics as they were at time of submission, not recalculated from current data. If the metrics are not stored as a JSON snapshot in the history record at export time, the widget will either show wrong data or require a full re-aggregation on every list load.

Mitigation & Contingency

Mitigation: Ensure the Bufdir export pipeline (bufdir-report-export feature) writes a summary_metrics JSONB column in the history record at export time, containing total_activities, total_hours, and participants_reached. The UI widget reads only from this snapshot field — never from live aggregation queries.

Contingency: If snapshot data is missing for historical records (e.g., older exports before the column existed), display a 'Metrics not available for this report' placeholder in the widget rather than showing zeros or triggering a live aggregation that could return different figures.

medium impact medium prob scope

Re-export can take several seconds (it runs the full generation pipeline). Without adequate progress feedback, coordinators may tap the button multiple times, triggering duplicate exports and duplicate history records.

Mitigation & Contingency

Mitigation: Disable the re-export button immediately on first tap and show an inline progress indicator on the list item. Guard against duplicate invocations at the service layer using an in-progress flag keyed by report ID. Display a loading state on the list item throughout the operation.

Contingency: If duplicate re-export records are created (e.g., due to a race condition), the history table will show multiple entries for the same original report — which is harmless but confusing. Add a deduplication UI hint ('Re-exported N times') and a backend guard that prevents more than one in-flight re-export for the same source record ID simultaneously.