high priority medium complexity testing pending testing specialist Tier 3

Acceptance Criteria

blocTest for successful load: emitting LoadChapterMemberships causes state sequence [ChapterMembershipLoading, ChapterMembershipLoaded(chapters: <list>)]
blocTest for trackPendingAdd: calling trackPendingAdd with a valid chapter ID appends it to pendingAdds in the emitted state
blocTest for 5-chapter limit: when existing affiliations + pendingAdds >= 5, calling trackPendingAdd emits MaxChaptersExceeded state
blocTest for commitChanges success: mock service returns success, state sequence ends with ChapterMembershipSuccess
blocTest for commitChanges error: mock service throws/returns error, state sequence ends with ChapterMembershipError containing error message
blocTest for rollbackChanges: pendingAdds is cleared and state reverts to last ChapterMembershipLoaded snapshot
All tests use mockito-generated mocks for MultiChapterMembershipService — no real Supabase calls
Test file is co-located at test/chapter_membership_cubit_test.dart
All 6 scenario tests pass with flutter test and 0 skipped
Test coverage for ChapterMembershipCubit reaches ≥ 95% branch coverage

Technical Requirements

frameworks
Flutter
BLoC
bloc_test
mockito
build_runner
flutter_test
data models
ChapterMembership
ChapterMembershipState
ChapterMembershipCubit
MultiChapterMembershipService
performance requirements
Each individual blocTest must complete in under 500ms
No async timers or delays unless testing debounce behavior
security requirements
Test fixtures must not contain real member IDs or personal data — use generated UUIDs

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use blocTest for all state-sequence tests. Avoid testing internal cubit fields directly — assert only via emitted states to maintain encapsulation. For error state tests, use thenThrow on the mock to simulate network failures and ensure the cubit catches all exceptions. Seed the cubit using the seed parameter of blocTest rather than calling methods in act when a specific initial state is required.

Keep test data in a separate fixtures file (test/fixtures/chapter_membership_fixtures.dart) for reusability. Make sure mockito annotations are regenerated (dart run build_runner build) before test run, and add this step to CI.

Testing Requirements

All tests are pure unit tests using bloc_test's blocTest helper. Each test must declare: build (cubit factory), act (event/method call), expect (ordered state list). Use @GenerateMocks([MultiChapterMembershipService]) and run build_runner to generate mocks. Add a setUp block to reinitialize mocks between tests.

For the 5-chapter limit test, seed the cubit with 4 existing affiliations then call trackPendingAdd once more and assert MaxChaptersExceeded. For rollbackChanges, first call trackPendingAdd twice, then rollbackChanges, and assert state returns to the pre-pending snapshot. Run flutter test --coverage and verify branch coverage with lcov.

Component
Chapter Membership Cubit
service medium
Epic Risks (3)
high impact medium prob technical

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.

medium impact medium prob technical

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.

medium impact high prob technical

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.