high priority medium complexity testing pending testing specialist Tier 3

Acceptance Criteria

Test file exists at test/features/speech/native_speech_api_bridge_test.dart
Mock MethodChannel handler simulates permission granted: requestPermission() returns SpeechPermissionResult.granted
Mock MethodChannel handler simulates permission denied: requestPermission() returns SpeechPermissionResult.denied
Mock MethodChannel handler simulates permission permanently denied: requestPermission() returns SpeechPermissionResult.permanentlyDenied
Mock EventChannel emits a sequence of partial results followed by a final result: all delivered to correct callbacks
Mock EventChannel emits a network error event: onError callback receives SpeechError.networkUnavailable
Mock EventChannel emits no-speech error: onError callback receives SpeechError.noSpeechDetected
stopRecognition() called during active recognition: no further events are delivered after stop, no exception thrown
isAvailable() returns true when mock channel returns true, false when mock returns false
All tests pass with flutter test — no flakiness over 5 consecutive runs
Test coverage for NativeSpeechApiBridge Dart-side logic is above 90% as reported by flutter test --coverage
Tests are platform-agnostic and run on any OS without requiring a connected device

Technical Requirements

frameworks
Flutter
Dart
flutter_test
apis
TestDefaultBinaryMessengerBinding
MethodChannel mock handlers
EventChannel mock handlers
data models
NativeSpeechApiBridge
SpeechPermissionResult
SpeechRecognitionEvent
SpeechError
performance requirements
Each test must complete within 5 seconds — no real timers or delays in mock channel handlers
Use fake async or manual stream controllers to avoid real-time waits in streaming tests
security requirements
Tests must not establish real MethodChannel connections — all native calls must be intercepted by mock handlers
No real microphone access should occur during testing

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

The primary challenge is mocking Flutter's EventChannel for streaming. Use the following pattern: in test setUp, call TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockStreamHandler('com.eircodex.speech/events', MockStreamHandler) where MockStreamHandler implements StreamHandlerOnListen to push test events into the sink. For MethodChannel, use setMockMethodCallHandler and return Future values matching the native response format (Map with 'type' and 'value' keys). Test the Dart-side mapping logic in IosSpeechApiBridge and AndroidSpeechApiBridge by using conditional compilation (#if dart.library.io) or by extracting the result-mapping logic into pure Dart functions that can be unit-tested without channel plumbing.

Document in test file which scenarios are covered by mock tests vs. which require real device testing via TestFlight.

Testing Requirements

All tests use flutter_test with TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler to intercept MethodChannel calls. For EventChannel streaming, use a StreamController that emits mock JSON payloads. Group tests by scenario: (1) Permission flow group, (2) Recognition lifecycle group (start → partial → final), (3) Error propagation group, (4) Resource cleanup group (stopRecognition). Use setUp/tearDown to reset mock handlers between tests.

Assert callback invocation count and arguments using expect with typed matchers. Run the full suite with flutter test test/features/speech/ and verify 0 failures. Add a test that calls startRecognition() and then immediately stopRecognition() with no events emitted — confirm no callbacks are called after stop.

Component
Native Speech API Bridge
infrastructure medium
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.