medium priority low complexity testing pending testing specialist Tier 1

Acceptance Criteria

All tests use flutter_test and fake_async to control Timer without real wall-clock delays
Test verifies a single polite announce dispatches exactly once after the debounce window elapses
Test verifies three rapid polite announces within the debounce window dispatch exactly once (last message wins)
Test verifies assertive announce dispatches synchronously without waiting for debounce
Test verifies assertive announce does not suppress a previously queued polite announce — both eventually dispatch
Test verifies empty string input produces zero SemanticsService calls
Test verifies dispose() prevents any pending timer from firing
Singleton test: reading the Riverpod provider twice returns identical object reference
Mock SemanticsService records all calls with message and TextDirection for assertion
All tests pass with `flutter test` and produce no analyzer warnings

Technical Requirements

frameworks
Flutter
flutter_test
Riverpod
apis
fake_async
SemanticsServiceWrapper (mock)
performance requirements
Test suite completes in under 5 seconds (fake_async eliminates real timer waits)

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Import `package:fake_async/fake_async.dart` — add to dev_dependencies if not already present. The mock wrapper should be defined in the test file (not a shared fixture) since it is specific to this component. For the assertive-does-not-cancel-polite test: call polite (queues timer), then call assertive (dispatches immediately), then elapse fake time past debounce window and assert polite message also dispatched. This validates the interaction model described in the implementation.

Do not use `mockito` code generation for this simple mock — a manual class is cleaner and avoids build_runner overhead.

Testing Requirements

Use fake_async to wrap all Timer-dependent test cases. Define a `MockSemanticsServiceWrapper` that extends `SemanticsServiceWrapper` and records calls in a `List<({String message, TextDirection direction})> calls` field. For the singleton test, use `ProviderContainer` from Riverpod's test utilities and assert `identical(container.read(p), container.read(p))` is true. For the debounce tests, use `fakeAsync((fake) { announcer.announce('msg'); fake.elapse(Duration(milliseconds: 600)); expect(mock.calls.length, 1); })`.

Group tests into: 'polite announcements', 'assertive announcements', 'edge cases', 'singleton behavior'.

Component
Accessibility Live Region Announcer
infrastructure low
Epic Risks (3)
high impact medium prob technical

iOS 15 on-device speech recognition has a 1-minute session limit and requires network fallback for longer sessions. Peer mentor way-forward dictation may routinely exceed this limit, causing silent truncation of transcribed content without user feedback.

Mitigation & Contingency

Mitigation: Implement session-chunking logic in NativeSpeechApiBridge that automatically restarts recognition before the limit is reached, preserving continuity via partial concatenation. Document the iOS 15 vs iOS 16 on-device recognition behaviour difference in code comments.

Contingency: If chunking causes user-visible interruptions, surface a non-blocking informational banner on iOS 15 devices informing users that very long dictation sessions may need to be broken into segments, and use PartialTranscriptionRepository to persist each chunk immediately.

high impact medium prob scope

On iOS, speech recognition permission can only be requested once. If the user denies the permission, the app cannot re-request it. A poor first-impression permission flow will permanently disable dictation for those users, impacting the Blindeforbundet blind-user base who rely on dictation most.

Mitigation & Contingency

Mitigation: Design the NativeSpeechApiBridge permission flow to show a clear pre-permission rationale screen before the OS dialog. Implement a graceful degradation path that hides the microphone button and shows a settings deep-link when permission is permanently denied.

Contingency: If users have already denied permission before the rationale screen is added, provide a settings deep-link in DictationScopeGuard's denial message directing users to iOS Settings > Privacy > Speech Recognition to re-enable manually.

medium impact low prob integration

The approved field IDs and screen routes configuration in DictationScopeGuard may fall out of sync with the actual report form schema as new fields are added by org administrators, silently blocking dictation on legitimately approved fields.

Mitigation & Contingency

Mitigation: Source the approved field configuration from the same org-field-config-loader used by the report form, rather than a hardcoded list. Add a developer-time assertion that logs a warning when a dictation-eligible field type is rendered but not in the approved routes map.

Contingency: Provide a runtime override mechanism in the scope guard that coordinators or admins can use to temporarily whitelist a field ID while the config is updated, with an automatic expiry.