Widget and accessibility tests for UI components
epic-multi-chapter-membership-handling-ui-task-012 — Write flutter_test widget tests for ChapterAffiliationsPanel (badge rendering, screen reader label content), DuplicateActivityWarningDialog (modal presence, button callbacks, audit log invocation), and ChapterAssignmentEditor (search filter, end-affiliation bottom sheet, max-chapter banner). Include SemanticsChecker.check() in each test to validate WCAG tree. Use WidgetTester.pumpAndSettle and verify focus trapping in dialog tests.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 5 - 253 tasks
Can start after Tier 4 completes
Implementation Notes
Wrap each test widget in MaterialApp + BlocProvider (or ProviderScope) to satisfy context dependencies. For the DuplicateActivityWarningDialog focus-trap test, note that Flutter's default dialog implementation wraps content in a FocusScope — assert FocusScope.of(context).hasFocus or use FocusManager.instance.primaryFocus to check the active node. For the bottom sheet test, call pumpAndSettle after the tap and use find.byType(ModalBottomSheet) or find.byKey to locate the sheet. Be aware that SemanticsChecker.check() is strict — if the preceding task (task-010) has not been completed and merged, placeholder Semantics may cause failures; ensure the test dependency is honoured in CI ordering.
For search filter tests, wrap the widget with a controlled TextEditingController rather than relying on internal state to make filter assertions deterministic.
Testing Requirements
Write three separate test groups (group() blocks) in a single file or split per component. Each group must call tester.ensureSemantics() at setUp and SemanticsChecker.check(tester) after every pumpAndSettle. For focus-trapping tests in DuplicateActivityWarningDialog, use tester.sendKeyEvent(LogicalKeyboardKey.tab) repeatedly and assert that the focused widget finder always finds a widget inside the dialog subtree. For the search filter test, set the text field value via tester.enterText and assert find.text for non-matching chapters returns findsNothing.
Provide all component dependencies through BlocProvider.value or ProviderScope overrides — never instantiate real services. Target ≥ 85% line coverage across the three widget files.
The Duplicate Activity Warning Dialog must announce itself to VoiceOver and TalkBack immediately on appearance. Flutter's default modal semantics do not guarantee focus shift to the dialog on all platform versions, risking silent appearance for screen reader users — a WCAG 2.2 failure.
Mitigation & Contingency
Mitigation: Wrap the dialog content in a Semantics node with liveRegion: true and explicitly request focus via FocusScope.of(context).requestFocus() on the dialog's primary action button in the post-frame callback. Test on physical iOS (VoiceOver) and Android (TalkBack) devices, not only simulators.
Contingency: If automatic focus fails on a specific platform version, add a platform-specific fallback using SemanticsService.announce() to force a live region announcement of the dialog's headline text.
The Chapter Membership Cubit tracks pending changes before commit to support the two-step add/confirm flow. If the user navigates away mid-edit or the app is backgrounded, uncommitted pending state could be replayed incorrectly on return, causing phantom affiliation additions or removals.
Mitigation & Contingency
Mitigation: Design the Cubit to hold pending changes only in transient in-memory state with no persistence. On any navigation-away event, emit a reset state that discards pending changes. Prevent accidental navigation during an active edit by showing a discard-changes confirmation dialog.
Contingency: If state desync is reported in production, add an explicit state reconciliation step in the Cubit's onResume handler that re-fetches the authoritative affiliation list from the repository and resets all pending state before re-rendering.
The Chapter Assignment Editor's searchable chapter list must load quickly. If the organisation has hundreds of chapters (NHF has 1,400 local chapters) and the full list is fetched on dialog open, the editor will be slow to display and the search will be sluggish.
Mitigation & Contingency
Mitigation: Scope the chapter list to only chapters within the coordinator's administrative scope (not all 1,400), leveraging the existing hierarchy access scope service. Implement server-side search with a minimum 2-character threshold and debounce to avoid excessive Supabase queries.
Contingency: If the scoped list is still too large, add local caching of the chapter list with a 15-minute TTL and an explicit refresh button, ensuring the editor is always responsive even on poor network conditions.