high priority low complexity testing pending testing specialist Tier 3

Acceptance Criteria

Every ExpenseType variant (kilometre-reimbursement, transit-ticket, parking, toll, and any future additions) has a non-empty semantic label in each of the three states: selected, unselected, and disabled.
No label string contains unreplaced placeholder tokens (e.g. '{{type}}', '%s', or similar patterns).
Conflict announcement strings for any conflicting pair include the display names of BOTH involved ExpenseType values in the returned string.
Focus-order hints return a list sorted strictly by display priority (ascending integer order); ties are broken by stable enum declaration order.
Calling the announcement method twice within 150 ms produces exactly one announcement emission; calling it with a 150 ms+ gap produces two separate emissions.
All tests pass with flutter_test on both debug and release build modes.
Test file is co-located with the service under test and follows the `*_test.dart` naming convention.

Technical Requirements

frameworks
Flutter
flutter_test
data models
ExpenseType
ExpenseTypeState
ConflictAnnouncement
performance requirements
Debounce window tested with fake async timers — no real-time delays in test execution
Full test suite for this file completes in under 5 seconds
security requirements
Announcement strings must contain no PII or user-identifiable content — verify in assertions

Execution Context

Execution Tier
Tier 3

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.

Component
Expense Type Accessibility Service
service low
Epic Risks (2)
high impact medium prob scope

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.

medium impact medium prob technical

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.