46 lines
1.6 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { parseRRule, nextOccurrence, validateMinInterval, MIN_INTERVAL_MS } from "./rrule.js";
describe("parseRRule", () => {
it("accepts a daily rule", () => {
expect(() => parseRRule("FREQ=DAILY;BYHOUR=9;BYMINUTE=0")).not.toThrow();
});
it("rejects invalid syntax", () => {
expect(() => parseRRule("not-a-rule")).toThrow();
});
});
describe("nextOccurrence", () => {
it("returns the next firing time after `after`", () => {
const rule = "FREQ=DAILY;BYHOUR=9;BYMINUTE=0";
const after = new Date("2026-05-03T08:00:00Z");
const next = nextOccurrence(rule, "Asia/Kuala_Lumpur", after);
expect(next).toBeInstanceOf(Date);
expect(next!.getTime()).toBeGreaterThan(after.getTime());
});
it("returns null when the rule has no further occurrences", () => {
const past = "DTSTART:20200101T000000Z\nRRULE:FREQ=DAILY;COUNT=1";
expect(nextOccurrence(past, "Asia/Kuala_Lumpur", new Date())).toBeNull();
});
});
describe("validateMinInterval", () => {
it("accepts a daily rule (interval > 5 min)", () => {
expect(validateMinInterval("FREQ=DAILY;BYHOUR=9;BYMINUTE=0", "Asia/Kuala_Lumpur"))
.toEqual({ ok: true });
});
it("rejects a rule firing every minute", () => {
const result = validateMinInterval("FREQ=MINUTELY", "Asia/Kuala_Lumpur");
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.reason).toMatch(/minimum interval/i);
}
});
});
describe("MIN_INTERVAL_MS", () => {
it("equals 5 minutes", () => {
expect(MIN_INTERVAL_MS).toBe(5 * 60 * 1000);
});
});