high priority medium complexity testing pending testing specialist Tier 2

Acceptance Criteria

Polite announcement queue test: given 3 polite announcements enqueued in rapid succession, SemanticsService.announce() is called in the exact same order with TextDirection.ltr
Assertive interrupt test: given a polite queue with 2 pending items, when an assertive announcement is enqueued, it is announced before the remaining polite items
No-drop test: given 10 announcements enqueued within a single frame, all 10 calls to SemanticsService.announce() are captured with no messages silently dropped
BLoC state observer mixin test: when BLoC emits a LoadingState, SuccessState, and ErrorState in sequence, the mixin maps each to the correct announcement text and queues them in the correct priority
SemanticsService.announce() mock captures both the message string and the TextDirection parameter for every call
All test cases pass on both iOS (VoiceOver) and Android (TalkBack) semantics targets via pumpAndSettle()
Test file is located under test/accessibility/ and named live_region_announcer_test.dart
Test coverage for LiveRegionAnnouncer reaches 100% branch coverage

Technical Requirements

frameworks
flutter_test
bloc_test
mocktail
apis
SemanticsService.announce()
WidgetTester.pumpAndSettle()
BLoC stream API
data models
LiveRegionPriority (polite/assertive enum)
AnnouncementQueueEntry
performance requirements
All tests complete within 30 seconds total in CI
No artificial delays (use fake async / FakeAsync rather than real timers)
security requirements
No real user data in test fixtures — use placeholder strings only

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

SemanticsService.announce() is a static method — use a thin wrapper interface (e.g. AnnouncementDispatcher) so it can be injected and mocked via mocktail without patching globals. The queue drain logic likely relies on Future.microtask or a Timer; use FakeAsync.elapse() to advance time deterministically rather than real await delays. For the assertive interrupt test, verify the queue state BEFORE and AFTER the assertive item is inserted to confirm pending polite items are paused, not discarded.

BLoC mixin tests should use a fake BLoC that emits a controlled sequence; assert the announcements produced match the mapping table defined in the mixin. Keep test descriptions in English matching the acceptance criteria above.

Testing Requirements

Unit + widget tests using flutter_test. Use FakeAsync to control time and prevent flakiness from real timer-based queue draining. Mock SemanticsService.announce() with mocktail to capture and assert call arguments. Use bloc_test's emitEach helper to emit state sequences.

Ensure at least one test per queue behaviour branch: polite-only, assertive-only, mixed polite+assertive interleave, rapid-fire no-drop, and BLoC mixin mapping. Target 100% branch coverage on LiveRegionAnnouncer and its state observer mixin.

Component
Live Region Announcer
ui 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.