From b0633b1d36155dc2f04339e644a801b4fc259d19 Mon Sep 17 00:00:00 2001 From: jubnl Date: Tue, 7 Apr 2026 23:10:20 +0200 Subject: [PATCH] fix(tests): fix remaining CI failures for URL.createObjectURL and Response mocking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two root causes: 1. authUrl.test.ts (007, 011, 012): Object.defineProperty in setup.ts fails silently on CI when jsdom's URL.createObjectURL is non-configurable. vi.restoreAllMocks() in beforeEach then restores the property to jsdom's native implementation (returns ''). Fix: assign URL.createObjectURL = vi.fn(() => 'blob:mock') directly in authUrl.test.ts's beforeEach, after restoreAllMocks(), so every test in the file gets a fresh, reliable mock. Remove the now- unnecessary mockClear() from test 012. 2. client.test.ts (013): MSW patches the global Response constructor and calls blob.stream() on the body — a method not implemented by jsdom's Blob. Fix: replace new Response(blob) with a plain-object duck-type ({ ok: true, blob: () => Promise.resolve(blob) }) to bypass the patched constructor entirely. --- client/tests/integration/api/client.test.ts | 12 +++++++----- client/tests/unit/api/authUrl.test.ts | 7 ++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/client/tests/integration/api/client.test.ts b/client/tests/integration/api/client.test.ts index 529fc715..0b71af53 100644 --- a/client/tests/integration/api/client.test.ts +++ b/client/tests/integration/api/client.test.ts @@ -334,12 +334,14 @@ describe('API client interceptors', () => { // ── backupApi.download ─────────────────────────────────────────────────────── it('FE-API-013: backupApi.download creates a temp anchor and clicks it', async () => { - // backupApi.download uses native fetch (not axios), so mock it directly to - // avoid jsdom/MSW interception differences across environments. + // backupApi.download uses native fetch (not axios). Mock fetch directly and + // use a plain-object Response duck-type to avoid MSW patching the Response + // constructor (which calls blob.stream() — not implemented in jsdom's Blob). const blob = new Blob(['zip-bytes'], { type: 'application/zip' }); - vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce( - new Response(blob, { status: 200 }) - ); + vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({ + ok: true, + blob: () => Promise.resolve(blob), + } as unknown as Response); const createObjectURL = vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:mock-url'); const revokeObjectURL = vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => {}); diff --git a/client/tests/unit/api/authUrl.test.ts b/client/tests/unit/api/authUrl.test.ts index 23e5f23a..16e3aead 100644 --- a/client/tests/unit/api/authUrl.test.ts +++ b/client/tests/unit/api/authUrl.test.ts @@ -9,6 +9,12 @@ const flushPromises = () => new Promise(r => setTimeout(r, 10)); beforeEach(() => { clearImageQueue(); vi.restoreAllMocks(); // restore any vi.spyOn() wrappers from the previous test + + // jsdom's URL.createObjectURL returns '' and may be non-configurable, so + // Object.defineProperty in setup.ts can fail silently on CI. Assign directly + // here (after restoreAllMocks) so every test in this file gets a fresh mock. + URL.createObjectURL = vi.fn(() => 'blob:mock') as typeof URL.createObjectURL; + URL.revokeObjectURL = vi.fn() as typeof URL.revokeObjectURL; }); // ── getAuthUrl ───────────────────────────────────────────────────────────────── @@ -193,7 +199,6 @@ 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));