Unit-test accessibility service label correctness
epic-expense-type-selection-foundation-task-009 — Write unit tests asserting: semantic labels for all ExpenseType variants in selected/unselected/disabled states, conflict announcement strings include both type names, focus-order hints return types sorted by display priority, and debounce prevents duplicate announcements within 150 ms window. Validate all label strings contain no placeholder tokens.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 3 - 413 tasks
Can start after Tier 2 completes
Implementation Notes
Instantiate ExpenseTypeAccessibilityService with a controlled clock (inject a DateTime provider or use FakeAsync.run). Avoid any dependency on Flutter widget rendering — this is a pure Dart service test; no pumpWidget calls needed. Use a StreamController or simple callback list as the announcement sink so you can assert emissions without real platform channels. Parameterise the state-label test using a data-driven table (List of tuples) so adding new ExpenseType variants automatically expands coverage.
For the placeholder check, define the regex once as a top-level const and reuse it across all label assertions to keep the test DRY.
Testing Requirements
Use flutter_test with fake_async (via flutter_test's FakeAsync) to simulate time progression for debounce tests. Write example-based tests for each ExpenseType variant × state combination. Add a regex-based assertion that scans every returned label for common placeholder patterns (e.g. /\{\{.*?\}\}/, /%[sd]/, /<.*?>/).
Test the focus-order sort with a shuffled input list and assert the returned order matches expected priority. For debounce, schedule two rapid calls at t=0 and t=100 ms, advance fake clock to t=200 ms, and assert only one emission. Then schedule two calls at t=0 and t=200 ms and assert two emissions. Aim for 100% branch coverage of the service's public API.
The compatibility matrix might be under-specified in source documentation. If a new organisation adds expense types or redefines rules, hardcoded pairwise logic becomes a maintenance liability and can silently allow previously excluded combinations.
Mitigation & Contingency
Mitigation: Model the matrix as a const Map<ExpenseType, Set<ExpenseType>> rather than if-else chains; add a unit test that exhaustively asserts every pair combination so any future matrix change forces explicit test updates.
Contingency: If per-organisation matrix variants are requested before the epic closes, extract matrix loading into expense-type-config with an org-override slot and defer per-org configuration to the repository epic.
VoiceOver (iOS) and TalkBack (Android) handle Semantics widget announcements differently in Flutter. Live-region behaviour for disabled state changes is inconsistent across Flutter versions and may require platform-specific workarounds that are not yet documented.
Mitigation & Contingency
Mitigation: Write accessibility integration tests using Flutter's SemanticsController targeting both iOS and Android simulators from the outset; pin to a Flutter version known to handle Semantics.liveRegion correctly.
Contingency: If platform parity is unachievable before release, ship with a known gap documented in the WCAG audit log and schedule a dedicated accessibility sprint; do not block other epics.