From cc76d4de9120608c0aeeb0c3c1740f2b9edd27d2 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 2 Jun 2026 19:48:00 +0200 Subject: [PATCH] test(mocks): add shared $app/navigation mock with simulateNavigate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exports the standard nav functions as vi.fn() and a beforeNavigate that captures the registered callback. The exported simulateNavigate(href) helper fires that callback and returns the cancel spy — the whole capture-and-fire pattern lives in the shared module, not the raw callback. An embedded beforeEach clears the captured callback and the mock call histories before every test. Part of #560. Co-Authored-By: Claude Opus 4.8 --- frontend/src/__mocks__/$app/navigation.ts | 62 +++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 frontend/src/__mocks__/$app/navigation.ts diff --git a/frontend/src/__mocks__/$app/navigation.ts b/frontend/src/__mocks__/$app/navigation.ts new file mode 100644 index 00000000..2421289e --- /dev/null +++ b/frontend/src/__mocks__/$app/navigation.ts @@ -0,0 +1,62 @@ +// Shared browser-test mock body for the SvelteKit `$app/navigation` virtual module. +// +// Imported into a sync vi.mock factory via the $mocks alias: +// import * as navMock from '$mocks/$app/navigation'; +// vi.mock('$app/navigation', () => ({ ...navMock })); +// +// All navigation functions are vi.fn() stubs. `beforeNavigate` additionally +// captures the registered callback so a test can drive it through the exported +// `simulateNavigate(href)` helper — the whole capture-and-fire pattern lives +// here, not the raw callback. The embedded `beforeEach` clears the captured +// callback and the mock call histories before every test, so isolation is +// structural. See ADR-012. + +import { beforeEach, vi } from 'vitest'; + +type BeforeNavigateCallback = (nav: { + cancel: () => void; + to: { url: { href: string } } | null; +}) => void; + +let _registeredBeforeNavigate: BeforeNavigateCallback | null = null; + +export const goto = vi.fn(); +export const invalidate = vi.fn(); +export const invalidateAll = vi.fn(); +export const beforeNavigate = vi.fn((fn: BeforeNavigateCallback) => { + _registeredBeforeNavigate = fn; +}); +export const afterNavigate = vi.fn(); +export const preloadCode = vi.fn(); +export const preloadData = vi.fn(); +export const pushState = vi.fn(); +export const replaceState = vi.fn(); +export const disableScrollHandling = vi.fn(); +export const onNavigate = vi.fn(); + +const _navMocks = [ + goto, + invalidate, + invalidateAll, + beforeNavigate, + afterNavigate, + preloadCode, + preloadData, + pushState, + replaceState, + disableScrollHandling, + onNavigate +]; + +// Fire the captured beforeNavigate callback as if navigating to `href`. +// Returns the cancel spy so the test can assert whether navigation was blocked. +export function simulateNavigate(href: string | null = '/somewhere'): ReturnType { + const cancel = vi.fn(); + _registeredBeforeNavigate?.({ cancel, to: href ? { url: { href } } : null }); + return cancel; +} + +beforeEach(() => { + _registeredBeforeNavigate = null; + _navMocks.forEach((mock) => mock.mockClear()); +});