high priority medium complexity testing pending testing specialist Tier 7

Acceptance Criteria

Widget tests exist for all 6 components: StatsSummaryCards, StatsPeriodFilterBar, PeerMentorStatsList, MonthlyActivityBarChart, ActivityTypeDonutChart, PersonalStatsView
Each component has tests for all 4 states: loading (skeleton visible), data (content rendered correctly), error (InlineErrorCard visible with retry button), and empty (empty-state message visible)
Tests verify that correct data values are displayed — e.g., StatsSummaryCards shows the exact count values from the mocked BLoC state
Tests verify that chart semantic labels are present — e.g., find a Semantics node with label 'January: 14 activities' in MonthlyActivityBarChart
Tests verify that all interactive elements have non-empty semantic labels (using tester.getSemantics())
Tests verify that filter chip taps in StatsPeriodFilterBar dispatch the correct BLoC events (using MockBloc)
Tests verify retry button tap dispatches RetryCoordinatorStatsEvent
MonthlyActivityBarChart tests verify that the accessible data table fallback is rendered and contains all data rows
Tests verify empty state renders the correct message widget when the data list is empty
All tests pass in CI with no flakiness — no real timers, real animation controllers, or real network calls used in tests
Total test coverage for tested files is ≥80% line coverage

Technical Requirements

frameworks
Flutter
flutter_test
BLoC
bloc_test
data models
CoordinatorStatsState
CoordinatorStatsSummary
MonthlyActivityData
ActivityTypeBreakdown
PeerMentorStatsEntry
PersonalStatsData
performance requirements
Full widget test suite must complete in under 60 seconds in CI
No test should use real async delays — use fakeAsync and fake timers for any debounce or animation testing
ui components
MockCoordinatorStatsBloc (using bloc_test MockBloc)
Test fixture data builders for all state variants
fakeAsync wrappers for debounce verification in StatsPeriodFilterBar tests

Execution Context

Execution Tier
Tier 7

Tier 7 - 84 tasks

Can start after Tier 6 completes

Implementation Notes

Create a shared test_helpers.dart file in the test/ directory that exports: (1) buildTestableWidget() — wraps a widget in MaterialApp + BlocProvider with the mock BLoC, (2) fixture builders for each state variant (e.g., buildLoadedState(), buildErrorState(), buildEmptyState()), (3) a mockBloc factory. This avoids boilerplate repetition across 6 test files. For MonthlyActivityBarChart, test the accessible data table separately from the chart canvas — find the DataTable widget and verify its rows match the input data. For animation-dependent tests (shimmer skeleton), use tester.pump(Duration(milliseconds: 500)) rather than pumpAndSettle() to avoid infinite loops from continuous animations.

Use const constructors in test widgets wherever possible for faster pump times. Add a CI step that runs flutter test --coverage and fails the build if coverage falls below 80% on the dashboard widget files.

Testing Requirements

All tests are flutter_test widget tests using MockBloc from bloc_test. Test structure per widget: one describe/group block per widget, with nested groups for each state (loading, data, error, empty). Each test pumps the widget wrapped in a BlocProvider(create: (_) => mockBloc) and a MaterialApp ancestor. Use tester.pumpAndSettle() for state transitions.

Use fakeAsync for debounce tests in StatsPeriodFilterBar. Verify semantic labels using tester.getSemantics(find.byType(WidgetUnderTest)) and expect(...semanticsNode.label...). Use verify(mockBloc.add(any)) from mockito/mocktail to assert correct events are dispatched. Run flutter test --coverage and verify ≥80% line coverage on all tested files.

Component
Stats Summary Cards
ui low
Epic Risks (4)
medium impact medium prob technical

PeerMentorStatsList must handle rosters of 40+ mentors efficiently. A naive ListView implementation that re-renders all rows on filter change may cause frame drops on mid-range devices, degrading the experience for coordinators managing large chapters.

Mitigation & Contingency

Mitigation: Use ListView.builder with const constructors for row widgets. Profile the list with Flutter DevTools on a release build against a 60-mentor dataset before submitting for review. Implement sort in the BLoC layer, not in the widget.

Contingency: If frame drops persist, introduce pagination (load 20 mentors, scroll to load more) and add a search filter to reduce visible row count in practice.

medium impact low prob integration

The ActivityTypeDonutChart must render org-configured activity type labels from the org-labels system. If the terminology provider is not available or returns stale labels, chart segments will display raw key strings instead of human-readable organisation-specific names, confusing coordinators.

Mitigation & Contingency

Mitigation: Always resolve labels through the OrganizationLabelsProvider before passing data to the donut chart widget. Implement a fallback that formats the raw key as a readable string (e.g., 'peer_support' → 'Peer Support') if the provider returns null.

Contingency: If the org-labels system is unavailable, display the formatted fallback label and log a warning. Do not block chart rendering on label resolution — render with fallbacks immediately.

high impact high prob technical

fl_chart widgets do not natively expose semantic labels for individual bars and donut segments. Without explicit Semantics wrappers, VoiceOver and TalkBack users will receive no meaningful chart information, failing accessibility requirements critical for Blindeforbundet and NHF deployments.

Mitigation & Contingency

Mitigation: Wrap each fl_chart widget in a Semantics widget with a descriptive label summarising the chart data. Implement the data table fallback toggle from the start, not as an afterthought. Validate with VoiceOver on iOS and TalkBack on Android during development.

Contingency: If fl_chart's rendering pipeline prevents semantic overlay from working reliably, replace chart widgets with a custom Canvas-based implementation that has full semantic control, or use a different charting library with better accessibility support.

medium impact medium prob technical

The period filter state must be preserved when navigating away from the dashboard (e.g., drilling into a mentor's detail screen and pressing back). If the BLoC is re-created on navigation, the filter resets to default, forcing coordinators to re-select their period every time they drill down, degrading operational workflow efficiency.

Mitigation & Contingency

Mitigation: Provide the StatsBloc at a route level above the dashboard screen using a BlocProvider scoped to the statistics navigation shell, not inside the screen widget itself. Verify persistence with a widget integration test that navigates away and back.

Contingency: If route-level scoping is not achievable within the current navigation architecture, persist the last-used filter to a local session store and restore it on screen re-entry.