Validate org-configurable threshold end-to-end
epic-receipt-capture-and-attachment-ui-accessibility-task-009 — Write an integration test that provisions two organizations with different receipt thresholds (e.g., Blindeforbundet at 100 NOK, a test org at 500 NOK) and verifies that the attachment indicator switches to required at the correct amount for each org without a code change. Confirm that the threshold value is read from the org config repository and not hardcoded anywhere in the UI layer.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Structure the test using a TestOrgFixture helper class that encapsulates Supabase service role calls for creating and deleting test organizations and users. This helper is reusable across the test suite. Use flutter_test's integration_test package for widget-level integration testing rather than a full e2e driver test to keep execution time low. The test should import the Riverpod ProviderContainer and override the org config provider with real Supabase data (not mocked) to validate the full data path.
For the static analysis assertion, implement as a Dart test that reads the source file as a string and asserts no threshold literals — this is a simple but effective guard against regression. Document the test environment requirements (Supabase test project URL and anon key) in the project's .env.example file.
Testing Requirements
Integration tests (flutter_test integration_test package): use Supabase service role client in test setup to insert org rows with different receipt_threshold values and create test user accounts. Use pumpWidget with the full app routing or a scoped widget tree that includes the expense form and indicator. Simulate amount input via tester.enterText(). Assert indicator semantic label or widget state after each amount change.
Teardown: delete test orgs and users via service role client in tearDown(). Static analysis step: run grep in CI over the indicator widget file and its transitive UI imports to confirm absence of threshold literals.
Flutter's accessibility live region support (SemanticsProperties.liveRegion) has known inconsistencies between iOS VoiceOver and Android TalkBack, and between Flutter versions. Threshold-crossing announcements may fail to fire or double-fire, breaking the accessibility contract for Blindeforbundet users.
Mitigation & Contingency
Mitigation: Test live region announcements on physical devices with VoiceOver and TalkBack enabled from the first iteration. Use the AccessibilityLiveRegionAnnouncer component pattern already established in the project. Verify announcement timing relative to Bloc state emissions to avoid double-fires.
Contingency: If Flutter live regions prove unreliable, implement a platform-channel fallback that calls UIAccessibility.post(notification:) on iOS and AccessibilityManager.sendAccessibilityEvent() on Android directly, bypassing Flutter's abstraction.
The org-configurable threshold must be available at form-render time. If the threshold configuration is fetched asynchronously and not cached, the indicator may briefly show the wrong state (e.g., 'optional' before the threshold loads), confusing users and potentially allowing invalid submissions.
Mitigation & Contingency
Mitigation: Ensure the receipt threshold validator loads and caches the org configuration at app startup or organization selection time, not lazily on form open. Use a loading state in the indicator widget rather than defaulting to 'optional' while configuration is pending.
Contingency: If startup caching is not feasible, treat an unknown threshold as 'receipt required' (fail safe) and surface a clear loading indicator until the configuration resolves, preventing invalid submissions while the config loads.