high priority medium complexity testing pending testing specialist Tier 4

Acceptance Criteria

Render test: dialog shows the correct field type label (e.g. 'Phone number') and the privacy warning text matching the SensitiveFieldConfig entry for that field identifier
Confirm path: tapping the Confirm button sets field visibility to revealed, triggers FocusManagementService.restoreFocus() with the trigger GlobalKey, and dismisses the dialog
Cancel path: tapping Cancel keeps field visibility hidden, triggers FocusManagementService.restoreFocus() with the trigger GlobalKey, and dismisses the dialog
Per-session suppression: after confirming once for a given field identifier, opening the same field again does NOT show the dialog and reveals the field immediately
Per-session suppression is scoped per field identifier: confirming field A does not suppress the dialog for field B
FocusManagementService mock receives exactly one restoreFocus() call per dialog dismissal (Confirm or Cancel), never zero and never more than one
All dialog text content is accessible via semantics labels verifiable with tester.getSemantics()
Tests are in test/accessibility/sensitive_field_warning_dialog_test.dart

Technical Requirements

frameworks
flutter_test
mocktail
riverpod (if session state managed via provider)
apis
FocusManagementService.restoreFocus()
GlobalKey
WidgetTester.tap()
Semantics API
data models
SensitiveFieldConfig
SensitiveFieldIdentifier
PerSessionConfirmationRegistry
performance requirements
Each test case completes within 5 seconds
No real async I/O — all service dependencies mocked
security requirements
Test fixtures must not use real personal data — use synthetic field labels only
Per-session state must not persist between test cases — reset registry in setUp()
ui components
SensitiveFieldWarningDialog
AppButton (Confirm)
AppButton (Cancel)

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

The per-session confirmation registry should be stored as a Set of confirmed field identifiers in an object with the same lifetime as the app session (e.g. a Riverpod provider with keepAlive, or a singleton service). Inject this registry into the dialog so tests can provide an empty or pre-populated instance. The trigger GlobalKey passed to FocusManagementService is the widget that originally opened the dialog — tests should create a real GlobalKey attached to a dummy widget to verify the correct key is forwarded.

Avoid testing platform focus behaviour (screen reader cursor movement) in widget tests; reserve that for the integration test in task-016. All warning texts must be sourced from the SensitiveFieldConfig warning message keys, not hardcoded in the dialog — verify this in tests by using a custom config with distinct test strings.

Testing Requirements

Widget tests using flutter_test. Inject a mock FocusManagementService via dependency injection or provider override. Use WidgetTester.pumpWidget() with a MaterialApp wrapper to host the dialog. For the per-session suppression test, run two sequential interactions within the same test to verify state persistence within a session, then run a fresh test case to verify state resets across tests (setUp clears registry).

Assert semantics labels on the dialog title, body text, and action buttons using tester.getSemantics() to ensure screen reader correctness. Aim for 100% branch coverage on the dialog widget and the per-session confirmation registry.

Component
Sensitive Field Warning Dialog
ui high
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.