Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 52 additions & 11 deletions tests/lib/abort-idle-deadline.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect, test } from "bun:test";
import { expect, spyOn, test } from "bun:test";
import { idleDeadline } from "../../src/lib/abort";

const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
Expand All @@ -17,18 +17,59 @@ test("idleDeadline fires once after the idle window with no reset", async () =>
expect(fired).toBe(1);
});

test("idleDeadline reset() re-arms and postpones firing", async () => {
test("idleDeadline reset() re-arms and postpones firing", () => {
// Keep this boundary check synchronous: real sleeps can resume after the idle window.
// The other cases below still exercise Bun's real timers.
type TimerHandle = ReturnType<typeof setTimeout>;
let now = 0;
let nextHandle = 0;
const timers = new Map<TimerHandle, { at: number; fire: () => void }>();
const timeoutSpy = spyOn(globalThis, "setTimeout").mockImplementation(((
callback: (...args: unknown[]) => void, delay = 0, ...args: unknown[]
) => {
const handle = ++nextHandle as unknown as TimerHandle;
timers.set(handle, { at: now + delay, fire: () => callback(...args) });
return handle;
}) as typeof setTimeout);
const clearSpy = spyOn(globalThis, "clearTimeout").mockImplementation(handle => {
timers.delete(handle as TimerHandle);
});
const advanceBy = (ms: number) => {
const target = now + ms;
for (;;) {
const due = [...timers].filter(([, timer]) => timer.at <= target)
.sort((a, b) => a[1].at - b[1].at)[0];
if (!due) break;
timers.delete(due[0]);
now = due[1].at;
due[1].fire();
}
now = target;
};
let fired = 0;
const idle = idleDeadline(120, () => { fired += 1; });
idle.reset();
for (let i = 0; i < 4; i++) {
await sleep(40);
idle.reset(); // keep-alive: total elapsed (160ms) exceeds 120ms but silence never does
let idle: ReturnType<typeof idleDeadline> | undefined;
try {
idle = idleDeadline(120, () => { fired += 1; });
idle.reset();
for (let i = 0; i < 4; i++) {
advanceBy(40);
idle.reset(); // total elapsed exceeds 120 ms, but each silent interval does not
}
expect(fired).toBe(0);
advanceBy(119);
expect(fired).toBe(0);
advanceBy(1);
expect(fired).toBe(1);
advanceBy(240);
expect(fired).toBe(1);
} finally {
try {
idle?.cancel();
} finally {
clearSpy.mockRestore();
timeoutSpy.mockRestore();
}
}
expect(fired).toBe(0);
await sleep(220);
expect(fired).toBe(1);
idle.cancel();
});

test("idleDeadline pause() disarms without retiring; reset() re-arms after pause", async () => {
Expand Down
Loading