diff --git a/testing/_time.ts b/testing/_time.ts index 59a5df46e..fc9512265 100644 --- a/testing/_time.ts +++ b/testing/_time.ts @@ -7,4 +7,5 @@ export const _internals = { clearTimeout, setInterval, clearInterval, + AbortSignalTimeout: AbortSignal.timeout, }; diff --git a/testing/time.ts b/testing/time.ts index 50f362909..b7c716e43 100644 --- a/testing/time.ts +++ b/testing/time.ts @@ -197,12 +197,21 @@ function setTimer( return id; } +function fakeAbortSignalTimeout(delay: number): AbortSignal { + const aborter = new AbortController(); + fakeSetTimeout(() => { + aborter.abort(new DOMException("Signal timed out.", "TimeoutError")); + }, delay); + return aborter.signal; +} + function overrideGlobals() { globalThis.Date = FakeDate; globalThis.setTimeout = fakeSetTimeout; globalThis.clearTimeout = fakeClearTimeout; globalThis.setInterval = fakeSetInterval; globalThis.clearInterval = fakeClearInterval; + AbortSignal.timeout = fakeAbortSignalTimeout; } function restoreGlobals() { @@ -211,6 +220,7 @@ function restoreGlobals() { globalThis.clearTimeout = _internals.clearTimeout; globalThis.setInterval = _internals.setInterval; globalThis.clearInterval = _internals.clearInterval; + AbortSignal.timeout = _internals.AbortSignalTimeout; } function* timerIdGen() { diff --git a/testing/time_test.ts b/testing/time_test.ts index 21c09417e..b8fdac6c0 100644 --- a/testing/time_test.ts +++ b/testing/time_test.ts @@ -13,8 +13,9 @@ import { import { FakeTime, TimeError } from "./time.ts"; import { _internals } from "./_time.ts"; import { assertSpyCall, spy, type SpyCall } from "./mock.ts"; +import { deadline, delay } from "@std/async"; -function fromNow(): () => number { +function fromNow(): (..._args: unknown[]) => number { const start: number = Date.now(); return () => Date.now() - start; } @@ -722,3 +723,66 @@ Deno.test("time.start returns the started time of the fake time", () => { time.now = 2000; assertEquals(time.start, 1000); }); + +Deno.test("FakeTime doesn't affect AbortSignal.timeout unchanged if uninitialized", () => { + assertStrictEquals(AbortSignal.timeout, _internals.AbortSignalTimeout); +}); + +Deno.test("FakeTime fakes AbortSignal.timeout", () => { + { + using _time = new FakeTime(9001); + assertNotEquals(AbortSignal.timeout, _internals.AbortSignalTimeout); + } + assertStrictEquals(AbortSignal.timeout, _internals.AbortSignalTimeout); +}); + +Deno.test("FakeTime controls AbortSignal.timeout", () => { + using time: FakeTime = new FakeTime(); + const cb = spy(fromNow()); + const expected: SpyCall[] = []; + + const signal = AbortSignal.timeout(1000); + signal.onabort = () => cb(); + time.tick(250); + assertEquals(cb.calls, expected); + time.tick(250); + assertEquals(cb.calls, expected); + time.tick(500); + expected.push({ args: [], returned: 1000 }); + assertEquals(cb.calls, expected); + time.tick(2500); + assertEquals(cb.calls, expected); + + assertEquals(signal.aborted, true); + assertInstanceOf(signal.reason, DOMException); + assertEquals(signal.reason.name, "TimeoutError"); + assertEquals(signal.reason.message, "Signal timed out."); + + const signalA = AbortSignal.timeout(1000); + signalA.addEventListener("abort", () => cb("a")); + const signalB = AbortSignal.timeout(2000); + signalB.addEventListener("abort", () => cb("b")); + const signalC = AbortSignal.timeout(1500); + signalC.addEventListener("abort", () => cb("c")); + assertEquals(cb.calls, expected); + time.tick(2500); + expected.push({ args: ["a"], returned: 4500 }); + expected.push({ args: ["c"], returned: 5000 }); + expected.push({ args: ["b"], returned: 5500 }); + assertEquals(cb.calls, expected); +}); + +// https://github.com/denoland/std/issues/5499 +Deno.test("FakeTime regression test for issue #5499", async () => { + using t = new FakeTime(); + const p = deadline(delay(1_000), 10); + let state: "pending" | "rejected" | "fulfilled" = "pending"; + p.then(() => { + state = "fulfilled"; + }).catch(() => { + state = "rejected"; + }); + await t.tickAsync(10); + await t.runMicrotasks(); + assertEquals(state, "rejected"); +});