high priority medium complexity testing pending testing specialist Tier 5

Acceptance Criteria

Test 1 (session restore): navigate to tab 3, push sub-route A, push sub-route B, simulate AppLifecycleState.paused then AppLifecycleState.resumed, assert tab 3 is selected in the bottom nav and sub-routes A and B are present in the branch navigator's page stack
Test 2 (logout clears state): navigate to tab 2, push a sub-route, trigger logout, assert the app rebuilds at tab 0 with an empty stack, then simulate a new login and confirm the restored state is default (tab 0, empty stacks)
Both tests use only flutter_test widget testing infrastructure — no real device, TestFlight, or network required
NavigationStateRepository is backed by a fake in-memory implementation for test isolation
GoRouter instance is constructed from the real router configuration (not mocked) to validate the actual routing table
Assertions use widget finders on actual rendered widgets (e.g., find.byKey for tab indicators and route-specific content) not just Riverpod state reads
Test completes in under 10 seconds on CI
Test is placed in the integration_test/ directory if using flutter_test's WidgetTester with pumpAndSettle, or test/ if using pure unit-style widget tests

Technical Requirements

frameworks
Flutter
GoRouter
Riverpod
flutter_test
apis
WidgetTester.pumpWidget
WidgetTester.pumpAndSettle
AppLifecycleListener (or WidgetsBindingObserver mock)
ProviderContainer overrides
GoRouter test helpers (navigatorKey)
data models
TabStateSnapshot
NavigationStateRepository
TabStateManager
performance requirements
No real timers — use tester.pump(Duration) to advance time deterministically
No SharedPreferences or SQLite I/O — all persistence via injected fake repository
security requirements
Test should not include real BankID or Vipps authentication flows — stub the auth provider to simulate logged-in state
ui components
BottomNavigationBar
Shell scaffold
At least two distinct sub-route page widgets with identifiable keys for assertion

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

The trickiest part is simulating backgrounding and restoring without a real device. Use tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.paused) followed by handleAppLifecycleStateChanged(AppLifecycleState.resumed) — this triggers the WidgetsBindingObserver callbacks that TabStateManager listens to. If GoRouter uses a navigatorKey, capture it after pumpWidget and use it to inspect the current route stack directly. Build a minimal test app scaffold that includes only the navigation shell and two or three stub page widgets — avoid pumping the full production widget tree to keep test speed acceptable.

Use ValueKey or GlobalKey on each stub page widget so finders are unambiguous.

Testing Requirements

This task IS the testing task. The test covers the integration seam between GoRouter, TabStateManager, and NavigationStateRepository. Use ProviderScope with overrides to inject the fake repository. Simulate AppLifecycleState changes by calling tester.binding.handleAppLifecycleStateChanged() directly.

For logout simulation, call the auth provider's logout method (or override the auth state provider to return logged-out). Assert both the Riverpod state (via ProviderContainer reads) AND the rendered widget tree (via widget finders) to ensure the test is not a false positive that only checks in-memory state. Run this test in CI with flutter test --coverage to confirm line coverage of the navigation foundation modules.

Component
Tab State Manager
service medium
Epic Risks (2)
high impact medium prob technical

StatefulShellRoute branch navigator state can interact unexpectedly with GoRouter's imperative navigation (go, push, replace), causing state snapshots to desync from actual route stacks. This could manifest as a user returning to a tab and seeing a different screen than expected, breaking the core motor-fatigue promise.

Mitigation & Contingency

Mitigation: Write integration tests that simulate cross-tab navigation with nested pushes before any UI layer is built. Pin go_router to a tested minor version and review the StatefulShellRoute changelog before upgrading.

Contingency: If branch navigator state consistently desyncs, fall back to a manual stack snapshot strategy using a custom NavigatorObserver that records and replays navigation events independently of StatefulShellRoute internals.

medium impact medium prob technical

Persisted navigation stacks in shared_preferences can become stale or corrupt if route paths are renamed during development, causing app crashes or infinite redirect loops on cold start for users who have an old snapshot.

Mitigation & Contingency

Mitigation: Version the persisted schema with a format key. On app start, validate that all stored route paths exist in the current route config before restoring; silently discard invalid entries rather than crashing.

Contingency: Implement a safe-mode cold start that skips state restoration after a detected crash (via a dirty-launch flag written at startup and cleared on successful first frame), falling back to the default root tab.