high priority low complexity testing pending testing specialist Tier 10

Acceptance Criteria

Integration test confirms OrgSelectionScreen renders a list of OrgCard widgets when service returns a non-empty org list
Previously selected org (provided via initial state/persisted storage) is rendered with isSelected=true on first load
Tapping an OrgCard widget updates its visual state to selected and deselects any previously selected card
CTA (confirm/continue) button is disabled (onPressed=null or ElevatedButton.styleFrom disabled) when no org is selected
CTA button becomes enabled after tapping an OrgCard
Tapping the enabled CTA navigates to the login screen (verify route change or pushed route name)
Selected org ID is persisted to local storage/provider state before navigation
Error state widget (error message + retry button) is displayed when OrgSelectionService throws an exception
Retry button triggers a new service fetch and restores the list on success
All tests run against a MockOrgSelectionService injected via dependency injection (no real Supabase calls)

Technical Requirements

frameworks
Flutter
flutter_test
BLoC
apis
OrgSelectionService (mocked)
data models
Organization
OrgSelectionState
OrgSelectionEvent
performance requirements
Full integration test suite completes in under 30 seconds
Screen renders within 2 seconds of navigation in test environment
security requirements
MockOrgSelectionService must not make real network calls
No real org IDs or user data in test fixtures
ui components
OrgSelectionScreen
OrgCard
CTA button
Error state widget
Loading indicator

Execution Context

Execution Tier
Tier 10

Tier 10 - 11 tasks

Can start after Tier 9 completes

Implementation Notes

Create `MockOrgSelectionService` implementing the same interface/abstract class as the real service, returning either a fixed list or throwing on demand. Inject it into the BLoC/Cubit before pumping the widget. Use `pumpAndSettle()` after taps to allow BLoC state transitions to complete. For navigation assertions, use a `NavigatorObserver` spy injected into the MaterialApp in the test.

For persistence verification, inject a mock secure storage and assert the correct org ID was written. Keep test data in `test/fixtures/org_fixtures.dart`.

Testing Requirements

Flutter integration tests using flutter_test (WidgetTester). Inject MockOrgSelectionService using the app's DI mechanism (Provider, BLoC, or Riverpod). Cover 7 test scenarios as listed in acceptance criteria. Use `tester.tap()`, `tester.pump()`, and `tester.pumpAndSettle()` for interaction simulation.

Verify navigation by checking the navigator stack or a pushed route name. Verify button enabled/disabled state via `tester.widget(find.byType(ElevatedButton)).onPressed`. Error state: configure mock to throw and verify error widget is present in the tree.

Epic Risks (3)
high impact medium prob technical

Flutter's Semantics system requires explicit configuration for custom card widgets — the default widget tree semantics may merge child nodes incorrectly, causing VoiceOver to announce partial or garbled text for each OrgCard. This is a critical failure for Blindeforbundet and violates the contractual WCAG 2.2 AA requirement.

Mitigation & Contingency

Mitigation: Wrap each OrgCard in a Semantics widget with an explicit label combining organization name and role label, set button role, and excludeSemantics: true on all child widgets to prevent double-announcement. Test on a physical iOS device with VoiceOver enabled before marking the epic complete.

Contingency: If automated semantics are insufficient, implement a dedicated AccessibilityLabel builder in the OrgCard that constructs the announcement string from OrgLabelsProvider output and passes it directly to the Semantics label field.

medium impact medium prob technical

Organization logos must load before the screen is considered ready. If assets are fetched from a remote URL and network latency is high (a realistic scenario for rural NHF users), the screen may render with broken image placeholders, degrading the first-impression quality and potentially confusing screen reader users whose VoiceOver announcements would reference an empty image.

Mitigation & Contingency

Mitigation: Cache organization logos locally after first load using the org-branding-cache pattern. For the initial load, render a styled placeholder with the organization's initials and primary color while the logo loads asynchronously. Ensure the Semantics label is derived from the organization name, not the image asset, so screen reader announcements are never dependent on image load state.

Contingency: If caching implementation is out of scope for this epic, ship with initials-based placeholders as permanent fallbacks and defer logo loading to the org-branding-cache epic. The screen remains fully functional and accessible without logos.

medium impact low prob scope

The user story specifies that single-organization users bypass the selection screen and are routed directly into the app. If the bypass logic is implemented in the UI layer rather than in OrgSelectionService, it creates a tight coupling that breaks if the routing logic is ever reused or tested in isolation. It also creates a potential timing issue where the screen briefly renders before the bypass fires.

Mitigation & Contingency

Mitigation: Implement the single-org bypass entirely in OrgSelectionService.getPersistedSelection() or as a route guard — the screen should never be pushed onto the navigation stack for single-org users. The OrgSelectionScreen widget itself has no knowledge of this bypass.

Contingency: If the route guard approach is deferred to the routing epic, add a local guard in the OrgSelectionScreen's initState that calls OrgSelectionService, auto-selects if count is 1, and navigates forward before the first frame is painted, using a loading indicator to prevent the flash.