high priority low complexity testing pending testing specialist Tier 4

Acceptance Criteria

Integration test: typing a query into the search field and waiting for the debounce period results in ContactSearchService being called exactly once with the typed query string
Integration test: after ContactSearchService returns results, SearchResultsList renders the correct number of result cards matching the mock data
Integration test: the semantics live region announces the result count after results are rendered
Integration test: when the mock offline repository is active (Supabase mock throws a network error), the offline banner is displayed and results come from the offline repository
Integration test: typing a query that returns zero results displays the empty state widget
Integration test: tapping the voice-to-text button triggers the speech recognition service mock and its transcription is populated in the search field
All integration tests pass consistently (no flakiness) across 3 consecutive runs
Tests use mocked Supabase client and a mocked offline repository — no real network calls are made

Technical Requirements

frameworks
Flutter
flutter_test
integration_test
apis
ContactSearchService
SpeechRecognitionService
data models
ContactSearchResult
performance requirements
Total integration test suite must complete in under 2 minutes
Debounce wait time in tests should use fake async (FakeAsync) or explicit pump durations to avoid real time delays
ui components
SearchScreen
AccessibleSearchInputField
SearchResultsList
OfflineBanner
EmptyStateWidget
VoiceSearchButton

Execution Context

Execution Tier
Tier 4

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.

Component
Contact Search Screen
ui low
Epic Risks (2)
medium impact medium prob technical

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.

low impact low prob dependency

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.