Release Calendar Tools 0.1.0

This commit is contained in:
2026-09-01 13:04:50 +02:00
commit 47fbaba0b7
57 changed files with 9361 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
import { expect, test, type Page } from "@playwright/test";
const ORIGIN = "http://127.0.0.1:4173";
async function localOnly(page: Page) {
const external: string[] = [];
await page.route("**/*", async (route) => {
const url = new URL(route.request().url());
if (url.origin !== ORIGIN) {
external.push(url.href);
await route.abort();
} else await route.continue();
});
return external;
}
test("runs from a nested path without external requests", async ({ page }) => {
const errors: string[] = [];
page.on("pageerror", (error) => errors.push(error.message));
page.on("console", (message) => {
if (message.type() === "error") errors.push(message.text());
});
const external = await localOnly(page);
await page.goto("/deep/nested/calendar/");
await expect(
page.getByRole("banner").getByRole("heading", { name: "Calendar Tools" }),
).toBeVisible();
expect(external).toEqual([]);
expect(errors).toEqual([]);
});
test("keeps prior inspection on failure and diagnoses a DST overlap", async ({
page,
}) => {
const external = await localOnly(page);
await page.goto("/deep/nested/calendar/");
await expect(page.getByText("Weekly review", { exact: true })).toBeVisible();
await page.getByLabel("iCalendar source").fill("broken");
await page.getByRole("button", { name: "Inspect calendar" }).click();
await expect(page.getByRole("alert")).toBeVisible();
await page.getByRole("tab", { name: "Timezone" }).click();
await page.getByRole("button", { name: "Diagnose" }).click();
await expect(
page.getByText(/Overlap: this local time occurs 2 times/u),
).toBeVisible();
expect(external).toEqual([]);
});
test("serves release identity and hardened headers", async ({ request }) => {
const index = await request.get("/deep/nested/calendar/");
expect(index.ok()).toBe(true);
expect(index.headers()["content-security-policy"]).toContain(
"connect-src 'self'",
);
expect(await index.text()).not.toMatch(/\b(?:src|href)=["']\//u);
const manifest = await request.get("/deep/nested/calendar/toolbox-app.json");
await expect(manifest.json()).resolves.toMatchObject({
id: "de.add-ideas.calendar-tools",
version: "0.1.0",
entry: "./",
privacy: { processing: "local", telemetry: false },
});
});
+87
View File
@@ -0,0 +1,87 @@
import { describe, expect, it } from "vitest";
import {
canonicalRepair,
findConflicts,
localTimeCandidates,
mergeCalendars,
parseCalendar,
previewOccurrences,
timezoneDiagnostics,
} from "../../src/calendar/model";
const recurring = `BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//test//EN\r\nBEGIN:VEVENT\r\nUID:a\r\nDTSTAMP:20260101T000000Z\r\nDTSTART:20260901T100000Z\r\nDTEND:20260901T110000Z\r\nRRULE:FREQ=DAILY;COUNT=3\r\nSUMMARY:A\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:b\r\nDTSTART:20260901T103000Z\r\nDTEND:20260901T120000Z\r\nSUMMARY:B\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n`;
describe("calendar model", () => {
it("parses and expands recurrence under a bound", () => {
const document = parseCalendar(recurring);
expect(document.events).toHaveLength(2);
const preview = previewOccurrences(document, 10, 30);
expect(preview.occurrences.filter((item) => item.uid === "a")).toHaveLength(
3,
);
expect(findConflicts(preview.occurrences)).toEqual(
expect.arrayContaining([expect.objectContaining({ kind: "overlap" })]),
);
});
it("canonicalizes line endings and supplies required metadata", () => {
const input = `BEGIN:VCALENDAR\nBEGIN:VEVENT\nUID:x\nDTSTART:20260901T100000Z\nDTEND:20260901T110000Z\nEND:VEVENT\nEND:VCALENDAR\n`;
const repaired = canonicalRepair(input);
expect(repaired.source).toContain("VERSION:2.0\r\n");
expect(repaired.source).toContain("PRODID:");
expect(repaired.source).not.toMatch(/(?<!\r)\n/u);
});
it("merges event identity using sequence then stamp", () => {
const newer = recurring.replace(
"SUMMARY:A",
"SEQUENCE:2\r\nSUMMARY:Updated A",
);
const merged = mergeCalendars([
parseCalendar(recurring),
parseCalendar(newer),
]);
expect(merged.deduplicated).toBe(2);
expect(merged.source).toContain("SUMMARY:Updated A");
expect(parseCalendar(merged.source).events).toHaveLength(2);
});
it("never deduplicates separate UID-less events", () => {
const uidless = `BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//test//EN\r\nBEGIN:VEVENT\r\nDTSTART:20260901T100000Z\r\nDTEND:20260901T110000Z\r\nSUMMARY:No identity\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n`;
const merged = mergeCalendars([
parseCalendar(uidless),
parseCalendar(uidless),
]);
expect(merged.deduplicated).toBe(0);
expect(parseCalendar(merged.source).events).toHaveLength(2);
expect(merged.diagnostics.map((item) => item.message).join(" ")).toMatch(
/UID-less/u,
);
});
it("uses embedded VTIMEZONE observances for custom TZIDs", () => {
const custom = `BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//test//EN\r\nBEGIN:VTIMEZONE\r\nTZID:Custom/Test\r\nBEGIN:STANDARD\r\nDTSTART:19700101T000000\r\nTZOFFSETFROM:+0230\r\nTZOFFSETTO:+0230\r\nTZNAME:CST\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:custom\r\nDTSTART;TZID=Custom/Test:20260901T100000\r\nDTEND;TZID=Custom/Test:20260901T110000\r\nSUMMARY:Custom zone\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n`;
const document = parseCalendar(custom);
expect(document.timezones[0]?.tzid).toBe("Custom/Test");
expect(document.events[0]?.startMs).toBe(
Date.parse("2026-09-01T07:30:00.000Z"),
);
});
it("classifies DST overlaps and reports yearly transitions", () => {
const candidates = localTimeCandidates("2026-10-25T02:30", "Europe/Berlin");
expect(candidates).toHaveLength(2);
expect(timezoneDiagnostics("Europe/Berlin", 2026).transitions).toHaveLength(
2,
);
});
it("rejects unbounded and malformed inputs", () => {
expect(() => previewOccurrences(parseCalendar(recurring), 501, 30)).toThrow(
/1500/u,
);
expect(() => parseCalendar("not a calendar")).toThrow(
/parse failed|root component/u,
);
});
});
+24
View File
@@ -0,0 +1,24 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { App } from "../../src/App";
describe("Calendar Tools", () => {
it("renders the local workbench and bounded recurrence view", async () => {
vi.stubGlobal(
"fetch",
vi.fn(async () => new Response("Not found", { status: 404 })),
);
render(<App />);
expect(
await screen.findByRole("heading", { name: "Calendar Tools" }),
).toBeVisible();
await userEvent.click(
await screen.findByRole("tab", { name: "Recurrence" }),
);
expect(
screen.getByRole("heading", { name: "Occurrence and conflict preview" }),
).toBeVisible();
expect(screen.getByText("Browser-local")).toBeVisible();
});
});