high priority medium complexity testing pending testing specialist Tier 5

Acceptance Criteria

blocTest for initialization: build() emits a single ActivityRegistrationIdle state with activityType pre-filled from RegistrationDefaultsManager.getDefaults()
blocTest for step advancement blocked: act triggers advanceStep() with a missing required field, expect emits ActivityRegistrationValidationError with a non-empty fieldErrors map
blocTest for step advancement success: act triggers advanceStep() with all required fields populated, expect emits the next step state without validation errors
blocTest for goBack: act advances two steps then calls goBack(), expect final state reflects step N-1 with all previously entered data preserved (no data loss)
blocTest for submit success: act calls submitRegistration(), expect emits [ActivityRegistrationSubmitting, ActivityRegistrationSuccess] in that exact order
blocTest for submit success record: the ActivityRecord in ActivityRegistrationSuccess has syncStatus == pending and id starting with 'local_'
blocTest for submit failure: stub service to throw, expect emits [ActivityRegistrationSubmitting, ActivityRegistrationFailure] with non-empty errorMessage
blocTest for defaults saved: after submit success, verify(mockDefaultsManager.saveLastUsedActivityType()).called(1) with the correct activity type id
blocTest for defaults not saved on failure: after submit failure, verify(mockDefaultsManager.saveLastUsedActivityType()).called(0)
All tests pass with flutter_test runner and no analyzer warnings

Technical Requirements

frameworks
Flutter
BLoC
bloc_test
flutter_test
mocktail
apis
ActivityRegistrationService
RegistrationDefaultsManager
data models
activity
activity_type
performance requirements
All bloc_test cases must run without real timers — use FakeAsync or bloc_test's built-in async handling
security requirements
No real credentials or PII in test fixtures — use synthetic data only

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

The critical subtlety for the goBack test is ensuring the cubit stores a state history stack internally — verify this is implemented as an immutable list of past states, not a mutable stack. For the initialization test, stub RegistrationDefaultsManager.getDefaults() to return a known defaults object and assert the pre-filled activityType matches. For the submit idempotency case (submitting while already submitting), add a test that calls submitRegistration() twice in act and verifies only one Submitting state is emitted. Use mocktail's any() matcher for arguments when the exact value is not the focus of the test, but use exact matchers when testing the saved activity type ID to catch regressions.

Testing Requirements

Use bloc_test package's blocTest() function for all Cubit tests — do not use manual emit() testing. Each blocTest must specify build, act, expect, and verify sections. Group tests by behavior category (initialization, navigation, submission). Use a shared buildCubit() helper in setUp() to avoid duplication.

For the goBack data-loss test, populate specific field values in act before navigating back and assert those values appear in the restored state. Run flutter test --coverage and confirm 100% branch coverage on the Cubit's state machine logic.

Component
Activity Registration Cubit
service medium
Epic Risks (3)
medium impact medium prob technical

The wizard Cubit manages multiple concurrent state slices (current step, each field value, submission status, error state). As the number of wizard steps grows, the state class can become unwieldy, making it difficult to reason about transitions, leading to subtle bugs where advancing a step resets a previously filled field.

Mitigation & Contingency

Mitigation: Use an immutable state model (copyWith pattern) with a separate sealed class per wizard step state. Keep the Cubit's emit calls minimal and always derive the next state from the current state to prevent accidental field resets. Document the state machine transitions explicitly in code comments.

Contingency: If state complexity becomes unmanageable, split into a parent WizardCubit (owns step navigation and submission) and per-step child Cubits (own individual field state), coordinating via a shared repository layer.

high impact medium prob scope

Organisation-specific compensation eligibility rules (e.g., activity type + duration thresholds) are business logic that may change independently of the app release cycle. Hardcoding these rules in ActivityRegistrationService means rule changes require a new app deployment, causing delays and potential financial errors if the deployed version uses outdated rules.

Mitigation & Contingency

Mitigation: Model compensation rules as configuration fetched from Supabase (stored per organisation), cached locally. ActivityRegistrationService reads from cache with a fallback to hardcoded defaults for offline scenarios. Design the rule schema to be extensible without code changes.

Contingency: If dynamic rules are not ready for initial release, ship with hardcoded rules and a feature flag that enables the remote-config path. Document the rule structure clearly so coordinators can trigger a rule update via a Supabase dashboard entry rather than a code deployment.

medium impact low prob integration

The last-used activity type stored in RegistrationPreferencesStore may become invalid if the organisation administrator deactivates that activity type between sessions. The Cubit would pre-populate a deleted type, and either the UI would show a missing item or submission would fail with a foreign-key constraint error.

Mitigation & Contingency

Mitigation: In RegistrationDefaultsManager, validate the retrieved last-used activity type ID against the current list of active types fetched from the activity type repository. If the stored ID is not in the active list, fall back to the first active type alphabetically.

Contingency: If validation cannot be performed offline, surface a non-blocking warning in the activity type step ('Your previously used activity type is no longer available') and require the user to make a new selection before advancing.