95 lines
3.6 KiB
TypeScript
95 lines
3.6 KiB
TypeScript
import { describe, expect, it } from 'bun:test';
|
|
import { createRefreshScheduler } from '../src/services/refreshScheduler';
|
|
|
|
const deferred = () => {
|
|
let resolve!: () => void;
|
|
const promise = new Promise<void>((done) => { resolve = done; });
|
|
return { promise, resolve };
|
|
};
|
|
|
|
describe('refresh scheduler', () => {
|
|
it('runs one request at a time and retains only the latest trailing request', async () => {
|
|
const scheduler = createRefreshScheduler(0);
|
|
const firstGate = deferred();
|
|
const calls: number[] = [];
|
|
const first = scheduler.schedule(async () => {
|
|
calls.push(1);
|
|
await firstGate.promise;
|
|
});
|
|
const replaced = scheduler.schedule(async () => { calls.push(2); });
|
|
let latest = replaced;
|
|
for (let index = 3; index <= 1000; index += 1) {
|
|
latest = scheduler.schedule(async () => { calls.push(index); });
|
|
expect(latest).toBe(replaced);
|
|
}
|
|
expect(calls).toEqual([1]);
|
|
firstGate.resolve();
|
|
await Promise.all([first, replaced, latest]);
|
|
expect(calls).toEqual([1, 1000]);
|
|
});
|
|
|
|
it('waits between background refreshes but lets an explicit refresh bypass the delay', async () => {
|
|
const scheduler = createRefreshScheduler(30);
|
|
await scheduler.schedule(async () => {});
|
|
const calls: string[] = [];
|
|
const queued = scheduler.schedule(async () => { calls.push('background'); });
|
|
expect(calls).toEqual([]);
|
|
await scheduler.schedule(async () => { calls.push('manual'); }, true);
|
|
await queued;
|
|
expect(calls).toEqual(['manual']);
|
|
const started = performance.now();
|
|
await scheduler.schedule(async () => { calls.push('delayed'); });
|
|
expect(performance.now() - started).toBeGreaterThanOrEqual(25);
|
|
});
|
|
|
|
it('starts foreground work immediately and holds trailing background refreshes until it finishes', async () => {
|
|
const scheduler = createRefreshScheduler(0);
|
|
const backgroundGate = deferred();
|
|
const foregroundGate = deferred();
|
|
const calls: string[] = [];
|
|
const background = scheduler.schedule(async () => {
|
|
calls.push('background');
|
|
await backgroundGate.promise;
|
|
});
|
|
const obsolete = scheduler.schedule(async () => { calls.push('obsolete'); });
|
|
const foreground = scheduler.runForeground(async () => {
|
|
calls.push('foreground');
|
|
await foregroundGate.promise;
|
|
});
|
|
const trailing = scheduler.schedule(async () => { calls.push('trailing'); }, true);
|
|
|
|
expect(calls).toEqual(['background', 'foreground']);
|
|
await obsolete;
|
|
backgroundGate.resolve();
|
|
await background;
|
|
expect(calls).toEqual(['background', 'foreground']);
|
|
foregroundGate.resolve();
|
|
await Promise.all([foreground, trailing]);
|
|
expect(calls).toEqual(['background', 'foreground', 'trailing']);
|
|
});
|
|
|
|
it('cancels queued work during navigation and accepts a fresh request', async () => {
|
|
const scheduler = createRefreshScheduler(0);
|
|
const gate = deferred();
|
|
const active = scheduler.schedule(() => gate.promise);
|
|
let obsoleteRan = false;
|
|
const obsolete = scheduler.schedule(async () => { obsoleteRan = true; });
|
|
scheduler.cancelPending();
|
|
await obsolete;
|
|
let freshRan = false;
|
|
const fresh = scheduler.schedule(async () => { freshRan = true; }, true);
|
|
gate.resolve();
|
|
await Promise.all([active, fresh]);
|
|
expect(obsoleteRan).toBe(false);
|
|
expect(freshRan).toBe(true);
|
|
});
|
|
|
|
it('continues after a failed refresh', async () => {
|
|
const scheduler = createRefreshScheduler(0);
|
|
await expect(scheduler.schedule(async () => { throw new Error('query failed'); })).rejects.toThrow('query failed');
|
|
let ran = false;
|
|
await scheduler.schedule(async () => { ran = true; });
|
|
expect(ran).toBe(true);
|
|
});
|
|
});
|