aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/island/calendar/test_cal.js
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-13 10:25:11 +0200
committerDanilo M. <danix@danix.xyz>2026-10-13 10:25:11 +0200
commitc3fefb77d78df863d9fe4b222fa98fa0d5fac092 (patch)
tree9c578562e777ab26b520788ec460997e74379510 /desktop/island/calendar/test_cal.js
parent091abf04a3e9be7ce36a3bc91441048085f56900 (diff)
downloadquickshell-c3fefb77d78df863d9fe4b222fa98fa0d5fac092.tar.gz
quickshell-c3fefb77d78df863d9fe4b222fa98fa0d5fac092.zip
feat(calendar): pure date logic for the panel
Month cells from Monday, which days an event covers, the day's agenda order, the grid's dots, time stepping, and the editor's draft in both directions. Days are strings and arithmetic is in UTC, so the DST change at the end of October never moves a day; the test steps across it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/island/calendar/test_cal.js')
-rw-r--r--desktop/island/calendar/test_cal.js124
1 files changed, 124 insertions, 0 deletions
diff --git a/desktop/island/calendar/test_cal.js b/desktop/island/calendar/test_cal.js
new file mode 100644
index 0000000..d91966e
--- /dev/null
+++ b/desktop/island/calendar/test_cal.js
@@ -0,0 +1,124 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// Run: node desktop/island/calendar/test_cal.js
+// Cal.js is a QML library, so its .pragma line is stripped first.
+
+const fs = require("fs");
+const assert = require("assert");
+
+const src = fs.readFileSync(__dirname + "/Cal.js", "utf8").replace(/^\.pragma library$/m, "");
+const C = new Function(src + "\nreturn { pad, addDays, today, daysBetween, monthCells, shiftMonth," +
+ " lastDay, onDay, dayEvents, dots, timeLabel, validTime, stepTime, newDraft, fromEvent, pick," +
+ " toPayload };")();
+
+// Dates are plain YYYY-MM-DD strings, arithmetic in UTC so DST never moves a day.
+assert.strictEqual(C.addDays("2026-10-31", 1), "2026-11-01");
+assert.strictEqual(C.addDays("2026-03-01", -1), "2026-02-28");
+assert.strictEqual(C.addDays("2026-10-24", 2), "2026-10-26");
+assert.strictEqual(C.daysBetween("2026-10-30", "2026-11-02"), 3);
+assert.strictEqual(C.today(new Date(2026, 9, 13, 23, 59)), "2026-10-13");
+
+// Month cells: six weeks from Monday. October 2026 starts on a Thursday.
+const oct = C.monthCells(2026, 9);
+assert.strictEqual(oct.length, 42);
+assert.strictEqual(oct[0].iso, "2026-09-28");
+assert.strictEqual(oct[0].inMonth, false);
+assert.strictEqual(oct[3].iso, "2026-10-01");
+assert.strictEqual(oct[3].inMonth, true);
+assert.strictEqual(oct[3].day, 1);
+assert.strictEqual(oct[41].iso, "2026-11-08");
+// June 2026 starts on a Monday: no leading days.
+assert.strictEqual(C.monthCells(2026, 5)[0].iso, "2026-06-01");
+
+assert.deepStrictEqual(C.shiftMonth(2026, 11, 1), { y: 2027, m0: 0 });
+assert.deepStrictEqual(C.shiftMonth(2026, 0, -1), { y: 2025, m0: 11 });
+
+// Which days an event covers.
+const timed = { uid: "t", calendar: "home", allDay: false, start: "2026-10-15T14:30", end: "2026-10-15T15:30" };
+const night = { uid: "n", calendar: "home", allDay: false, start: "2026-10-15T22:00", end: "2026-10-16T00:00" };
+const late = { uid: "l", calendar: "home", allDay: false, start: "2026-10-15T23:00", end: "2026-10-16T01:00" };
+const trip = { uid: "a", calendar: "trips", allDay: true, start: "2026-10-14", end: "2026-10-16" };
+assert.strictEqual(C.lastDay(timed), "2026-10-15");
+assert.strictEqual(C.lastDay(night), "2026-10-15"); // ends at midnight: not on the 16th
+assert.strictEqual(C.lastDay(late), "2026-10-16");
+assert.strictEqual(C.lastDay(trip), "2026-10-16");
+assert.ok(C.onDay(trip, "2026-10-15") && !C.onDay(trip, "2026-10-17"));
+
+// The day's agenda: all-day first, then by start.
+const evs = [timed, late, trip, night];
+assert.deepStrictEqual(C.dayEvents(evs, "2026-10-15").map(e => e.uid), ["a", "t", "n", "l"]);
+assert.deepStrictEqual(C.dayEvents(evs, "2026-10-16").map(e => e.uid), ["a", "l"]);
+
+// Dots: one per calendar with something that day, in calendar order.
+const cals = [{ name: "home", color: "green" }, { name: "trips", color: "#ff8800" }];
+assert.deepStrictEqual(C.dots(evs, "2026-10-15", cals).map(c => c.name), ["home", "trips"]);
+assert.deepStrictEqual(C.dots(evs, "2026-10-14", cals).map(c => c.name), ["trips"]);
+assert.deepStrictEqual(C.dots(evs, "2026-10-20", cals), []);
+
+assert.strictEqual(C.timeLabel(trip, "2026-10-15"), "all day");
+assert.strictEqual(C.timeLabel(timed, "2026-10-15"), "14:30–15:30");
+assert.strictEqual(C.timeLabel(late, "2026-10-15"), "23:00–…");
+assert.strictEqual(C.timeLabel(late, "2026-10-16"), "…–01:00");
+
+// Times.
+assert.ok(C.validTime("09:05") && C.validTime("23:59"));
+assert.ok(!C.validTime("24:00") && !C.validTime("9:05") && !C.validTime("ab:cd"));
+assert.strictEqual(C.stepTime("14:30", 15), "14:45");
+assert.strictEqual(C.stepTime("23:50", 15), "00:05");
+assert.strictEqual(C.stepTime("00:05", -15), "23:50");
+assert.strictEqual(C.stepTime("bad", 15), "bad");
+
+// A new event: the next whole hour, one hour long, alarm 1h.
+const d = C.newDraft("2026-10-20", new Date(2026, 9, 13, 9, 20), "home");
+assert.strictEqual(d.startTime, "10:00");
+assert.strictEqual(d.endTime, "11:00");
+assert.strictEqual(d.startDay, "2026-10-20");
+assert.strictEqual(d.alarm, "1h");
+assert.strictEqual(d.calendar, "home");
+assert.strictEqual(C.newDraft("2026-10-20", new Date(2026, 9, 13, 23, 10), "home").startTime, "22:00");
+
+// From calctl's JSON.
+const fe = C.fromEvent({ uid: "u", calendar: "home", title: "Trip", allDay: true, start: "2026-10-14",
+ end: "2026-10-16", repeat: "none", until: "", alarm: "none", location: "", notes: "", override: false });
+assert.strictEqual(fe.startDay, "2026-10-14");
+assert.strictEqual(fe.endDay, "2026-10-16");
+assert.strictEqual(fe.allDay, true);
+assert.strictEqual(C.fromEvent(Object.assign({}, timed, { title: "x" })).startTime, "14:30");
+
+// Picking on the grid: a click moves the event and keeps its length in days;
+// shift-click sets the end, or the start when before it.
+const p1 = C.pick(fe, "2026-10-20", false);
+assert.strictEqual(p1.startDay, "2026-10-20");
+assert.strictEqual(p1.endDay, "2026-10-22");
+assert.strictEqual(fe.startDay, "2026-10-14"); // input untouched
+const p2 = C.pick(fe, "2026-10-18", true);
+assert.strictEqual(p2.startDay, "2026-10-14");
+assert.strictEqual(p2.endDay, "2026-10-18");
+const p3 = C.pick(fe, "2026-10-10", true);
+assert.strictEqual(p3.startDay, "2026-10-10");
+assert.strictEqual(p3.endDay, "2026-10-16");
+
+// To calctl's JSON.
+assert.strictEqual(C.toPayload(Object.assign({}, d, { title: " " })).error, "Title is empty");
+assert.strictEqual(C.toPayload(Object.assign({}, d, { title: "x", startTime: "9:00" })).error, "Times are HH:MM");
+assert.strictEqual(C.toPayload(Object.assign({}, d, { title: "x", endTime: "09:00" })).error, "End is before start");
+assert.strictEqual(C.toPayload(Object.assign({}, d, { title: "x", repeat: "weekly", until: "2026-10-01" })).error,
+ "Until is before the start");
+const ok = C.toPayload(Object.assign({}, d, { title: " Gym ", repeat: "weekly", until: "2026-12-01" })).payload;
+assert.deepStrictEqual(ok, { calendar: "home", title: "Gym", allDay: false, start: "2026-10-20T10:00",
+ end: "2026-10-20T11:00", repeat: "weekly", until: "2026-12-01", alarm: "1h", location: "", notes: "" });
+const ad = C.toPayload(Object.assign({}, fe, { repeat: "none", until: "2026-12-01" })).payload;
+assert.strictEqual(ad.start, "2026-10-14");
+assert.strictEqual(ad.end, "2026-10-16");
+assert.strictEqual(ad.until, ""); // no repeat, no until
+
+console.log("test_cal: ok");