high priority medium complexity testing pending testing specialist Tier 3

Acceptance Criteria

Test file compiles and all tests pass with flutter test --coverage
Branch coverage for FocusManagementService reaches ≥90% as reported by lcov
Test: focus lands on the designated first-focus element after a named route push
Test: focus is restored to the originating element after Navigator.pop
Test: focus moves to the dialog widget root when a dialog route is pushed
Test: focus is restored to the trigger element when dialog is dismissed via Confirm
Test: focus is restored to the trigger element when dialog is dismissed via Cancel
Test: moveFocusTo(unmountedKey) does not throw and emits a debug warning log
Test: moveFocusTo(null) is a no-op and does not throw
Test: calling moveFocusTo on a FocusNode that is already focused is a no-op (no duplicate requestFocus calls)
Test: focus history stack is correctly maintained across multiple route pushes and pops
All tests use AccessibilityTestHarness widget wrapper and do not depend on production Supabase or BankID connections
Tests are deterministic — no flakiness from async timing (all async operations use pumpAndSettle or explicit pump counts)

Technical Requirements

frameworks
Flutter
flutter_test
apis
AccessibilityTestHarness
FocusManagementService
FocusNode
Navigator
WidgetTester.pumpAndSettle
data models
FocusHistory (internal stack)
FocusManagementService
performance requirements
Each test must complete within 5 seconds
No real timers — use fake_async or tester.pump(Duration) for time-sensitive focus assertions
ui components
Scaffold
TextButton
TextField
AlertDialog (test doubles)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

FocusManagementService likely maintains an internal stack of FocusNodes for history-based restoration. Write a test helper function buildTestRoute(harness, {FocusNode? autoFocusNode}) that creates a simple page widget which auto-focuses the given node on mount — this eliminates boilerplate across tests. For the unmounted target test, build a StatefulWidget that removes the target from the tree (setState removes a conditional child) before calling moveFocusTo — this reliably reproduces the unmounted case.

Use debugFocusTree() output in test failure messages to aid diagnosis. If FocusManagementService is a Riverpod provider, override it in tests using ProviderScope(overrides: [...]) with a real (not mocked) instance to test actual behaviour rather than mock behaviour.

Testing Requirements

Pure flutter_test suite. Organise tests in describe-style groups: 'route navigation focus', 'dialog focus', 'edge cases'. Use testWidgets for all tests requiring a widget tree. For each test, build a minimal widget tree via AccessibilityTestHarness that reproduces exactly the scenario being tested — avoid reusing complex production screens to keep tests fast and isolated.

Use expect(tester.semantics, ...) matchers where available for focus assertions, or check FocusNode.hasFocus directly after pumpAndSettle. Document each test with a one-line comment explaining which branch of FocusManagementService it exercises.

Component
Focus Management Service
service medium
Epic Risks (3)
high impact high prob technical

Flutter's build pipeline and SemanticsService.announce() operate asynchronously. Announcements triggered too early (before the semantic tree settles) may be swallowed silently on both platforms, causing acceptance criteria around the 500ms window to fail intermittently in CI and on device, which would block pilot launch.

Mitigation & Contingency

Mitigation: Implement the LiveRegionAnnouncer with a post-frame callback delay and an internal timing guard. Write integration tests using WidgetTester.pump() sequences that verify announcement delivery across multiple frame boundaries. Validate on physical devices at each sprint boundary, not only in CI.

Contingency: If consistent announcement timing cannot be achieved within Flutter's semantic pipeline, switch to a platform channel approach that calls native UIAccessibility.post (iOS) and AccessibilityManager.announce (Android) directly, bypassing Flutter's intermediary.

high impact medium prob technical

Flutter does not natively emit a focus-gain event to Dart code when VoiceOver or TalkBack moves focus to a specific widget. If the intercept mechanism for the SensitiveFieldWarningDialog relies on an unsupported or undocumented hook in the semantics tree, it may miss focus events for some field types or in some navigation contexts, leaving sensitive data unprotected.

Mitigation & Contingency

Mitigation: Prototype the focus-intercept mechanism during the first sprint of this epic, before building the dialog UI. Evaluate Flutter's SemanticsBinding.instance callbacks and custom SemanticsActions as intercept points. Document the chosen mechanism with platform compatibility notes.

Contingency: If no reliable focus-intercept is available, implement an alternative where sensitive fields show a static 'Screen reader active — tap to reveal' overlay instead of an OS dialog, which is less seamless but achieves equivalent privacy protection without relying on an unreliable event hook.

medium impact low prob dependency

The AccessibilityTestHarness depends on internal flutter_test semantic tree APIs that can change between Flutter minor versions. If the project upgrades Flutter during this epic, the harness may break silently, causing CI accessibility tests to pass while actually skipping assertions.

Mitigation & Contingency

Mitigation: Pin the Flutter SDK version in pubspec.yaml for the duration of this epic. Document which flutter_test APIs are used and their stability tier. Add a canary test that explicitly fails if the semantic tree API surface changes.

Contingency: If a forced Flutter upgrade breaks the harness, prioritise patching the harness as a blocking task before any other epic work continues, using the canary test failure as the trigger.