high priority high complexity testing pending testing specialist Tier 7

Acceptance Criteria

Widget test suite covers the complete happy-path expense registration flow: type selection → amount entry → receipt attachment → summary → submission, asserting the correct BLoC state emitted at each step.
Mutual-exclusion rule for expense types is tested: selecting 'km reimbursement' disables 'public transport' option and vice versa; tapping a mutually exclusive type emits ExpenseTypeConflictState and shows an error widget.
Amount threshold crossing is tested: entering an amount ≥ 100 NOK triggers the receipt attachment prompt widget; entering < 100 NOK skips the receipt step; both branches are covered with explicit state assertions.
Receipt attachment flow is tested: mock image picker returns a file, BLoC emits ReceiptAttachedState, and the summary screen renders a thumbnail; rejecting the picker returns the form to its previous state without regression.
Submission test verifies that ExpenseFormBloc emits [SubmittingState, SubmissionSuccessState] in sequence and that the mocked Supabase insert is called exactly once with the correct payload fields (type, amount, receipt_url, peer_mentor_id, date).
Coordinator attestation flow: a test using a mocked Supabase realtime stream verifies that a pending expense appears in the coordinator queue widget; tapping 'Approve' emits AttestationApprovedState; the queue item disappears or updates to 'Approved' status without requiring a full page reload.
Realtime update reflection test: mock Supabase channel fires an UPDATE event with status='approved'; the widget tree re-renders within one pump cycle to reflect the new status badge.
All BLoC state transitions are tested in isolation using bloc_test: initial → loading → success, initial → loading → error (network failure), and initial → loading → error (validation failure) branches.
Test coverage for this file group reaches ≥ 90% line coverage as reported by flutter test --coverage.
All tests pass with zero flakiness across 3 consecutive CI runs.

Technical Requirements

frameworks
Flutter
BLoC
flutter_test
bloc_test
mocktail
apis
Supabase Realtime (mocked)
Supabase PostgREST insert (mocked)
Image Picker (mocked)
data models
ExpenseRegistration
ExpenseType
ReceiptAttachment
CoordinatorAttestation
performance requirements
Each widget test must complete within 2 seconds
Full integration test suite must complete within 60 seconds on CI
No memory leaks — all BLoC instances closed in tearDown
security requirements
Mocked Supabase client must never make real network calls — enforce with MockClient assertion
Receipt image data must not be logged in test output
ui components
ExpenseTypeSelector widget
AmountEntryField widget
ReceiptAttachmentWidget
ExpenseSummaryCard
CoordinatorAttestationQueue
StatusBadge

Execution Context

Execution Tier
Tier 7

Tier 7 - 84 tasks

Can start after Tier 6 completes

Implementation Notes

Start by creating a shared test fixture file (expense_test_fixtures.dart) that defines reusable mock Supabase responses and sample ExpenseRegistration models — this prevents duplication across test files. For the mutual-exclusion test, directly call the BLoC's add(SelectExpenseTypeEvent) and use bloc_test's expect to assert the conflict state rather than relying on widget tap simulation, which is more fragile. For realtime tests, use a StreamController>> to emit mock Supabase events and inject it into the BLoC constructor via the test setup. The coordinator attestation test should use a FakeAsync zone to control time if any debounce/throttle logic exists in the BLoC.

Avoid testing UI pixel positions or exact colors — only assert widget presence/absence, semantic labels, and BLoC state correctness. All BLoC instances must be explicitly closed in tearDownAll to prevent test suite leaks that cause false failures on CI.

Testing Requirements

Use flutter_test for widget tests and bloc_test for isolated BLoC state transition tests. Mock the Supabase client using mocktail — define a MockSupabaseClient that stubs insert(), stream(), and from() calls. Use tester.pumpAndSettle() after async operations and tester.pump(Duration.zero) for immediate frame advances. Structure tests in three groups: (1) peer mentor expense registration flow, (2) coordinator attestation flow, (3) realtime update reflection.

Each group should have a happy path, at least one error path, and one edge case (e.g., threshold boundary at exactly 100 NOK). Run flutter test --coverage and assert ≥ 90% line coverage for all files under the expense registration feature directory.

Component
Expense Form BLoC
service high
Epic Risks (3)
medium impact medium prob dependency

The image_picker Flutter plugin requires platform-specific permissions (NSPhotoLibraryUsageDescription, camera permission) and behaves differently across iOS and Android versions. Permission denial or plugin misconfiguration can silently prevent receipt attachment.

Mitigation & Contingency

Mitigation: Configure all required permission strings in Info.plist and AndroidManifest.xml during initial plugin setup. Use the permission_handler package to check and request permissions before launching the picker, with clear user-facing explanations. Test on both platforms across at least two OS versions.

Contingency: If image_picker proves unreliable on a specific platform version, fall back to file_picker as an alternative that uses the OS document picker interface, which requires fewer permissions on some Android versions.

high impact medium prob technical

The expense form BLoC manages interconnected state across expense type selection, field visibility, receipt requirement, threshold evaluation, and submission flow. Incorrect state transitions can cause UI inconsistencies such as required receipt indicator not updating after amount change, or form appearing valid when mutual exclusion is violated.

Mitigation & Contingency

Mitigation: Model BLoC states as sealed classes with exhaustive pattern matching. Write state transition unit tests covering every combination of: type selection change, amount field change above/below threshold, receipt attachment/removal, and offline mode toggle. Use bloc_test for comprehensive state sequence assertions.

Contingency: If BLoC complexity becomes unmanageable, split into two BLoCs — one for type selection/exclusion state and one for field values/submission — coordinating via a parent provider, accepting the small overhead of inter-BLoC communication.

high impact medium prob technical

The expense type selector must enforce mutual exclusion visually by disabling options and showing conflict tooltips, while remaining fully accessible to screen reader users who cannot perceive visual disable states. Incorrect semantics labelling will fail WCAG 2.2 AA requirements critical for Blindeforbundet and HLF users.

Mitigation & Contingency

Mitigation: Use Flutter Semantics widgets to explicitly set disabled state and provide conflict explanations as semanticLabel strings on disabled options. Run accessibility audits with TalkBack and VoiceOver during widget development, not post-completion. Reference the project's accessibility test harness for required test coverage.

Contingency: If custom widget accessibility is difficult to certify, implement the selector as a standard Flutter Radio/Checkbox group with built-in accessibility semantics and an explanatory Text widget below each conflicting option, sacrificing visual elegance for guaranteed WCAG compliance.