import { describe, expect, it } from "@suaveplan/testing/runner"; import fc from "fast-check"; import { parseCron } from "../src/index.js"; const EXPRESSIONS = [ "* * * * *", "0 * * * *", "*/5 * * * *", "*/15 * * * *", "0 9 * * *", "0 9 * * MON-FRI", "30 14 * * 0", "0 0 1 * *", "0 0 1 1 *", "0,15,30,45 9-17 * * MON-FRI", "0 10-12/2 * * *", ]; const TIMEZONES = [ "UTC", "America/New_York", "Europe/London", "Australia/Sydney", ]; const timestampArb = fc.integer({ min: Date.UTC(2026, 0, 1), max: Date.UTC(2028, 0, 1), }); describe("property — nextRun > after", () => { it("returns a strictly later instant for every supported expression", { timeout: 30000, }, () => { fc.assert( fc.property( fc.constantFrom(...EXPRESSIONS), timestampArb, fc.constantFrom(...TIMEZONES), (e, t, tz) => { const next = parseCron(e).nextRun(t, tz); expect(next).not.toBeNull(); expect(next as number).toBeGreaterThan(t); } ), { numRuns: 50 } ); }); }); describe("property — nextRuns strictly monotonic", () => { it("every adjacent pair is strictly ascending", { timeout: 30000 }, () => { fc.assert( fc.property( fc.constantFrom(...EXPRESSIONS), timestampArb, fc.constantFrom(...TIMEZONES), (e, t, tz) => { const runs = parseCron(e).nextRuns(10, t, tz); expect(runs.length).toBeGreaterThan(0); for (let i = 1; i < runs.length; i += 1) { expect(runs[i]).toBeGreaterThan(runs[i - 1] ?? 0); } } ), { numRuns: 30 } ); }); }); describe("property — matches(nextRun) is true", () => { it("the returned instant always matches the expression", { timeout: 30000, }, () => { fc.assert( fc.property( fc.constantFrom(...EXPRESSIONS), timestampArb, fc.constantFrom(...TIMEZONES), (e, t, tz) => { const expr = parseCron(e); const next = expr.nextRun(t, tz); expect(expr.matches(next as number, tz)).toBe(true); } ), { numRuns: 30 } ); }); }); describe("property — previousRun(nextRun) <= after", () => { it("backward search lands at-or-before the original anchor", { timeout: 30000, }, () => { fc.assert( fc.property( fc.constantFrom(...EXPRESSIONS), timestampArb, fc.constantFrom(...TIMEZONES), (e, t, tz) => { const expr = parseCron(e); const next = expr.nextRun(t, tz) as number; const prev = expr.previousRun(next, tz); expect(prev).not.toBeNull(); expect(prev as number).toBeLessThanOrEqual(t); } ), { numRuns: 30 } ); }); });