UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

253 lines (188 loc) 7.18 kB
import { Alepha } from "alepha"; import { describe, expect, it } from "vitest"; import { DateTimeProvider } from "../index.ts"; describe("DateTimeProvider", () => { it("should pause time and reset", async () => { const app = Alepha.create(); const dt = app.inject(DateTimeProvider); const clock = () => dt.nowISOString(); const n1 = clock(); await new Promise((resolve) => setTimeout(resolve, 10)); dt.pause(); const n2 = clock(); expect(n1).not.toBe(n2); expect(n2).toBe(clock()); expect(n2).toBe(clock()); expect(n2).toBe(clock()); dt.reset(); await new Promise((resolve) => setTimeout(resolve, 10)); expect(n2).not.toBe(clock()); }); it("should time travel and trigger scheduled callbacks", async () => { const stack: string[] = []; const app = Alepha.create(); const dt = app.inject(DateTimeProvider); stack.push("A"); dt.wait([10, "minutes"]).then(() => stack.push("B")); dt.wait([20, "minutes"]).then(() => stack.push("C")); expect(stack).toEqual(["A"]); await dt.travel([5, "minutes"]); expect(stack).toEqual(["A"]); await dt.travel([30, "minutes"]); expect(stack).toEqual(["A", "B", "C"]); }); it("should handle timeouts with clearTimeout", async () => { const app = Alepha.create(); const dt = app.inject(DateTimeProvider); const stack: string[] = []; dt.createTimeout(() => stack.push("A"), [10, "minutes"]); const n2 = dt.createTimeout(() => stack.push("B"), [10, "minutes"]); expect(stack).toEqual([]); await dt.travel([5, "minutes"]); expect(stack).toEqual([]); dt.clearTimeout(n2); await dt.travel([5, "minutes"]); expect(stack).toEqual(["A"]); }); it("should wait with abort signal support", async () => { const app = Alepha.create(); const dt = app.inject(DateTimeProvider); const stack: string[] = []; const abortController = new AbortController(); dt.wait([10, "minutes"], { signal: abortController.signal }).then(() => stack.push("A"), ); expect(stack).toEqual([]); abortController.abort(); await new Promise((resolve) => setTimeout(resolve, 10)); expect(stack).toEqual(["A"]); }); }); describe("DateTimeProvider.nowMillis", () => { it("should return frozen millis when paused", () => { const app = Alepha.create(); const dt = app.inject(DateTimeProvider); dt.pause(); const a = dt.nowMillis(); const b = dt.nowMillis(); expect(a).toBe(b); }); it("should advance millis after travel", async () => { const app = Alepha.create(); const dt = app.inject(DateTimeProvider); dt.pause(); const before = dt.nowMillis(); await dt.travel([5, "minutes"]); const after = dt.nowMillis(); expect(after - before).toBe(5 * 60 * 1000); }); }); describe("DateTimeProvider.deadline", () => { it("should return result when handler completes in time", async () => { const app = Alepha.create(); const dt = app.inject(DateTimeProvider); const result = await dt.deadline(async () => "done", [1, "seconds"]); expect(result).toBe("done"); }); it("should abort signal when handler exceeds deadline", async () => { const app = Alepha.create(); const dt = app.inject(DateTimeProvider); let aborted = false; const promise = dt.deadline( async (signal) => { signal.addEventListener("abort", () => { aborted = true; }); await dt.wait([10, "minutes"]); return "done"; }, [5, "minutes"], ); // Travel past deadline (5min) but handler is still waiting (10min) await dt.travel([5, "minutes"]); expect(aborted).toBe(true); // Travel remaining time so handler resolves await dt.travel([5, "minutes"]); await expect(promise).resolves.toBe("done"); }); }); describe("DateTimeProvider.isDurationLike", () => { it("should return true for valid durations", () => { const app = Alepha.create(); const dt = app.inject(DateTimeProvider); expect(dt.isDurationLike(1000)).toBe(true); expect(dt.isDurationLike([5, "minutes"])).toBe(true); }); it("should return false for invalid values", () => { const app = Alepha.create(); const dt = app.inject(DateTimeProvider); expect(dt.isDurationLike("not-a-duration")).toBe(false); expect(dt.isDurationLike(null)).toBe(false); expect(dt.isDurationLike(undefined)).toBe(false); expect(dt.isDurationLike({})).toBe(false); }); }); describe("DateTimeProvider.duration", () => { it("should pass through an existing Duration object", () => { const app = Alepha.create(); const dt = app.inject(DateTimeProvider); const dur = dt.duration([5, "seconds"]); const same = dt.duration(dur); expect(same).toBe(dur); expect(same.asMilliseconds()).toBe(5000); }); }); describe("DateTimeProvider.travel — reschedule surviving timeout", () => { it("should reschedule a timeout that has not yet expired", async () => { const app = Alepha.create(); const dt = app.inject(DateTimeProvider); const stack: string[] = []; dt.createTimeout(() => stack.push("A"), [10, "minutes"]); // travel less than 10 minutes — timeout survives await dt.travel([3, "minutes"]); expect(stack).toEqual([]); // travel past the remaining time — timeout fires await dt.travel([8, "minutes"]); expect(stack).toEqual(["A"]); }); }); describe("DateTimeProvider.createTimeout — replay with ref", () => { it("should fire immediately when replaying a past timeout", async () => { const app = Alepha.create(); const dt = app.inject(DateTimeProvider); const stack: string[] = []; // Pause time at a known point, then travel forward dt.pause(); await dt.travel([30, "minutes"]); // Create a timeout with a `now` in the past — should fire immediately const pastNow = dt.now().subtract(20, "minutes").valueOf(); dt.createTimeout(() => stack.push("replayed"), [10, "minutes"], pastNow); expect(stack).toEqual(["replayed"]); }); it("should not fire when replay timeout is still in the future", async () => { const app = Alepha.create(); const dt = app.inject(DateTimeProvider); const stack: string[] = []; dt.pause(); await dt.travel([5, "minutes"]); // Create a timeout with a `now` that puts expiry in the future const recentNow = dt.now().subtract(1, "minutes").valueOf(); dt.createTimeout(() => stack.push("future"), [10, "minutes"], recentNow); expect(stack).toEqual([]); }); }); describe("DateTimeProvider.clearInterval", () => { it("should stop a running interval", async () => { const app = Alepha.create(); const dt = app.inject(DateTimeProvider); let count = 0; const interval = dt.createInterval(() => count++, [10, "seconds"], true); await new Promise((r) => setTimeout(r, 10)); dt.clearInterval(interval); const countAfterClear = count; await new Promise((r) => setTimeout(r, 20)); expect(count).toBe(countAfterClear); expect(interval.duration).toBe(0); expect(interval.timer).toBeNull(); }); });