high priority low complexity testing pending testing specialist Tier 5

Acceptance Criteria

Widget test renders OrgCard in default unselected state without throwing errors
Widget test confirms selected visual state differs from unselected (border color, background, icon) when isSelected=true is passed
Tap callback is invoked exactly once when card is tapped in test; additional taps do not fire if card is already selected and onTap is guarded
Semantics node contains org name, role label string, and selection state (selected/not selected) readable by screen reader
AccessibilityGuideline.androidTapTargetGuideline and AccessibilityGuideline.iOSTapTargetGuideline assertions pass (minimum 48dp touch target)
meetsGuideline(textContrastGuideline) assertion passes for both selected and unselected states
When logoUrl is null, fallback initials widget renders with correct initials derived from org name
When logoUrl is provided, Image widget is rendered instead of initials
All tests pass with flutter test and produce zero analyzer warnings

Technical Requirements

frameworks
Flutter
flutter_test
data models
OrgCard
Organization
performance requirements
All widget tests complete in under 5 seconds total
No memory leaks; all controllers and streams disposed in tearDown
security requirements
No real org data or PII used in test fixtures; use synthetic stubs
ui components
OrgCard widget
Logo/initials fallback widget
Selection indicator (border or checkmark)
Semantics wrapper

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Use `tester.getSemantics(find.byType(OrgCard))` to inspect the semantics node — ensure the widget sets Semantics(label: ..., selected: ...) explicitly rather than relying on implicit semantics. For touch target tests, wrap OrgCard in a SizedBox and verify that the rendered hit-test area is at least 48x48dp. For initials fallback, stub logoUrl as null on the OrgCard model and verify no Image widget is in the tree while a Text widget with initials is present. Keep test fixtures in a dedicated `test/fixtures/org_card_fixtures.dart` file to avoid duplication with integration tests.

Testing Requirements

Unit/widget tests only using flutter_test. Cover 6 distinct test cases: (1) default render, (2) selected state, (3) tap callback, (4) semantics label, (5) touch target size via AccessibilityGuideline matchers, (6) logo fallback. Use WidgetTester.pumpWidget with a minimal MaterialApp wrapper. Assert semantics tree with tester.getSemantics().

Use expect(tester, meetsGuideline(...)) for accessibility assertions. No integration or golden tests required for this task.

Component
Organization Card Widget
ui low
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.