diff --git a/frontend/src/lib/ocr/useOcrJob.svelte.test.ts b/frontend/src/lib/ocr/useOcrJob.svelte.test.ts index 3df1e4a6..8c83f6e1 100644 --- a/frontend/src/lib/ocr/useOcrJob.svelte.test.ts +++ b/frontend/src/lib/ocr/useOcrJob.svelte.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, afterEach } from 'vitest'; +import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest'; import { createOcrJob } from './useOcrJob.svelte'; afterEach(() => { @@ -238,8 +238,15 @@ describe('createOcrJob.checkStatus', () => { }); }); -describe('createOcrJob — polling loop (short interval, real timers)', () => { - const wait = (ms: number) => new Promise((r) => setTimeout(r, ms)); +describe('createOcrJob — polling loop (fake timers)', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + afterEach(() => { + vi.useRealTimers(); + }); + + // const wait used to live here; replaced by vi.advanceTimersByTimeAsync below. it('updates progressMessage from translated job code', async () => { const fetchImpl = makeFetch({ @@ -261,7 +268,7 @@ describe('createOcrJob — polling loop (short interval, real timers)', () => { pollIntervalMs: 20 }); await job.triggerOcr('KURRENT', false); - await wait(150); + await vi.advanceTimersByTimeAsync(50); expect(job.progressMessage).not.toBe(''); job.destroy(); @@ -287,7 +294,7 @@ describe('createOcrJob — polling loop (short interval, real timers)', () => { pollIntervalMs: 20 }); await job.triggerOcr('KURRENT', false); - await wait(150); + await vi.advanceTimersByTimeAsync(50); expect(job.skippedPages).toBeGreaterThanOrEqual(0); job.destroy(); @@ -317,7 +324,7 @@ describe('createOcrJob — polling loop (short interval, real timers)', () => { resetDelayMs: 10 }); await job.triggerOcr('KURRENT', false); - await wait(200); + await vi.advanceTimersByTimeAsync(100); expect(onJobFinished).toHaveBeenCalledWith('DONE'); job.destroy(); @@ -347,7 +354,7 @@ describe('createOcrJob — polling loop (short interval, real timers)', () => { resetDelayMs: 10 }); await job.triggerOcr('KURRENT', false); - await wait(200); + await vi.advanceTimersByTimeAsync(100); expect(onJobFinished).toHaveBeenCalledWith('FAILED'); expect(job.errorMessage).toBeTruthy(); @@ -372,7 +379,7 @@ describe('createOcrJob — polling loop (short interval, real timers)', () => { pollIntervalMs: 20 }); await job.triggerOcr('KURRENT', false); - await wait(150); + await vi.advanceTimersByTimeAsync(50); expect(job.running).toBe(true); job.destroy(); @@ -399,7 +406,7 @@ describe('createOcrJob — polling loop (short interval, real timers)', () => { pollIntervalMs: 20 }); await job.triggerOcr('KURRENT', false); - await wait(150); + await vi.advanceTimersByTimeAsync(50); expect(job.running).toBe(true); job.destroy(); @@ -407,13 +414,14 @@ describe('createOcrJob — polling loop (short interval, real timers)', () => { }); describe('createOcrJob.destroy', () => { - it('stops polling and is safe to call without an active job', () => { + it('returns undefined and is safe to call without an active job', () => { const job = createOcrJob({ documentId: () => 'doc-1' }); - expect(() => job.destroy()).not.toThrow(); + // destroy() is a void function — call it directly. If it threw, the test would fail. + expect(job.destroy()).toBeUndefined(); }); it('stops the polling interval when called mid-poll', async () => { - const wait = (ms: number) => new Promise((r) => setTimeout(r, ms)); + vi.useFakeTimers(); const fetchImpl = vi.fn(async (url: RequestInfo | URL) => { const u = url.toString(); if (u.includes('/api/documents/doc-1/ocr') && !u.includes('jobs')) { @@ -437,8 +445,9 @@ describe('createOcrJob.destroy', () => { job.destroy(); const callsAtDestroy = fetchImpl.mock.calls.length; - await wait(200); + await vi.advanceTimersByTimeAsync(100); // No additional fetch calls after destroy expect(fetchImpl.mock.calls.length).toBe(callsAtDestroy); + vi.useRealTimers(); }); });