test(ocr): convert useOcrJob polling tests to fake timers
Replaces 2 setTimeout-based wait() helpers with vi.useFakeTimers() + vi.advanceTimersByTimeAsync() so the polling-loop tests no longer race against the real clock under CI load — they instead deterministically advance the setInterval by the exact poll interval and let microtasks flush. Also converts the destroy() .not.toThrow smoke into a direct expect(job.destroy()).toBeUndefined() check. Per Sara: polling-loop tests are the legitimate case for fake timers (time progression matters) — exactly the pattern she requested. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user