diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-13 10:25:03 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-13 10:25:03 +0200 |
| commit | 9bc2dba1f595ed5ed7121d17b3361a718cc3b1a6 (patch) | |
| tree | 598eeda7b551aff960ee7249859c21470772a19a /desktop/island/calendar/test_calctl.py | |
| parent | 35bce0a57b6839471e6d8a31f1af4870af0ab94d (diff) | |
| download | quickshell-9bc2dba1f595ed5ed7121d17b3361a718cc3b1a6.tar.gz quickshell-9bc2dba1f595ed5ed7121d17b3361a718cc3b1a6.zip | |
feat(calendar): calctl reads the vdir as JSON
The calendar list comes from khal's config, so there is no second list.
Recurrence is expanded with dateutil in naive local time, which keeps a
weekly 09:00 at 09:00 across the DST change; the test pins that on 26
October. A rule richer than a plain FREQ, or alarms other than one
preset, read as custom, which is what lets an edit leave them alone.
A file that fails to parse is skipped rather than blanking the month.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/island/calendar/test_calctl.py')
| -rw-r--r-- | desktop/island/calendar/test_calctl.py | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/desktop/island/calendar/test_calctl.py b/desktop/island/calendar/test_calctl.py new file mode 100644 index 0000000..e6acd10 --- /dev/null +++ b/desktop/island/calendar/test_calctl.py @@ -0,0 +1,137 @@ +#!/usr/bin/python3 +# 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: /usr/bin/python3 desktop/island/calendar/test_calctl.py + +Drives calctl as a CLI against temporary vdirs and a temporary khal config, +never the live calendars. Plain asserts, no framework. +""" + +import json +import os +import shutil +import subprocess +import tempfile +from pathlib import Path + +CALCTL = Path(__file__).resolve().parent / "calctl" + +tmp = Path(tempfile.mkdtemp(prefix="calctl-test-")) +(tmp / "a").mkdir() +(tmp / "b").mkdir() +(tmp / "khal").write_text(f"""[calendars] + +[[home]] +path = {tmp}/a +type = calendar +color = light green + +[[birthdays]] +path = {tmp}/b +color = '#ff8800' + +[locale] +firstweekday = 0 + +[default] +default_calendar = home +""") +ENV = dict(os.environ, + CALCTL_KHAL_CONFIG=str(tmp / "khal"), + CALCTL_OVERRIDES=str(tmp / "overrides.toml"), + CALCTL_NOSYNC="1", + TZ="Europe/Rome") + + +def run(*args, ok=True): + p = subprocess.run([str(CALCTL), *args], env=ENV, capture_output=True, text=True) + r = json.loads(p.stdout) + assert r["ok"] is ok, (args, p.stdout, p.stderr) + assert (p.returncode == 0) is ok, (args, p.returncode) + return r["data"] if ok else r["error"] + + +def put(path, body): + """A hand-written .ics, CRLF like a real one.""" + Path(path).write_text(body.strip().replace("\n", "\r\n") + "\r\n") + + +# calendars: khal colour names mapped to Theme names, a hex passed through. +c = run("calendars") +assert c["default"] == "home" +assert [(x["name"], x["color"]) for x in c["calendars"]] == [("home", "green"), ("birthdays", "#ff8800")] +assert c["calendars"][0]["path"] == str(tmp / "a") + +# A yearly all-day event from long ago is expanded into the asked window. +put(tmp / "b" / "bday.ics", """ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//test//EN +BEGIN:VEVENT +UID:bday-1 +DTSTAMP:20200101T000000Z +DTSTART;VALUE=DATE:19900417 +DTEND;VALUE=DATE:19900418 +RRULE:FREQ=YEARLY;WKST=TU +SUMMARY:Birthday +END:VEVENT +END:VCALENDAR +""") +apr = [e for e in run("list", "2026-04-01", "2026-05-01") if e["uid"] == "bday-1"] +assert len(apr) == 1, apr +assert apr[0]["start"] == "2026-04-17" and apr[0]["end"] == "2026-04-17", apr +assert apr[0]["allDay"] is True and apr[0]["repeat"] == "yearly" and apr[0]["color"] == "#ff8800" + +# A rich rule and two alarms read as custom; the weekly rule is expanded in +# local time, so 09:00 stays 09:00 across the DST change on 25 October. +put(tmp / "a" / "weird.ics", """ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//test//EN +BEGIN:VEVENT +UID:weird-1 +DTSTAMP:20260101T000000Z +DTSTART;TZID=Europe/Rome:20261005T090000 +DTEND;TZID=Europe/Rome:20261005T100000 +RRULE:FREQ=WEEKLY;BYDAY=MO,WE +SUMMARY:Standup +X-FOO:bar +BEGIN:VALARM +ACTION:DISPLAY +DESCRIPTION:a +TRIGGER:-PT1H +END:VALARM +BEGIN:VALARM +ACTION:DISPLAY +DESCRIPTION:b +TRIGGER:-PT5M +END:VALARM +END:VEVENT +END:VCALENDAR +""") +w = run("get", "weird-1") +assert w["repeat"] == "custom" and w["alarm"] == "custom" and w["title"] == "Standup", w +assert w["start"] == "2026-10-05T09:00", w +occ = [e["start"] for e in run("list", "2026-10-01", "2026-11-01") if e["uid"] == "weird-1"] +assert len(occ) == 8 and occ[0] == "2026-10-05T09:00" and "2026-10-26T09:00" in occ, occ + +# A broken file is skipped, not fatal. +(tmp / "a" / "broken.ics").write_text("this is not a calendar\n") +assert run("list", "2026-10-01", "2026-11-01") + +# Unknown uid and bad usage: the error envelope, exit 1. +assert "no event" in run("get", "nope", ok=False) +assert "usage" in run("frobnicate", ok=False) + +# --- writes (Task 3) go here --- + +shutil.rmtree(tmp) +print("test_calctl: ok") |
