Integration test: end-to-end search flow
epic-contact-search-accessible-ui-task-010 — Write integration tests that cover the full search flow: user types a query, debounce fires, ContactSearchService is called, results are displayed in SearchResultsList, and screen reader announcements are triggered. Test offline mode fallback path and empty state. Verify the voice-to-text button triggers speech recognition correctly. Use flutter_test integration_test package against a mocked Supabase and offline repository.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 4 - 323 tasks
Can start after Tier 3 completes
Implementation Notes
Structure the test file with a single main() entry point and group tests by flow scenario: group('Online search flow', ...), group('Offline fallback flow', ...), group('Voice input flow', ...), group('Empty state flow', ...). Use ProviderScope overrides (Riverpod) or BlocProvider with mock blocs to inject test doubles. For debounce handling, prefer tester.pump(Duration(milliseconds: debounceMs + 50)) over pumpAndSettle() to have predictable timing. Keep mock data minimal (3–5 contact records) to keep test intent clear.
Assert both data (result count, content) and UI (widget visibility, text content) to ensure the full pipeline works. Avoid testing implementation details of individual widgets here — that is covered by widget tests in task-009.
Testing Requirements
Use the integration_test package with testWidgets() in a dedicated integration_test/ directory. Inject mock implementations of IContactSearchRepository (returns predefined ContactSearchResult lists) and IOfflineSearchRepository via dependency injection or provider overrides. Use FakeAsync or tester.pump(Duration(milliseconds: 350)) to advance past the debounce timer without real delays. For offline tests, configure the mock Supabase repository to throw a SocketException, then assert the offline banner appears and results are sourced from the offline mock.
Use a MockSpeechRecognitionService that immediately returns a predefined transcription string. Assert semantics announcements using tester.getSemantics() after pumpAndSettle(). Each scenario should be an independent test with no shared mutable state between tests.
Flutter's Semantics live region support for announcing dynamic result count changes may behave inconsistently between VoiceOver (iOS) and TalkBack (Android), particularly regarding announcement throttling and focus management, causing the feature to pass testing on one platform and fail on the other.
Mitigation & Contingency
Mitigation: Test live region announcements on both iOS (VoiceOver) and Android (TalkBack) early in development using the existing accessibility test harness. Reference the existing LiveRegionAnnouncer component (608-live-region-announcer) patterns used elsewhere in the app.
Contingency: If cross-platform consistency cannot be achieved, implement a platform-specific announcement strategy using the SemanticsService.announce API with platform-conditional announcement timing to work around OS-specific throttling behaviour.
Voice-to-text progressive enhancement for Blindeforbundet may not be available or may behave unpredictably on all device/OS combinations, particularly older Android devices, potentially causing crashes or silent failures that degrade the search experience.
Mitigation & Contingency
Mitigation: Implement voice-to-text as a strictly optional enhancement: detect availability at runtime, show the microphone button only when the platform speech API reports availability, and wrap all voice invocations in try/catch with graceful degradation to standard text input.
Contingency: If voice-to-text causes instability on a subset of devices discovered during TestFlight/beta, disable the feature flag for that platform version while a fix is investigated, without impacting the core text-based search functionality.