medium priority medium complexity testing pending testing specialist Tier 9

Acceptance Criteria

Every tappable widget on CoordinatorStatsDashboardScreen has a non-empty `Semantics.label` verified via SemanticsController
Every chart segment (bar, pie slice, or data point) has a descriptive semantic label such as 'March: 12 activities'
No interactive element has a rendered touch target smaller than 44×44 logical pixels, verified programmatically
All text elements meet WCAG 2.2 AA contrast ratio (≥4.5:1 for normal text, ≥3:1 for large text) as verified by the project's WCAG compliance checker utility
Accessibility tests are added to the CI pipeline and block merging on failure
Test suite runs in under 60 seconds on a standard CI runner
Zero accessibility violations reported by `flutter_test`'s built-in `expectAccessibilityGuidelines()` call on the full dashboard widget tree
Test file is co-located with other dashboard tests and follows the project's naming convention (e.g., `coordinator_stats_dashboard_accessibility_test.dart`)

Technical Requirements

frameworks
Flutter
flutter_test
integration_test
data models
CoordinatorStats
PersonalStats
performance requirements
Full accessibility test suite completes within 60 seconds in CI
security requirements
No real user data used in accessibility tests; use stubbed/mocked data providers
ui components
CoordinatorStatsDashboardScreen
StatsBarChart
StatSummaryCard
PeriodFilterSelector
PersonalStatsView

Execution Context

Execution Tier
Tier 9

Tier 9 - 22 tasks

Can start after Tier 8 completes

Implementation Notes

Call `tester.ensureSemantics()` at the top of each test and dispose with `semantics.dispose()` in tearDown. Use `SemanticsController` to traverse the tree and collect all nodes, then assert non-empty labels on nodes with `SemanticsFlag.isButton`, `isLink`, `isFocusable`, or custom chart nodes. For chart segments, the production chart widget must expose each data point as a `Semantics` widget with a computed label — coordinate with the chart implementation task if this is missing. Touch target checks: iterate `find.byType(GestureDetector)` and `find.byType(InkWell)`, call `tester.getSize()`, and assert ≥ 44×44.

For contrast, use `meetsGuideline(textContrastGuideline)` from `flutter_test/flutter_test.dart`. Register the test file in CI's test matrix under the `accessibility` tag so it can also be run independently with `flutter test --tags accessibility`.

Testing Requirements

Widget-level accessibility tests using `flutter_test`. Use `tester.ensureSemantics()` to enable the semantics tree. Assert semantic labels on all interactive and data-display widgets with `expect(semantics, hasSemantics(...))`. Use `expectAccessibilityGuidelines()` for broad WCAG compliance.

Write individual touch-target size assertions using `tester.getSize(finder)` and verify both width and height ≥ 44.0. Contrast ratio assertions should use the shared WCAG utility (or `flutter_test`'s `meetsGuideline(textContrastGuideline)`). Tests must be pure widget tests (no network calls) using mocked BLoC/Riverpod providers. Include in CI as a required check.

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.