diff --git a/client/tests/integration/api/client.test.ts b/client/tests/integration/api/client.test.ts index c9be28d6..529fc715 100644 --- a/client/tests/integration/api/client.test.ts +++ b/client/tests/integration/api/client.test.ts @@ -334,10 +334,14 @@ describe('API client interceptors', () => { // ── backupApi.download ─────────────────────────────────────────────────────── it('FE-API-013: backupApi.download creates a temp anchor and clicks it', async () => { - const createObjectURL = vi.fn(() => 'blob:mock-url'); - const revokeObjectURL = vi.fn(); - Object.defineProperty(URL, 'createObjectURL', { writable: true, value: createObjectURL }); - Object.defineProperty(URL, 'revokeObjectURL', { writable: true, value: revokeObjectURL }); + // backupApi.download uses native fetch (not axios), so mock it directly to + // avoid jsdom/MSW interception differences across environments. + const blob = new Blob(['zip-bytes'], { type: 'application/zip' }); + vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce( + new Response(blob, { status: 200 }) + ); + const createObjectURL = vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:mock-url'); + const revokeObjectURL = vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => {}); // Spy on createElement to intercept the anchor click const originalCreate = document.createElement.bind(document); @@ -350,12 +354,6 @@ describe('API client interceptors', () => { return el; }); - server.use( - http.get('/api/backup/download/backup.zip', () => { - return new HttpResponse(new Blob(['zip-bytes'], { type: 'application/zip' }), { status: 200 }); - }) - ); - await expect(backupApi.download('backup.zip')).resolves.toBeUndefined(); expect(createObjectURL).toHaveBeenCalled(); expect(revokeObjectURL).toHaveBeenCalled(); diff --git a/client/tests/setup.ts b/client/tests/setup.ts index 2f507fed..0d6373e3 100644 --- a/client/tests/setup.ts +++ b/client/tests/setup.ts @@ -59,13 +59,10 @@ globalThis.ResizeObserver = vi.fn().mockImplementation(() => ({ disconnect: vi.fn(), })) as unknown as typeof ResizeObserver; -// URL.createObjectURL / revokeObjectURL — used by file uploads -if (typeof URL.createObjectURL === 'undefined') { - Object.defineProperty(URL, 'createObjectURL', { writable: true, value: vi.fn(() => 'blob:mock') }); -} -if (typeof URL.revokeObjectURL === 'undefined') { - Object.defineProperty(URL, 'revokeObjectURL', { writable: true, value: vi.fn() }); -} +// URL.createObjectURL / revokeObjectURL — jsdom defines these but returns '' for createObjectURL; +// always override so tests get a predictable 'blob:mock' string. +Object.defineProperty(URL, 'createObjectURL', { writable: true, configurable: true, value: vi.fn(() => 'blob:mock') }); +Object.defineProperty(URL, 'revokeObjectURL', { writable: true, configurable: true, value: vi.fn() }); // Element.prototype.scrollIntoView — jsdom doesn't implement it Element.prototype.scrollIntoView = vi.fn(); diff --git a/client/tests/unit/api/authUrl.test.ts b/client/tests/unit/api/authUrl.test.ts index 3780678a..23e5f23a 100644 --- a/client/tests/unit/api/authUrl.test.ts +++ b/client/tests/unit/api/authUrl.test.ts @@ -193,6 +193,7 @@ describe('clearImageQueue', () => { it('removes queued items so they never execute after active slots drain', async () => { const resolvers: Array<() => void> = []; const createObjectURLSpy = vi.spyOn(URL, 'createObjectURL'); + createObjectURLSpy.mockClear(); // vi.spyOn returns the same vi.fn() set in setup.ts; reset accumulated calls from prior tests vi.spyOn(globalThis, 'fetch').mockImplementation(async () => { await new Promise(r => resolvers.push(r));