import { describe, expect, it } from "vitest"; import { nextOccurrences } from "./occurrences_extra.js"; const ISO = (o: { fireAtUtc: number }) => new Date(o.fireAtUtc).toISOString().replace(".000Z", "Z"); describe("count boundary semantics", () => { it("never returns more than count", () => { for (const count of [1, 2, 5, 17]) { const out = nextOccurrences({ cron: "*/15 * * * *", timezone: "America/New_York" }, "2024-03-09T00:00:00Z", count); expect(out.length).toBe(count); } }); it("stops at the collapse edge with the latest collapsed label", () => { // Exclusive from 05:00Z (== 00:00 local): the first slot is 00:15. The // eighth occurrence is the collapse edge — nominal slots 02:00..03:00 all // materialize onto 07:00Z — and it carries the LATEST label (03:00), with // nothing beyond the edge emitted. const out = nextOccurrences({ cron: "*/15 * * * *", timezone: "America/New_York" }, "2024-03-10T05:00:00Z", 8); expect(out.length).toBe(8); expect(out.map(ISO)[7]).toBe("2024-03-10T07:00:00Z"); expect(out[7]!.nominalLocal).toBe("2024-03-10T03:00"); // The same result whether the boundary is reached with 8 or 80 requested, // because generation stops at the first slot after the boundary instant. const wide = nextOccurrences({ cron: "*/15 * * * *", timezone: "America/New_York" }, "2024-03-10T05:00:00Z", 80); expect(wide[7]!.nominalLocal).toBe("2024-03-10T03:00"); }); });