critical priority medium complexity testing pending testing specialist Tier 6

Acceptance Criteria

Integration test taps each of the 5 tabs sequentially using tap (no swipe) and asserts the correct screen is shown
After each tab tap, the route reported by GoRouter matches the expected route path for that tab
After tab selection, VoiceOver/TalkBack announces the screen name (verified via Semantics liveRegion or SemanticsAction)
Notification badge count (injected via Riverpod override) is present in the Notifications tab Semantics label
The active tab's Semantics label includes 'selected' (or equivalent, e.g., 'Tab 3 of 5, selected')
Inactive tabs do not include 'selected' in their Semantics label
AccessibilityAuditRunner.audit() on the full nav bar semantic subtree returns zero violations
All five tabs remain reachable via keyboard/switch access traversal order (logical tab order)
Test suite runs successfully in CI (no device-specific dependencies beyond flutter_test integration test runner)

Technical Requirements

frameworks
Flutter
flutter_test
Riverpod
GoRouter
data models
NotificationCount (Riverpod provider, overridden in test)
performance requirements
Full integration test suite completes in under 60 seconds
No memory leaks between tab switches (dispose providers correctly)
security requirements
Integration test must not make real network calls — mock or stub Supabase and all remote providers
ui components
AccessibleBottomNavBar (component under test)
All 5 tab destination screens (stubbed or real)
AccessibilityAuditRunner

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

This is an integration test not a unit widget test — set it up in the integration_test/ directory using flutter_test's IntegrationTestWidgetsFlutterBinding.ensureInitialized(). Build a complete minimal app fixture with GoRouter (5 routes, stub screens) and ProviderScope. Tab traversal without swipe is the key requirement: use tester.tap(find.byKey(tabKey)) for each tab, never tester.drag(). The 'selected' announcement requirement maps to Flutter's NavigationBar semantics behavior — ensure AccessibleBottomNavBar sets selected: true on the active NavigationDestination or equivalent and that the Semantics.label includes 'selected'.

For the notification badge Semantics label test, override notificationCountProvider to return 3 and assert the full label string. Keep screen stubs minimal (just a Text widget per screen) to avoid test complexity. Run this test in CI using `flutter test integration_test/` with no physical device requirement.

Testing Requirements

Use flutter_test integration test infrastructure (not patrol or another third-party framework unless already in use). Set up a full app with GoRouter and ProviderScope with all required Riverpod overrides (notification count, auth, etc.). Iterate through all 5 tabs: tap each, pump, assert route, assert Semantics. For notification badge, inject count = 3 via ProviderScope override and assert the Notifications tab label contains '3'.

For active state, after tapping each tab assert that tab's Semantics includes 'selected' and the others do not. Run AccessibilityAuditRunner.audit(tester) once after the full nav is stable. Ensure no real Supabase/BankID/Vipps calls occur by overriding all remote providers.

Component
Accessible Bottom Navigation Bar
ui medium
Dependencies (5)
Implement an accessible notification count badge on the Notifications tab of AccessibleBottomNavBar. The badge count must be announced as part of the tab's Semantics label (e.g., 'Notifications, 3 unread notifications, Tab 5 of 5'). Badge must be visible at contrast ratio ≥ 4.5:1 and update reactively from a Riverpod notification count provider. epic-navigation-and-gesture-accessibility-ui-components-task-014 Create the AccessibilityAuditRunner infrastructure component that wraps flutter_test accessibility matchers and the WCAG 2.2 AA checker. It should run semantic tree assertions, touch target size checks (44×44dp minimum), and contrast ratio validations as a reusable test utility callable from widget tests. epic-navigation-and-gesture-accessibility-ui-components-task-003 Write flutter_test widget tests for ModalCloseButton covering: button renders in correct position, tap triggers Navigator.pop(), Semantics label and hint are correct, touch target meets 44×44dp, and dismissal announcement fires. Use AccessibilityAuditRunner to assert no WCAG violations. epic-navigation-and-gesture-accessibility-ui-components-task-015 Write flutter_test widget tests for PersistentBackButton covering: button visible on non-root routes, hidden on root routes, tap triggers GoRouter pop, Semantics label is 'Go back', touch target ≥ 44×44dp, and tooltip displays on long press. Run AccessibilityAuditRunner assertions on all test cases. epic-navigation-and-gesture-accessibility-ui-components-task-016 Write flutter_test widget tests for VerticalScrollContainer covering: horizontal scroll gestures do not trigger navigation, vertical scroll works correctly, scroll semantics are present (scrollUp/scrollDown actions), and overscroll is clamped. Simulate horizontal drag and assert no route change occurs. epic-navigation-and-gesture-accessibility-ui-components-task-017
Epic Risks (3)
high impact high prob technical

Flutter's ModalBottomSheet and showDialog do not automatically confine VoiceOver or TalkBack focus to the modal's subtree on all platform versions. Background content may remain reachable by screen readers, confusing users and violating WCAG 2.2 criterion 1.3.1.

Mitigation & Contingency

Mitigation: Wrap modal content in an ExcludeSemantics or BlockSemantics widget for background content. Use a Semantics node with liveRegion on the modal container and manually request focus via FocusScope after the modal animation completes. Test on both iOS (VoiceOver) and Android (TalkBack) during widget development.

Contingency: If platform-level focus trapping is unreliable, implement a custom modal wrapper widget that uses a FocusTrap widget (available in Flutter's internal tooling) and an Overlay entry with semantics blocking on the dimmed background layer.

medium impact medium prob technical

On iOS, the system-level swipe-back gesture (UINavigationController) can bypass PopScope and GoRouter's gesture suppression, meaning users can still accidentally dismiss screens via swipe even after the component is implemented. This breaks the gesture-free contract for motor-impaired users.

Mitigation & Contingency

Mitigation: Set popGestureEnabled: false in GoRouter route configurations where swipe-back is suppressed. Test specifically against Flutter's CupertinoPageRoute, which respects this flag, and verify that GoRouter generates Cupertino routes on iOS rather than Material routes with gesture enabled.

Contingency: If go_router's popGestureEnabled flag does not propagate correctly, wrap affected routes in a WillPopScope replacement (PopScope with canPop: false) and file a bug with the go_router maintainers. Document the workaround in the navigation-route-config component for future maintainers.

medium impact medium prob scope

The feature description implies migrating all existing ModalBottomSheet and dialog call sites across the app to use the new accessible helpers, which is a cross-cutting change. Scope underestimation could mean the epic finishes the new components but leaves many call sites un-migrated, leaving the accessibility promise partially broken.

Mitigation & Contingency

Mitigation: Audit all existing modal call sites at the start of the epic (grep for showModalBottomSheet, showDialog, showCupertinoDialog) and add the count to the task list. Treat migration as explicit tasks, not an implied post-step.

Contingency: If migration scope grows beyond the epic's estimate, create a follow-up tech-debt epic scoped only to call-site migration, and gate the release on at minimum all flows used by the accessibility user-story acceptance criteria being migrated.