Initial Commit
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import * as fc from "fast-check";
|
||||
import { formatRRule, parseRRule } from "./recurrence.js";
|
||||
|
||||
describe("parseRRule", () => {
|
||||
it("parses FREQ=DAILY", { timeout: 5000 }, () => {
|
||||
const rule = parseRRule("FREQ=DAILY");
|
||||
expect(rule.freq).toBe("daily");
|
||||
});
|
||||
|
||||
it("parses FREQ=WEEKLY", { timeout: 5000 }, () => {
|
||||
expect(parseRRule("FREQ=WEEKLY").freq).toBe("weekly");
|
||||
});
|
||||
|
||||
it("parses FREQ=MONTHLY", { timeout: 5000 }, () => {
|
||||
expect(parseRRule("FREQ=MONTHLY").freq).toBe("monthly");
|
||||
});
|
||||
|
||||
it("parses FREQ=YEARLY", { timeout: 5000 }, () => {
|
||||
expect(parseRRule("FREQ=YEARLY").freq).toBe("yearly");
|
||||
});
|
||||
|
||||
it("parses INTERVAL", { timeout: 5000 }, () => {
|
||||
const rule = parseRRule("FREQ=WEEKLY;INTERVAL=2");
|
||||
expect(rule.interval).toBe(2);
|
||||
});
|
||||
|
||||
it("parses COUNT", { timeout: 5000 }, () => {
|
||||
const rule = parseRRule("FREQ=DAILY;COUNT=5");
|
||||
expect(rule.count).toBe(5);
|
||||
});
|
||||
|
||||
it("parses UNTIL (YYYYMMDD)", { timeout: 5000 }, () => {
|
||||
const rule = parseRRule("FREQ=DAILY;UNTIL=20241231");
|
||||
expect(rule.until?.getFullYear()).toBe(2024);
|
||||
expect(rule.until?.getMonth()).toBe(11);
|
||||
expect(rule.until?.getDate()).toBe(31);
|
||||
});
|
||||
|
||||
it("parses UNTIL with time suffix", { timeout: 5000 }, () => {
|
||||
const rule = parseRRule("FREQ=DAILY;UNTIL=20241231T000000Z");
|
||||
expect(rule.until?.getFullYear()).toBe(2024);
|
||||
});
|
||||
|
||||
it("parses BYDAY", { timeout: 5000 }, () => {
|
||||
const rule = parseRRule("FREQ=WEEKLY;BYDAY=MO,WE,FR");
|
||||
expect(rule.byDay).toEqual([1, 3, 5]);
|
||||
});
|
||||
|
||||
it("parses BYMONTHDAY", { timeout: 5000 }, () => {
|
||||
const rule = parseRRule("FREQ=MONTHLY;BYMONTHDAY=15");
|
||||
expect(rule.byMonthDay).toEqual([15]);
|
||||
});
|
||||
|
||||
it("ignores parts without '=' sign", { timeout: 5000 }, () => {
|
||||
// Covers the `if (eq === -1) continue` branch in the parser loop
|
||||
const rule = parseRRule("FREQ=DAILY;BADPART;COUNT=3");
|
||||
expect(rule.freq).toBe("daily");
|
||||
expect(rule.count).toBe(3);
|
||||
});
|
||||
|
||||
it("throws for missing FREQ", { timeout: 5000 }, () => {
|
||||
let err: unknown;
|
||||
try {
|
||||
parseRRule("INTERVAL=2");
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
expect((err as { code?: string }).code).toBe(
|
||||
"DATETIME.PARSE.INVALID_INPUT"
|
||||
);
|
||||
});
|
||||
|
||||
it("throws for unknown FREQ", { timeout: 5000 }, () => {
|
||||
let err: unknown;
|
||||
try {
|
||||
parseRRule("FREQ=HOURLY");
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
expect((err as { code?: string }).code).toBe(
|
||||
"DATETIME.PARSE.INVALID_INPUT"
|
||||
);
|
||||
});
|
||||
|
||||
it("handles all day abbreviations", { timeout: 5000 }, () => {
|
||||
const rule = parseRRule("FREQ=WEEKLY;BYDAY=SU,MO,TU,WE,TH,FR,SA");
|
||||
expect(rule.byDay).toEqual([0, 1, 2, 3, 4, 5, 6]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatRRule", () => {
|
||||
it("formats basic rule", { timeout: 5000 }, () => {
|
||||
const rule = parseRRule("FREQ=DAILY;COUNT=5");
|
||||
expect(formatRRule(rule)).toBe("FREQ=DAILY;COUNT=5");
|
||||
});
|
||||
|
||||
it("formats UNTIL", { timeout: 5000 }, () => {
|
||||
const rule = parseRRule("FREQ=WEEKLY;UNTIL=20241231");
|
||||
const formatted = formatRRule(rule);
|
||||
expect(formatted).toContain("UNTIL=20241231");
|
||||
});
|
||||
|
||||
it("formats BYDAY", { timeout: 5000 }, () => {
|
||||
const rule = parseRRule("FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=6");
|
||||
const formatted = formatRRule(rule);
|
||||
expect(formatted).toContain("BYDAY=MO,WE,FR");
|
||||
expect(formatted).toContain("FR");
|
||||
});
|
||||
|
||||
it("formats all day abbreviations round-trip", { timeout: 5000 }, () => {
|
||||
const rule = parseRRule(
|
||||
"FREQ=WEEKLY;BYDAY=SU,MO,TU,WE,TH,FR,SA;COUNT=1"
|
||||
);
|
||||
const formatted = formatRRule(rule);
|
||||
expect(formatted).toContain("SU");
|
||||
expect(formatted).toContain("MO");
|
||||
expect(formatted).toContain("TU");
|
||||
expect(formatted).toContain("WE");
|
||||
expect(formatted).toContain("TH");
|
||||
expect(formatted).toContain("FR");
|
||||
expect(formatted).toContain("SA");
|
||||
});
|
||||
|
||||
it("formats BYMONTHDAY", { timeout: 5000 }, () => {
|
||||
const rule = parseRRule("FREQ=MONTHLY;BYMONTHDAY=15;COUNT=3");
|
||||
expect(formatRRule(rule)).toContain("BYMONTHDAY=15");
|
||||
});
|
||||
|
||||
it("formats INTERVAL", { timeout: 5000 }, () => {
|
||||
// Ensures the `if (rule.interval !== undefined)` branch is taken
|
||||
const rule = parseRRule("FREQ=WEEKLY;INTERVAL=2;COUNT=4");
|
||||
const formatted = formatRRule(rule);
|
||||
expect(formatted).toContain("INTERVAL=2");
|
||||
});
|
||||
|
||||
it("parseRRule → formatRRule → parseRRule identity (property test)", {
|
||||
timeout: 30000,
|
||||
}, () => {
|
||||
const freqs = ["DAILY", "WEEKLY", "MONTHLY", "YEARLY"] as const;
|
||||
fc.assert(
|
||||
fc.property(
|
||||
fc.constantFrom(...freqs),
|
||||
fc.integer({ min: 1, max: 10 }),
|
||||
(freq, count) => {
|
||||
const input = `FREQ=${freq};COUNT=${count}`;
|
||||
const parsed = parseRRule(input);
|
||||
const formatted = formatRRule(parsed);
|
||||
const reparsed = parseRRule(formatted);
|
||||
expect(reparsed.freq).toBe(parsed.freq);
|
||||
expect(reparsed.count).toBe(parsed.count);
|
||||
}
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user