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';
|
import { createOcrJob } from './useOcrJob.svelte';
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -238,8 +238,15 @@ describe('createOcrJob.checkStatus', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('createOcrJob — polling loop (short interval, real timers)', () => {
|
describe('createOcrJob — polling loop (fake timers)', () => {
|
||||||
const wait = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
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 () => {
|
it('updates progressMessage from translated job code', async () => {
|
||||||
const fetchImpl = makeFetch({
|
const fetchImpl = makeFetch({
|
||||||
@@ -261,7 +268,7 @@ describe('createOcrJob — polling loop (short interval, real timers)', () => {
|
|||||||
pollIntervalMs: 20
|
pollIntervalMs: 20
|
||||||
});
|
});
|
||||||
await job.triggerOcr('KURRENT', false);
|
await job.triggerOcr('KURRENT', false);
|
||||||
await wait(150);
|
await vi.advanceTimersByTimeAsync(50);
|
||||||
|
|
||||||
expect(job.progressMessage).not.toBe('');
|
expect(job.progressMessage).not.toBe('');
|
||||||
job.destroy();
|
job.destroy();
|
||||||
@@ -287,7 +294,7 @@ describe('createOcrJob — polling loop (short interval, real timers)', () => {
|
|||||||
pollIntervalMs: 20
|
pollIntervalMs: 20
|
||||||
});
|
});
|
||||||
await job.triggerOcr('KURRENT', false);
|
await job.triggerOcr('KURRENT', false);
|
||||||
await wait(150);
|
await vi.advanceTimersByTimeAsync(50);
|
||||||
|
|
||||||
expect(job.skippedPages).toBeGreaterThanOrEqual(0);
|
expect(job.skippedPages).toBeGreaterThanOrEqual(0);
|
||||||
job.destroy();
|
job.destroy();
|
||||||
@@ -317,7 +324,7 @@ describe('createOcrJob — polling loop (short interval, real timers)', () => {
|
|||||||
resetDelayMs: 10
|
resetDelayMs: 10
|
||||||
});
|
});
|
||||||
await job.triggerOcr('KURRENT', false);
|
await job.triggerOcr('KURRENT', false);
|
||||||
await wait(200);
|
await vi.advanceTimersByTimeAsync(100);
|
||||||
|
|
||||||
expect(onJobFinished).toHaveBeenCalledWith('DONE');
|
expect(onJobFinished).toHaveBeenCalledWith('DONE');
|
||||||
job.destroy();
|
job.destroy();
|
||||||
@@ -347,7 +354,7 @@ describe('createOcrJob — polling loop (short interval, real timers)', () => {
|
|||||||
resetDelayMs: 10
|
resetDelayMs: 10
|
||||||
});
|
});
|
||||||
await job.triggerOcr('KURRENT', false);
|
await job.triggerOcr('KURRENT', false);
|
||||||
await wait(200);
|
await vi.advanceTimersByTimeAsync(100);
|
||||||
|
|
||||||
expect(onJobFinished).toHaveBeenCalledWith('FAILED');
|
expect(onJobFinished).toHaveBeenCalledWith('FAILED');
|
||||||
expect(job.errorMessage).toBeTruthy();
|
expect(job.errorMessage).toBeTruthy();
|
||||||
@@ -372,7 +379,7 @@ describe('createOcrJob — polling loop (short interval, real timers)', () => {
|
|||||||
pollIntervalMs: 20
|
pollIntervalMs: 20
|
||||||
});
|
});
|
||||||
await job.triggerOcr('KURRENT', false);
|
await job.triggerOcr('KURRENT', false);
|
||||||
await wait(150);
|
await vi.advanceTimersByTimeAsync(50);
|
||||||
|
|
||||||
expect(job.running).toBe(true);
|
expect(job.running).toBe(true);
|
||||||
job.destroy();
|
job.destroy();
|
||||||
@@ -399,7 +406,7 @@ describe('createOcrJob — polling loop (short interval, real timers)', () => {
|
|||||||
pollIntervalMs: 20
|
pollIntervalMs: 20
|
||||||
});
|
});
|
||||||
await job.triggerOcr('KURRENT', false);
|
await job.triggerOcr('KURRENT', false);
|
||||||
await wait(150);
|
await vi.advanceTimersByTimeAsync(50);
|
||||||
|
|
||||||
expect(job.running).toBe(true);
|
expect(job.running).toBe(true);
|
||||||
job.destroy();
|
job.destroy();
|
||||||
@@ -407,13 +414,14 @@ describe('createOcrJob — polling loop (short interval, real timers)', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('createOcrJob.destroy', () => {
|
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' });
|
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 () => {
|
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 fetchImpl = vi.fn(async (url: RequestInfo | URL) => {
|
||||||
const u = url.toString();
|
const u = url.toString();
|
||||||
if (u.includes('/api/documents/doc-1/ocr') && !u.includes('jobs')) {
|
if (u.includes('/api/documents/doc-1/ocr') && !u.includes('jobs')) {
|
||||||
@@ -437,8 +445,9 @@ describe('createOcrJob.destroy', () => {
|
|||||||
job.destroy();
|
job.destroy();
|
||||||
|
|
||||||
const callsAtDestroy = fetchImpl.mock.calls.length;
|
const callsAtDestroy = fetchImpl.mock.calls.length;
|
||||||
await wait(200);
|
await vi.advanceTimersByTimeAsync(100);
|
||||||
// No additional fetch calls after destroy
|
// No additional fetch calls after destroy
|
||||||
expect(fetchImpl.mock.calls.length).toBe(callsAtDestroy);
|
expect(fetchImpl.mock.calls.length).toBe(callsAtDestroy);
|
||||||
|
vi.useRealTimers();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user