Context
The bootstrap layer is untested today:
| File |
Lines |
Direct tests |
lib/main.dart |
140 |
0 |
lib/setup/routing/router_config.dart |
303 |
0 |
lib/setup/di.dart |
254 |
0 (consumed transitively) |
lib/setup/lifecycle_initializer.dart |
76 |
1 file, 3 testWidgets cases (hidden, paused, resumed) |
lib/setup/routing/routes/*.dart |
53 total |
0 |
A wrong DI registration, a broken route name, or a missed guard transition makes the app crash on cold start. Today no spec catches this before the iOS/Android build.
How navigation actually works in the repo
This is intentionally spelled out because previous iterations assumed GoRouter redirects — that's not how it works here:
router_config.dart:51-303 contains zero redirect: clauses on GoRouter or on any GoRoute. The route table is flat name-and-path declarations only.
- Routing decisions are imperative in
lib/main.dart:113-138 (_WalletAppState._navigate()). It reads HomeBloc.state + PinAuthCubit.state and calls routerConfig.goNamed(targetRoute).
- The ladder has eight branches (one early-return plus seven explicit conditions):
isLoadingWallet → early return, no navigation (lib/main.dart:117)
!softwareTermsAccepted → AppRoutes.home
!hasWallet → OnboardingRoutes.welcome
!onboardingCompleted → OnboardingRoutes.completed
!isPinSetup → PinRoutes.setup
!isPinVerified → PinRoutes.verify
openWallet == null → trigger LoadCurrentWalletEvent and wait
- otherwise →
AppRoutes.dashboard
Tests must drive HomeBloc / PinAuthCubit states and assert which goNamed(...) fires, not mock GoRouter.redirect.
Actual route paths (taken from router_config.dart:51-303)
/home (initial), /welcome, /createWallet, /restoreWallet, /verifySeed, /onboardingComplete, /debugAuth (kDebugMode-only at lib/setup/routing/router_config.dart:90-95), /pinGate, /setupPin, /verifyPin, /dashboard (+ child transactionHistory), /buy, /sell, /sellBitbox, /legalDisclaimer, /legalDocument, /termsOfUse, /kyc, /receive, /settings (+ 11 direct children including userData which has 3 sub-children), /support (+ tickets, create, chat/:uid), /webView.
Route paths are camelCase. Route names (e.g. AppRoutes.dashboard = 'dashboard') and route paths (/dashboard) are two separate namespaces — tests should use the named API. Note also that the route name LegalRoutes.terms = 'terms' maps to path /termsOfUse.
Scope
setup_di_test.dart (new)
router_config_test.dart (new)
main_navigate_test.dart (new)
lifecycle_initializer_test.dart (extend)
Deep-link / custom-URI handling
The repo is in an inconsistent state today:
ios/Runner/Info.plist:25-37 declares CFBundleURLTypes with scheme realunit-wallet (URL name realunit) and FlutterDeepLinkingEnabled: true (line 40-41).
android/app/src/main/AndroidManifest.xml:36-39 has only the LAUNCHER intent — no <intent-filter> with android:scheme.
lib/** has no Dart handler for getInitialLink/uni_links/app_links.
This is owned by a separate decision-issue (see Related). When that lands, the deep-link tests get added here as a follow-up commit.
Acceptance criteria
- New test files live under
test/setup/ (mirror lib structure)
- All specs run inside the existing
Analyze & Test job; no native plugin channels opened in test
- The eight-branch coverage of
_navigate() is the load-bearing assertion — if any branch lacks an explicit spec, the issue is not done
- Mock surface documented at the top of each new test file
Estimated effort
| Sub-task |
Days |
setup_di_test.dart (incl. mocking platform channels) |
1.5 |
router_config_test.dart (22 top-level + child routes) |
1.5 |
main_navigate_test.dart (8 branches) |
0.5 |
lifecycle_initializer_test.dart extension (cancelSync assertion) |
0.25 |
Allow-list / convention docs in docs/testing.md |
0.25 |
| Total |
~4 engineer-days |
Excludes the deep-link decision (separate issue) and any handler implementation (additional 2-4 days if Option B is picked).
Related
Context
The bootstrap layer is untested today:
lib/main.dartlib/setup/routing/router_config.dartlib/setup/di.dartlib/setup/lifecycle_initializer.darttestWidgetscases (hidden,paused,resumed)lib/setup/routing/routes/*.dartA wrong DI registration, a broken route name, or a missed guard transition makes the app crash on cold start. Today no spec catches this before the iOS/Android build.
How navigation actually works in the repo
This is intentionally spelled out because previous iterations assumed
GoRouterredirects — that's not how it works here:router_config.dart:51-303contains zeroredirect:clauses onGoRouteror on anyGoRoute. The route table is flat name-and-path declarations only.lib/main.dart:113-138(_WalletAppState._navigate()). It readsHomeBloc.state+PinAuthCubit.stateand callsrouterConfig.goNamed(targetRoute).isLoadingWallet→ early return, no navigation (lib/main.dart:117)!softwareTermsAccepted→AppRoutes.home!hasWallet→OnboardingRoutes.welcome!onboardingCompleted→OnboardingRoutes.completed!isPinSetup→PinRoutes.setup!isPinVerified→PinRoutes.verifyopenWallet == null→ triggerLoadCurrentWalletEventand waitAppRoutes.dashboardTests must drive
HomeBloc/PinAuthCubitstates and assert whichgoNamed(...)fires, not mockGoRouter.redirect.Actual route paths (taken from
router_config.dart:51-303)/home(initial),/welcome,/createWallet,/restoreWallet,/verifySeed,/onboardingComplete,/debugAuth(kDebugMode-only atlib/setup/routing/router_config.dart:90-95),/pinGate,/setupPin,/verifyPin,/dashboard(+ childtransactionHistory),/buy,/sell,/sellBitbox,/legalDisclaimer,/legalDocument,/termsOfUse,/kyc,/receive,/settings(+ 11 direct children includinguserDatawhich has 3 sub-children),/support(+tickets,create,chat/:uid),/webView.Route paths are camelCase. Route names (e.g.
AppRoutes.dashboard = 'dashboard') and route paths (/dashboard) are two separate namespaces — tests should use the named API. Note also that the route nameLegalRoutes.terms = 'terms'maps to path/termsOfUse.Scope
setup_di_test.dart(new)GetItregistration chain inlib/setup/di.dart(setupBlocsends at line 223 — the section through line 223 is the load-bearing registration; lines 224-254 are unrelated migration helpers). Assert every registered type resolves (getIt<WalletService>(),getIt<DfxAuthService>(), every cubit factory). Mockpath_provider,FlutterSecureStorage, and the DriftAppDatabasesofinishSetup()doesn't open native channels.NetworkMode.mainnet ↔ testnet(defined atlib/packages/config/network_mode.dart:4-20) rebinds:apiHost(api.dfx.swiss↔dev.api.dfx.swiss),asset,ethAssetId,zchfAssetId(all perlib/packages/config/api_config.dart:13-22). Storage and wallet bindings (di.dart:78-93) are environment-independent.router_config_test.dart(new)MaterialApp.router(routerConfig: routerConfig), navigate viacontext.goNamed(...)using the constants inroutes/*.dart, assert the expected page widget rendered./dashboard/transactionHistory,/support/tickets,/support/create,/support/chat/:uid, and every/settings/*child (11 direct + 3 user_data sub-children).kDebugModeguard on/debugAuth— in release-mode builds the route must not be registered.main_navigate_test.dart(new)_navigate()(lib/main.dart:113-138) by fakingHomeBloc.stateandPinAuthCubit.state. The first branch is theisLoadingWalletearly-return — assert no navigation fires.hasWallet == true), assertLoadCurrentWalletEventis added and no navigation fires.lifecycle_initializer_test.dart(extend)hidden→walletService.lockCurrentWallet().pausedis NOT a no-op —_onPaused()atlib/setup/lifecycle_initializer.dart:64-66callsBalanceService.cancelSync(). Current test only assertslockCurrentWalletis NOT called; add an explicit assertion thatcancelSyncIS called.resumedbranch: no-op today; assert nothing fires (regression guard for unexpected resume logic creep).lifecycle_initializer.dart. Drop them from this issue or implement the behaviour first and then test it.Deep-link / custom-URI handling
The repo is in an inconsistent state today:
ios/Runner/Info.plist:25-37declaresCFBundleURLTypeswith schemerealunit-wallet(URL namerealunit) andFlutterDeepLinkingEnabled: true(line 40-41).android/app/src/main/AndroidManifest.xml:36-39has only theLAUNCHERintent — no<intent-filter>withandroid:scheme.lib/**has no Dart handler forgetInitialLink/uni_links/app_links.This is owned by a separate decision-issue (see Related). When that lands, the deep-link tests get added here as a follow-up commit.
Acceptance criteria
test/setup/(mirror lib structure)Analyze & Testjob; no native plugin channels opened in test_navigate()is the load-bearing assertion — if any branch lacks an explicit spec, the issue is not doneEstimated effort
setup_di_test.dart(incl. mocking platform channels)router_config_test.dart(22 top-level + child routes)main_navigate_test.dart(8 branches)lifecycle_initializer_test.dartextension (cancelSync assertion)docs/testing.mdExcludes the deep-link decision (separate issue) and any handler implementation (additional 2-4 days if Option B is picked).
Related
lib/main.dart,lib/setup/**lib/packages/config/network_mode.dart,lib/packages/config/api_config.dart