audit report: factory vi.mock → prop-injection / __mocks__ migration (87 call sites, 12 modules) #560
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #554.
Full audit of all factory-style
vi.mock(module, factory)call sites infrontend/src/**/*.svelte.{spec,test}.ts, as scoped by #554. Methodology: exhaustivegrep -rn "vi\.mock("across*.spec.tsand*.test.ts, then filtered to.svelte.{spec,test}.tsfiles and classified per the three-bucket schema.Reference: ADR-012 — Browser-mode test mocking strategy
Scope
src/**/*.svelte.spec.tsandsrc/**/*.svelte.test.ts— 87 call sites across 12 mocked modules*.server.spec.ts,__meta__/*.test.ts, and non-svelte*.spec.ts— run in Node mode, birpc race concern from ADR-012 does not applyClassification Table
Files are relative to
frontend/src/.(b)
__mocks__/redirect — 72 call sites, 7 modulesStable, never-changing fakes. One shared file per module eliminates duplicate factory boilerplate and removes the birpc race surface for these modules entirely.
$app/forms— 13 call sites — start hereEvery factory is byte-for-byte identical:
{ enhance: () => () => {} }. One__mocks__/$app/forms.tsfile and a single-pass removal across 13 files.lib/person/genealogy/StammbaumCard.svelte.spec.ts$app/forms{ enhance: () => () => {} }— zero per-test variationlib/person/genealogy/StammbaumSidePanel.svelte.spec.ts$app/formsenhancelib/person/relationship/AddRelationshipForm.svelte.spec.ts$app/formslib/person/relationship/RelationshipChip.svelte.spec.ts$app/formsroutes/admin/groups/[id]/page.svelte.spec.ts$app/formsroutes/admin/tags/[id]/page.svelte.spec.ts$app/formsroutes/admin/tags/[id]/TagDeleteGuard.svelte.spec.ts$app/formsroutes/admin/tags/[id]/TagMergeZone.svelte.spec.ts$app/formsroutes/admin/users/[id]/page.svelte.spec.ts$app/formsroutes/admin/users/new/page.svelte.spec.ts$app/formsroutes/persons/[id]/edit/NameHistoryEditCard.svelte.spec.ts$app/formsroutes/persons/[id]/edit/NameHistoryEditCard.svelte.test.ts$app/formsroutes/persons/[id]/PersonMergePanel.svelte.spec.ts$app/forms$env/static/public— 1 call siteroutes/layout.svelte.spec.ts$env/static/public$lib/paraglide/runtime— 1 call sitelib/shared/primitives/LanguageSwitcher.svelte.spec.ts$lib/paraglide/runtime{ getLocale: () => 'de', setLocale: vi.fn() }— single consumer, never changes$lib/paraglide/messages.js— 1 in-scope call sitelib/shared/help/TranscribeCoachEmptyState.svelte.spec.ts$lib/paraglide/messages.js__mocks__version returns key-as-value identity functions — same module also used inlib/ocr/translateOcrProgress.spec.ts(out of scope, but shared__mocks__file covers it too)$app/stores— 2 call sites$app/storesis the legacy SvelteKit store API superseded by$app/state. Only two files still use it. Preferred approach: first migrate the host component (admin/tags/[id]/+page.svelte) from$app/storesto$app/stateso the mock disappears automatically; add__mocks__/$app/stores.tsonly as a safety net if stragglers remain.routes/admin/tags/[id]/page.svelte.spec.ts$app/stores__mocks__exports a writable store, tests.set()before renderroutes/admin/tags/[id]/page.svelte.test.ts$app/stores$app/state— 18 call sitesFactories vary by which properties each test needs (
navigating,page,page.url), but all return plain object stubs. A__mocks__/$app/state.tswith getter-based proxies fornavigatingandpage(backed by module-level variables tests can assign) covers every consumer.lib/document/EnrichmentBlock.svelte.spec.ts$app/statenavigating.toroutes/admin/entity-nav.svelte.spec.ts$app/statepage.url.pathnameroutes/admin/EntityNav.svelte.test.ts$app/statepage.url.pathnameroutes/admin/groups/GroupsListPanel.svelte.test.ts$app/statepage.urlroutes/admin/groups/layout.svelte.spec.ts$app/statepage.url.pathnameroutes/admin/layout.svelte.spec.ts$app/statepage.url.pathnameroutes/admin/tags/layout.svelte.spec.ts$app/statepage.url.pathnameroutes/admin/tags/TagTreeNode.svelte.test.ts$app/statepage.url.pathnameroutes/admin/users/layout.svelte.spec.ts$app/statepage.url.pathnameroutes/admin/users/UsersListPanel.svelte.test.ts$app/statepage.urlroutes/aktivitaeten/page.svelte.test.ts$app/statenavigatingandpage.urlroutes/AppNav.svelte.test.ts$app/statepage.url.pathnameroutes/documents/[id]/page.svelte.test.ts$app/statepage.urlroutes/documents/page.svelte.spec.ts$app/statenavigating.toroutes/documents/page.svelte.test.ts$app/statenavigatingandpage.urlroutes/error.svelte.test.ts$app/statepage.errorandpage.statusroutes/geschichten/page.svelte.spec.ts$app/statenavigating.toroutes/stammbaum/page.svelte.test.ts$app/statepage.url$app/navigation— 36 call sitesThe largest group. Factories differ in which nav functions they stub (
goto,invalidateAll,beforeNavigate, etc.), but all providevi.fn()stubs. A__mocks__/$app/navigation.tsthat exports every known function asvi.fn()lets each test import and spy on only what it needs, withvi.clearAllMocks()inafterEach.lib/document/BulkDocumentEditLayout.svelte.spec.ts$app/navigationgotostub onlylib/document/BulkSelectionBar.svelte.spec.ts$app/navigationgotostub onlylib/document/DocumentRow.svelte.spec.ts$app/navigationgotostub onlylib/document/DocumentRow.svelte.test.ts$app/navigationgoto+invalidateAllstubslib/notification/NotificationBell.svelte.spec.ts$app/navigationgoto+beforeNavigatestubslib/notification/NotificationDropdown.svelte.test.ts$app/navigationgotostub onlylib/person/genealogy/StammbaumSidePanel.svelte.spec.ts$app/navigationinvalidateAllstub onlylib/person/genealogy/StammbaumSidePanel.svelte.test.ts$app/navigationlib/shared/hooks/useUnsavedWarning.svelte.test.ts$app/navigationbeforeNavigatestubroutes/admin/groups/[id]/page.svelte.spec.ts$app/navigationbeforeNavigate+gotostubsroutes/admin/groups/new/page.svelte.test.ts$app/navigationroutes/admin/page.svelte.spec.ts$app/navigationgotostub onlyroutes/admin/page.svelte.test.ts$app/navigationroutes/admin/tags/[id]/page.svelte.spec.ts$app/navigationbeforeNavigate+goto+replaceStatestubsroutes/admin/tags/[id]/page.svelte.test.ts$app/navigationroutes/admin/users/[id]/page.svelte.spec.ts$app/navigationbeforeNavigate+gotostubsroutes/admin/users/new/page.svelte.test.ts$app/navigationroutes/aktivitaeten/page.svelte.test.ts$app/navigationroutes/briefwechsel/CorrespondenzHero.svelte.spec.ts$app/navigationgotostub onlyroutes/briefwechsel/page.svelte.spec.ts$app/navigationgotostub onlyroutes/briefwechsel/page.svelte.test.ts$app/navigationroutes/DocumentList.svelte.spec.ts$app/navigationgotostub onlyroutes/DocumentList.svelte.test.ts$app/navigationroutes/documents/bulk-edit/page.svelte.test.ts$app/navigationroutes/documents/[id]/page.svelte.test.ts$app/navigationroutes/documents/page.svelte.spec.ts$app/navigationgotostub onlyroutes/documents/page.svelte.test.ts$app/navigationroutes/DropZone.svelte.spec.ts$app/navigationinvalidateAllstub onlyroutes/DropZone.svelte.test.ts$app/navigationroutes/geschichten/[id]/edit/page.svelte.test.ts$app/navigationroutes/geschichten/new/page.svelte.test.ts$app/navigationroutes/geschichten/page.svelte.spec.ts$app/navigationgotostub onlyroutes/geschichten/page.svelte.test.ts$app/navigationroutes/page.svelte.spec.ts$app/navigationgoto+invalidateAllstubsroutes/persons/page.svelte.spec.ts$app/navigationgotostub onlyroutes/persons/page.svelte.test.ts$app/navigation(a) Prop-injection — 10 call sites, 2 modules
Domain services with a provider pattern. Replace the factory mock with a
.test-host.sveltewrapper that provides the service via context, then thread the service instance to the test via a prop callback (onReady).$lib/shared/services/confirm.svelte— 8 call sitesconfirm.test-host.sveltealready exists (lib/shared/services/confirm.test-host.svelte). Migration is mechanical: render the test-host wrapping the component under test, capture the service viaonReady, remove thevi.mockcall.lib/document/transcription/TranscriptionEditView.svelte.test.ts$lib/shared/services/confirm.svelteroutes/admin/groups/[id]/page.svelte.test.ts$lib/shared/services/confirm.svelteroutes/admin/tags/[id]/page.svelte.test.ts$lib/shared/services/confirm.svelteroutes/admin/users/[id]/page.svelte.test.ts$lib/shared/services/confirm.svelteroutes/documents/[id]/edit/page.svelte.test.ts$lib/shared/services/confirm.svelteroutes/documents/[id]/page.svelte.test.ts$lib/shared/services/confirm.svelteroutes/geschichten/[id]/page.svelte.test.ts$lib/shared/services/confirm.svelteroutes/persons/[id]/edit/page.svelte.test.ts$lib/shared/services/confirm.svelte$lib/notification/notifications.svelte— 2 call sitesNo test-host yet.
notificationStoreis a stateful service. Needs a newnotification.test-host.svelte(modelled onconfirm.test-host.svelte) before migration can begin.lib/notification/NotificationBell.svelte.spec.ts$lib/notification/notifications.sveltevi.hoistedclosures for per-testnotificationslistroutes/aktivitaeten/page.svelte.test.ts$lib/notification/notifications.svelte(c) Keep as factory — 5 call sites, 3 modules
Component stubs (
default: () => null). No provider pattern exists for Svelte components. Factory is the correct and low-risk approach here.lib/person/genealogy/StammbaumSidePanel.svelte.spec.ts$lib/person/PersonTypeahead.sveltedefault: () => nullis idiomaticlib/person/relationship/AddRelationshipForm.svelte.spec.ts$lib/person/PersonTypeahead.svelteroutes/persons/[id]/PersonMergePanel.svelte.spec.ts$lib/person/PersonTypeahead.sveltelib/person/genealogy/StammbaumCard.svelte.spec.ts$lib/person/relationship/RelationshipChip.sveltelib/person/genealogy/StammbaumCard.svelte.spec.ts$lib/person/relationship/AddRelationshipForm.svelteSummary
__mocks__/redirectOut-of-scope findings
Files outside
*.svelte.{spec,test}.ts— run in Node mode, no birpc concern — noted for completeness.$lib/shared/api.server.server.spec.ts)$env/dynamic/privatedocuments/[id]/page.server.spec.ts)$lib/shared/errorsbriefwechsel/page.server.spec.ts)$lib/paraglide/messages.jslib/ocr/translateOcrProgress.spec.ts)__mocks__file from in-scope migration covers this consumer tooRoadmap
Phase 1 —
__mocks__/redirect (class b) — ~2 daysPure file additions plus factory removal. No test-logic changes needed.
Priority within phase 1:
$app/forms(13 files) — start here; factory is always identical, zero risk. Createfrontend/src/__mocks__/$app/forms.ts:Remove all 13
vi.mock('$app/forms', ...)calls in one pass.$env/static/public(1 file) — trivial; createfrontend/src/__mocks__/$env/static/public.tswith the poll-interval constant.$lib/paraglide/runtime(1 file) — createfrontend/src/__mocks__/$lib/paraglide/runtime.tswith{ getLocale: () => 'de', setLocale: vi.fn() }.$lib/paraglide/messages.js(1 in-scope + 1 out-of-scope consumer) — createfrontend/src/__mocks__/$lib/paraglide/messages.jswith identity functions for all message keys; covers both consumers simultaneously.$app/state(18 files) — createfrontend/src/__mocks__/$app/state.tswith getter-based proxies backed by module-level variables:Tests that need custom values import
_navigating/_pageand assign before render.$app/navigation(36 files, largest batch) — createfrontend/src/__mocks__/$app/navigation.tsexporting all known nav functions asvi.fn(). Tests import the mock functions for assertions; addvi.clearAllMocks()inafterEach. Functions needed:goto,invalidate,invalidateAll,beforeNavigate,afterNavigate,preloadCode,preloadData,pushState,replaceState,disableScrollHandling,onNavigate.$app/stores(2 files) — first check ifadmin/tags/[id]/+page.svelteuses$app/storesand can be migrated to$app/state; if yes, both mocks disappear automatically. Otherwise addfrontend/src/__mocks__/$app/stores.ts.Phase 2 — prop-injection (class a) — ~3 days
Requires creating or extending test-host components.
$lib/shared/services/confirm.svelte(8 files) — test-host already exists atlib/shared/services/confirm.test-host.svelte. For each of the 8 files: wrap the component under test in the test-host, capture theConfirmServiceviaonReady, then remove thevi.mockcall. ~0.5 day per batch of similar files.$lib/notification/notifications.svelte(2 files) — createlib/notification/notification.test-host.sveltemodelled onconfirm.test-host.svelte; it should callprovideNotificationStore(), acceptonReady: (store) => void, and render<NotificationDropdown />(or similar provider component). Then migrate both spec files (~1 day total).Phase 3 — keep as factory (class c) — 0 effort
5 call sites, no migration. Revisit if a component-DI pattern emerges in Svelte 5 (e.g. snippet injection as a prop).
Total scope
__mocks__filesnotification)Audit produced 2026-05-14. All paths relative to
frontend/src/. Based on main branch state at audit time.