Unit-test analytics tracker buffer and dispatch
epic-expense-type-selection-foundation-task-006 — Write unit tests covering: event enqueuing up to buffer capacity, automatic flush on capacity exceeded, flush on app-lifecycle background signal, no-op behaviour when the dispatch sink is unavailable, and serialisation of all AnonymisedExpenseEvent variants. Mock the sink; do not make real network calls.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 3 - 413 tasks
Can start after Tier 2 completes
Implementation Notes
The lifecycle signal test requires calling the tracker's didChangeAppLifecycleState(AppLifecycleState.paused) method directly rather than going through the Flutter binding — structure the tracker so this method is accessible for testing. For the FakeAsync timer test, the tracker must accept an optional Clock or TimerFactory in its constructor (or via the Riverpod override) so the fake clock can control the periodic timer. The 'no-op when sink throws' test should verify that trackEvent() and the public interface remain callable after a sink failure — the tracker must not enter a broken state. Use verify(mockSink.dispatch(captureAny)) from mockito to assert both call count and argument content.
Testing Requirements
Pure unit tests with flutter_test. Use mockito's @GenerateMocks([AnalyticsSink]) or a handwritten mock implementing the AnalyticsSink interface. Use the fake_async package to control Timer.periodic without real time delays. Group tests by scenario: 'buffer management', 'flush triggers', 'error resilience', 'serialisation'.
Each group should have 2-3 focused tests. Avoid testing internals — test only the public API (trackEvent, flush, lifecycle signal) and the observable side effect (mockSink.dispatch call count and arguments).
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.