test(web): stub SvelteKit app modules suite-wide so no test loads the client runtime (#3943)
release / govulncheck (push) Successful in 32s
release / web (push) Successful in 1m25s
release / go (push) Successful in 1m41s
release / integration (push) Successful in 4m55s
release / android (push) Successful in 6m15s
release / Build signed APK (releases and dev) (push) Successful in 6m42s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 2m4s
release / Verify release artifacts (tag releases only) (push) Skipped

The home page reaches the real $app/navigation through AlbumCard and
AlbumMenu. That loads SvelteKit's client runtime, whose $app/paths reads
__SVELTEKIT_PAYLOAD__ at module load. The global is only there when the
kit plugin's define reaches the module, and under vitest that is not
reliable: page.test.ts failed to load on CI run 6576 and passed on its
re-run. #374 was the same class of failure.

vitest.setup.ts now mocks $app/navigation, $app/state and $app/paths for
every test. Per-file mocks still win. A small guard test fails if the
suite-wide mocks are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 21:26:21 -04:00
co-authored by Claude Opus 5.5
parent e5dac9ddf0
commit c17f4273c6
2 changed files with 61 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
import { describe, expect, test, vi } from 'vitest';
import { goto } from '$app/navigation';
import { page } from '$app/state';
// Guards the suite-wide $app mocks in vitest.setup.ts (#3943). If they are
// removed, tests that never mock $app themselves go back to loading
// SvelteKit's client runtime, which fails at module load on some CI runs.
describe('suite-wide SvelteKit app-module mocks', () => {
test('$app/navigation is the mock, not the client runtime', () => {
expect(vi.isMockFunction(goto)).toBe(true);
});
test('$app/state gives a page at the root URL', () => {
expect(page.url.pathname).toBe('/');
});
});
+45
View File
@@ -15,6 +15,51 @@ vi.mock('@tanstack/svelte-query', async (orig) => {
return { ...actual, useQueryClient: () => ({ invalidateQueries: vi.fn() }) };
});
// Default SvelteKit app-module mocks for the whole suite (#3943). Without
// them, any page or component that reaches $app/navigation or $app/state —
// the home page does, through AlbumCard → AlbumMenu — loads SvelteKit's
// client runtime, whose $app/paths reads __SVELTEKIT_PAYLOAD__ at module
// load. That global only exists when the kit plugin's define reaches the
// module, which it does not reliably under vitest: page.test.ts failed to
// load on one CI run and passed on the re-run of the same commit (#374
// was the same class). Stubbing the modules keeps every test off the
// client bootstrap. Tests that assert on goto or need a particular URL
// still mock per file (pageUrlModule in test-utils/mocks/appState.ts),
// and per-file mocks win over these.
vi.mock('$app/navigation', () => ({
goto: vi.fn(() => Promise.resolve()),
invalidate: vi.fn(() => Promise.resolve()),
invalidateAll: vi.fn(() => Promise.resolve()),
preloadData: vi.fn(() => Promise.resolve()),
preloadCode: vi.fn(() => Promise.resolve()),
beforeNavigate: vi.fn(),
afterNavigate: vi.fn(),
onNavigate: vi.fn(),
pushState: vi.fn(),
replaceState: vi.fn(),
disableScrollHandling: vi.fn()
}));
vi.mock('$app/state', () => ({
page: {
url: new URL('http://localhost/'),
params: {},
route: { id: null },
status: 200,
error: null,
data: {},
form: null,
state: {}
},
navigating: null,
updated: { current: false, check: () => Promise.resolve(false) }
}));
vi.mock('$app/paths', () => ({
base: '',
assets: '',
resolve: (path: string) => path,
asset: (path: string) => path
}));
// Node 25 ships a partial localStorage global that lacks getItem/setItem/clear
// unless launched with --localstorage-file. jsdom sees Node's global and skips
// installing its own. Replace with an in-memory Storage-compatible polyfill so