diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-13 10:40:51 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-13 10:40:51 +0200 |
| commit | dc6211b61676fcea24aabd3300648cec699175fb (patch) | |
| tree | b74b3cd3d9fa3025cf8a9800f860a7cdb438b1d2 /desktop/island/calendar/test_calctl.py | |
| parent | 901a88e26b91eb104aa9a837372d35db24f7b6bf (diff) | |
| download | quickshell-dc6211b61676fcea24aabd3300648cec699175fb.tar.gz quickshell-dc6211b61676fcea24aabd3300648cec699175fb.zip | |
fix(calendar): review findings in calctl and the editor
An edit now rewrites only the fields that changed. A save with nothing
changed used to rebuild the alarm, turning ACTION:AUDIO into DISPLAY
and dropping ACKNOWLEDGED, re-anchor the start to the local zone, and
strip SUMMARY's parameters; the test edits a foreign event unchanged and
checks every one survives.
get, edit and delete name the calendar the event is in. The same invite
synced into two calendars has one UID, and the panel acted on whichever
file came first, so editing one copy could overwrite or delete the
other.
A custom rule's date cannot be moved from the panel: BYDAY=MO stayed on
Monday while the start moved, and the series lost the day shown.
UNTIL is read back in local time, so a 22:30 series keeps its last
occurrence. Files are written 0644 like vdirsyncer's. Enter in the title
no longer bypasses the busy guard, which could create an event twice,
and a throwing callback no longer leaks its Process.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/island/calendar/test_calctl.py')
| -rw-r--r-- | desktop/island/calendar/test_calctl.py | 101 |
1 files changed, 90 insertions, 11 deletions
diff --git a/desktop/island/calendar/test_calctl.py b/desktop/island/calendar/test_calctl.py index 98973ee..91ad384 100644 --- a/desktop/island/calendar/test_calctl.py +++ b/desktop/island/calendar/test_calctl.py @@ -117,7 +117,7 @@ END:VALARM END:VEVENT END:VCALENDAR """) -w = run("get", "weird-1") +w = run("get", "weird-1", "home") 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"] @@ -128,7 +128,7 @@ assert len(occ) == 8 and occ[0] == "2026-10-05T09:00" and "2026-10-26T09:00" in 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 "no event" in run("get", "nope", "home", ok=False) assert "usage" in run("frobnicate", ok=False) def ev(**kw): @@ -156,7 +156,7 @@ assert len(mine) == 1, mine assert mine[0]["start"] == "2026-10-15T14:30" and mine[0]["end"] == "2026-10-15T15:30" assert mine[0]["alarm"] == "1h" and mine[0]["repeat"] == "none" and mine[0]["color"] == "green" assert mine[0]["override"] is False -assert run("get", uid)["title"] == "Dentist" +assert run("get", uid, "home")["title"] == "Dentist" # all-day, multi-day: exclusive DTEND on disk, inclusive end in JSON. trip = run("new", ev(title="Trip", allDay=True, start="2026-10-30", end="2026-11-02", alarm="none"))["uid"] @@ -171,11 +171,19 @@ assert not [e for e in run("list", "2026-10-01", "2026-10-30") if e["uid"] == tr gym = run("new", ev(title="Gym", repeat="weekly", until="2026-10-29"))["uid"] occ = [e["start"] for e in run("list", "2026-10-01", "2026-12-01") if e["uid"] == gym] assert occ == ["2026-10-15T14:30", "2026-10-22T14:30", "2026-10-29T14:30"], occ -assert run("get", gym)["until"] == "2026-10-29" +assert os.stat(tmp / "a" / f"{gym}.ics").st_mode & 0o777 == 0o644 + +# A late-evening series keeps its last occurrence: UNTIL is UTC on disk and +# read back in local time. +late = run("new", ev(title="Late", start="2026-10-05T22:30", end="2026-10-05T23:00", + repeat="weekly", until="2026-10-19"))["uid"] +occ = [e["start"] for e in run("list", "2026-10-01", "2026-11-01") if e["uid"] == late] +assert occ == ["2026-10-05T22:30", "2026-10-12T22:30", "2026-10-19T22:30"], occ +assert run("get", gym, "home")["until"] == "2026-10-29" # edit keeping custom: the rich rule, both alarms and an unknown property # survive; SEQUENCE goes up. -run("edit", "weird-1", ev(title="Standup v2", start="2026-10-05T09:30", +run("edit", "weird-1", "home", ev(title="Standup v2", start="2026-10-05T09:30", end="2026-10-05T10:00", repeat="custom", alarm="custom")) t = (tmp / "a" / "weird.ics").read_text() assert "BYDAY=MO,WE" in t and t.count("BEGIN:VALARM") == 2 and "X-FOO:bar" in t, t @@ -183,24 +191,95 @@ assert "SUMMARY:Standup v2" in t and "SEQUENCE:1" in t, t no_temp() # edit to presets: the rule goes, the alarms become one. -run("edit", "weird-1", ev(title="Standup v2", start="2026-10-05T09:30", +run("edit", "weird-1", "home", ev(title="Standup v2", start="2026-10-05T09:30", end="2026-10-05T10:00", repeat="none", alarm="10m")) t = (tmp / "a" / "weird.ics").read_text() assert "RRULE" not in t and t.count("BEGIN:VALARM") == 1 and "TRIGGER:-PT10M" in t, t +# a custom rule cannot have its date moved: its BY* parts would stay on the +# old day. +put(tmp / "a" / "weird2.ics", """ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//test//EN +BEGIN:VEVENT +UID:weird-2 +DTSTAMP:20260101T000000Z +DTSTART;TZID=Europe/Rome:20261005T090000 +DTEND;TZID=Europe/Rome:20261005T100000 +RRULE:FREQ=WEEKLY;BYDAY=MO +SUMMARY:Standup +END:VEVENT +END:VCALENDAR +""") +assert "rule" in run("edit", "weird-2", "home", ev(title="Standup", start="2026-10-07T09:00", + end="2026-10-07T10:00", repeat="custom", + alarm="none"), ok=False) + +# an edit that changes nothing leaves the event's own encoding alone: zone, +# DURATION, the alarm's ACTION and extras, SUMMARY's parameter. +put(tmp / "a" / "foreign.ics", """ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//test//EN +BEGIN:VEVENT +UID:foreign-1 +DTSTAMP:20260101T000000Z +DTSTART;TZID=America/New_York:20261005T100000 +DURATION:PT1H +SUMMARY;LANGUAGE=it:Chiamata +BEGIN:VALARM +ACTION:AUDIO +TRIGGER:-PT1H +ACKNOWLEDGED:20261001T080000Z +END:VALARM +END:VEVENT +END:VCALENDAR +""") +g = run("get", "foreign-1", "home") +same = {k: g[k] for k in ("calendar", "title", "allDay", "start", "end", "repeat", "until", + "alarm", "location", "notes")} +run("edit", "foreign-1", "home", json.dumps(same)) +t = (tmp / "a" / "foreign.ics").read_text() +for kept in ("DTSTART;TZID=America/New_York:20261005T100000", "DURATION:PT1H", "ACTION:AUDIO", + "ACKNOWLEDGED:20261001T080000Z", "SUMMARY;LANGUAGE=it:Chiamata"): + assert kept in t, (kept, t) +# Changing only the title rewrites the title, nothing else. +run("edit", "foreign-1", "home", json.dumps(dict(same, title="Call"))) +t = (tmp / "a" / "foreign.ics").read_text() +assert "SUMMARY:Call" in t and "TZID=America/New_York" in t and "ACTION:AUDIO" in t, t + +# the same UID in two calendars: each command acts on the copy it names. +for d in ("a", "b"): + put(tmp / d / "dup.ics", f""" +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//test//EN +BEGIN:VEVENT +UID:dup-1 +DTSTAMP:20260101T000000Z +DTSTART;VALUE=DATE:20261020 +SUMMARY:copy {d} +END:VEVENT +END:VCALENDAR +""") +assert run("get", "dup-1", "birthdays")["title"] == "copy b" +run("delete", "dup-1", "home") +assert not (tmp / "a" / "dup.ics").exists() and (tmp / "b" / "dup.ics").exists() + # a calendar change moves the file. -run("edit", uid, ev(calendar="birthdays")) +run("edit", uid, "home", ev(calendar="birthdays")) assert not f.exists() and (tmp / "b" / f.name).exists() -assert run("get", uid)["calendar"] == "birthdays" +assert run("get", uid, "birthdays")["calendar"] == "birthdays" # cal-notif's per-event override is reported. (tmp / "overrides.toml").write_text(f'"{uid}" = ["10m"]\n') -assert run("get", uid)["override"] is True +assert run("get", uid, "birthdays")["override"] is True # delete. -assert run("delete", uid) == {} +assert run("delete", uid, "birthdays") == {} assert not (tmp / "b" / f.name).exists() -assert "no event" in run("get", uid, ok=False) +assert "no event" in run("get", uid, "birthdays", ok=False) # validation: the error envelope, exit 1, nothing written. before = sorted(p.name for p in tmp.rglob("*.ics")) |
