diff options
| author | Craig Jennings <c@cjennings.net> | 2026-04-05 06:51:20 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-04-05 06:51:20 -0500 |
| commit | 7eb6c0692d48b8c3d01efba8abcd2bfdfaa7cd31 (patch) | |
| tree | 2af7aa512feaae39b384939160af68fa0537fc22 /tests | |
| parent | 47453fd03be79205c6209d31a26cb4f7724af317 (diff) | |
test(calendar-sync): add 68 tests across 13 files for untested pure functions
Covers core parsing (parse-ics-datetime, parse-timestamp, format-timestamp,
split-events, parse-event), date utilities (add-months, add-days,
weekday-to-number, date-weekday, event-start-time), and timezone
(format-timezone-offset, convert-utc-to-local, localize-parsed-datetime).
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test-calendar-sync--add-days.el | 44 | ||||
| -rw-r--r-- | tests/test-calendar-sync--add-months.el | 43 | ||||
| -rw-r--r-- | tests/test-calendar-sync--convert-utc-to-local.el | 41 | ||||
| -rw-r--r-- | tests/test-calendar-sync--date-weekday.el | 35 | ||||
| -rw-r--r-- | tests/test-calendar-sync--event-start-time.el | 41 | ||||
| -rw-r--r-- | tests/test-calendar-sync--format-timestamp.el | 60 | ||||
| -rw-r--r-- | tests/test-calendar-sync--format-timezone-offset.el | 44 | ||||
| -rw-r--r-- | tests/test-calendar-sync--localize-parsed-datetime.el | 54 | ||||
| -rw-r--r-- | tests/test-calendar-sync--parse-event.el | 82 | ||||
| -rw-r--r-- | tests/test-calendar-sync--parse-ics-datetime.el | 53 | ||||
| -rw-r--r-- | tests/test-calendar-sync--parse-timestamp.el | 59 | ||||
| -rw-r--r-- | tests/test-calendar-sync--split-events.el | 54 | ||||
| -rw-r--r-- | tests/test-calendar-sync--weekday-to-number.el | 37 |
13 files changed, 647 insertions, 0 deletions
diff --git a/tests/test-calendar-sync--add-days.el b/tests/test-calendar-sync--add-days.el new file mode 100644 index 00000000..a7b5eb39 --- /dev/null +++ b/tests/test-calendar-sync--add-days.el @@ -0,0 +1,44 @@ +;;; test-calendar-sync--add-days.el --- Tests for day arithmetic -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for calendar-sync--add-days. Uses noon internally for DST safety. + +;;; Code: + +(require 'ert) +(require 'testutil-calendar-sync) +(require 'calendar-sync) + +;;; Normal Cases + +(ert-deftest test-calendar-sync--add-days-normal-forward () + "Adding 7 days advances by one week." + (should (equal '(2026 3 22) (calendar-sync--add-days '(2026 3 15) 7)))) + +(ert-deftest test-calendar-sync--add-days-normal-backward () + "Negative days go backward." + (should (equal '(2026 3 8) (calendar-sync--add-days '(2026 3 15) -7)))) + +;;; Boundary Cases + +(ert-deftest test-calendar-sync--add-days-boundary-month-rollover () + "Adding days past end of month rolls into next month." + (should (equal '(2026 4 1) (calendar-sync--add-days '(2026 3 31) 1)))) + +(ert-deftest test-calendar-sync--add-days-boundary-leap-year () + "Feb 28 + 1 = Feb 29 in leap year 2028." + (should (equal '(2028 2 29) (calendar-sync--add-days '(2028 2 28) 1)))) + +(ert-deftest test-calendar-sync--add-days-boundary-zero () + "Adding zero days returns same date." + (should (equal '(2026 3 15) (calendar-sync--add-days '(2026 3 15) 0)))) + +;;; Error Cases + +(ert-deftest test-calendar-sync--add-days-error-large-offset () + "Adding 365 days crosses into next year." + (let ((result (calendar-sync--add-days '(2026 1 1) 365))) + (should (equal 2027 (nth 0 result))))) + +(provide 'test-calendar-sync--add-days) +;;; test-calendar-sync--add-days.el ends here diff --git a/tests/test-calendar-sync--add-months.el b/tests/test-calendar-sync--add-months.el new file mode 100644 index 00000000..fdc00208 --- /dev/null +++ b/tests/test-calendar-sync--add-months.el @@ -0,0 +1,43 @@ +;;; test-calendar-sync--add-months.el --- Tests for month arithmetic -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for calendar-sync--add-months. Pure date arithmetic. + +;;; Code: + +(require 'ert) +(require 'testutil-calendar-sync) +(require 'calendar-sync) + +;;; Normal Cases + +(ert-deftest test-calendar-sync--add-months-normal-forward () + "Adding 3 months advances correctly." + (should (equal '(2026 6 15) (calendar-sync--add-months '(2026 3 15) 3)))) + +(ert-deftest test-calendar-sync--add-months-normal-backward () + "Subtracting months goes backward." + (should (equal '(2025 12 15) (calendar-sync--add-months '(2026 3 15) -3)))) + +;;; Boundary Cases + +(ert-deftest test-calendar-sync--add-months-boundary-year-rollover () + "Adding months past December rolls into next year." + (should (equal '(2027 2 1) (calendar-sync--add-months '(2026 11 1) 3)))) + +(ert-deftest test-calendar-sync--add-months-boundary-year-rollback () + "Subtracting months past January rolls into previous year." + (should (equal '(2025 11 1) (calendar-sync--add-months '(2026 2 1) -3)))) + +(ert-deftest test-calendar-sync--add-months-boundary-zero () + "Adding zero months returns same date." + (should (equal '(2026 3 15) (calendar-sync--add-months '(2026 3 15) 0)))) + +;;; Error Cases + +(ert-deftest test-calendar-sync--add-months-error-large-offset () + "Adding 24 months (2 years) works correctly." + (should (equal '(2028 3 15) (calendar-sync--add-months '(2026 3 15) 24)))) + +(provide 'test-calendar-sync--add-months) +;;; test-calendar-sync--add-months.el ends here diff --git a/tests/test-calendar-sync--convert-utc-to-local.el b/tests/test-calendar-sync--convert-utc-to-local.el new file mode 100644 index 00000000..1a5be4d3 --- /dev/null +++ b/tests/test-calendar-sync--convert-utc-to-local.el @@ -0,0 +1,41 @@ +;;; test-calendar-sync--convert-utc-to-local.el --- Tests for UTC to local conversion -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for calendar-sync--convert-utc-to-local. +;; Converts UTC datetime to local timezone. Results depend on system timezone. + +;;; Code: + +(require 'ert) +(require 'testutil-calendar-sync) +(require 'calendar-sync) + +;;; Normal Cases + +(ert-deftest test-calendar-sync--convert-utc-to-local-normal-returns-5-elements () + "Conversion returns (year month day hour minute) list." + (let ((result (calendar-sync--convert-utc-to-local 2026 3 15 18 0 0))) + (should (= 5 (length result))) + (should (numberp (nth 0 result))) + (should (numberp (nth 3 result))))) + +(ert-deftest test-calendar-sync--convert-utc-to-local-normal-offset-applied () + "Hour differs from UTC input (unless system is UTC)." + (let* ((tz-offset (car (current-time-zone))) + (result (calendar-sync--convert-utc-to-local 2026 3 15 12 0 0))) + (if (= tz-offset 0) + (should (= 12 (nth 3 result))) + (should-not (= 12 (nth 3 result)))))) + +;;; Boundary Cases + +(ert-deftest test-calendar-sync--convert-utc-to-local-boundary-date-change () + "Late UTC time may shift to next day for western timezones." + (let* ((tz-offset (car (current-time-zone))) + (result (calendar-sync--convert-utc-to-local 2026 3 15 23 30 0))) + ;; For negative offsets, 23:30 UTC stays on same day + ;; For positive offsets > 30min, date rolls forward + (should (= 5 (length result))))) + +(provide 'test-calendar-sync--convert-utc-to-local) +;;; test-calendar-sync--convert-utc-to-local.el ends here diff --git a/tests/test-calendar-sync--date-weekday.el b/tests/test-calendar-sync--date-weekday.el new file mode 100644 index 00000000..61a499bd --- /dev/null +++ b/tests/test-calendar-sync--date-weekday.el @@ -0,0 +1,35 @@ +;;; test-calendar-sync--date-weekday.el --- Tests for date weekday calculation -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for calendar-sync--date-weekday. Returns 1 (Mon) through 7 (Sun). + +;;; Code: + +(require 'ert) +(require 'testutil-calendar-sync) +(require 'calendar-sync) + +;;; Normal Cases + +(ert-deftest test-calendar-sync--date-weekday-normal-known-date () + "2026-03-15 is a Sunday (7)." + (should (= 7 (calendar-sync--date-weekday '(2026 3 15))))) + +(ert-deftest test-calendar-sync--date-weekday-normal-monday () + "2026-03-16 is a Monday (1)." + (should (= 1 (calendar-sync--date-weekday '(2026 3 16))))) + +;;; Boundary Cases + +(ert-deftest test-calendar-sync--date-weekday-boundary-sunday-is-7 () + "Sunday returns 7, not 0 (Emacs decode-time returns 0 for Sunday)." + ;; 2026-01-04 is a Sunday + (should (= 7 (calendar-sync--date-weekday '(2026 1 4))))) + +(ert-deftest test-calendar-sync--date-weekday-boundary-saturday () + "Saturday returns 6." + ;; 2026-01-03 is a Saturday + (should (= 6 (calendar-sync--date-weekday '(2026 1 3))))) + +(provide 'test-calendar-sync--date-weekday) +;;; test-calendar-sync--date-weekday.el ends here diff --git a/tests/test-calendar-sync--event-start-time.el b/tests/test-calendar-sync--event-start-time.el new file mode 100644 index 00000000..1a9a5f7e --- /dev/null +++ b/tests/test-calendar-sync--event-start-time.el @@ -0,0 +1,41 @@ +;;; test-calendar-sync--event-start-time.el --- Tests for event start time extraction -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for calendar-sync--event-start-time. Extracts comparable time value from event plist. + +;;; Code: + +(require 'ert) +(require 'testutil-calendar-sync) +(require 'calendar-sync) + +;;; Normal Cases + +(ert-deftest test-calendar-sync--event-start-time-normal-timed () + "Timed event returns non-zero time value." + (let ((event (list :start '(2026 3 15 14 30) :summary "Test"))) + (should (not (equal 0 (calendar-sync--event-start-time event)))))) + +(ert-deftest test-calendar-sync--event-start-time-normal-ordering () + "Earlier event has smaller time value than later event." + (let ((early (list :start '(2026 3 15 9 0))) + (late (list :start '(2026 3 15 17 0)))) + (should (time-less-p (calendar-sync--event-start-time early) + (calendar-sync--event-start-time late))))) + +;;; Boundary Cases + +(ert-deftest test-calendar-sync--event-start-time-boundary-all-day () + "All-day event (nil hour/minute) uses 0 for time components." + (let ((event (list :start '(2026 3 15 nil nil)))) + (should (not (equal 0 (calendar-sync--event-start-time event)))))) + +;;; Error Cases + +(ert-deftest test-calendar-sync--event-start-time-error-no-start () + "Event without :start returns 0." + (let ((event (list :summary "No start"))) + (should (equal 0 (calendar-sync--event-start-time event))))) + +(provide 'test-calendar-sync--event-start-time) +;;; test-calendar-sync--event-start-time.el ends here diff --git a/tests/test-calendar-sync--format-timestamp.el b/tests/test-calendar-sync--format-timestamp.el new file mode 100644 index 00000000..5b8a6d02 --- /dev/null +++ b/tests/test-calendar-sync--format-timestamp.el @@ -0,0 +1,60 @@ +;;; test-calendar-sync--format-timestamp.el --- Tests for org timestamp formatting -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for calendar-sync--format-timestamp. +;; Converts parsed start/end times to org-mode timestamp strings. + +;;; Code: + +(require 'ert) +(require 'testutil-calendar-sync) +(require 'calendar-sync) + +;;; Normal Cases + +(ert-deftest test-calendar-sync--format-timestamp-normal-with-time () + "Timed event produces '<YYYY-MM-DD Day HH:MM-HH:MM>'." + (let ((result (calendar-sync--format-timestamp + '(2026 3 15 14 0) '(2026 3 15 15 30)))) + (should (string-match-p "^<2026-03-15 .* 14:00-15:30>$" result)))) + +(ert-deftest test-calendar-sync--format-timestamp-normal-all-day () + "All-day event (nil hours) produces '<YYYY-MM-DD Day>'." + (let ((result (calendar-sync--format-timestamp + '(2026 3 15 nil nil) '(2026 3 16 nil nil)))) + (should (string-match-p "^<2026-03-15 .*>$" result)) + (should-not (string-match-p "[0-9]:[0-9]" result)))) + +(ert-deftest test-calendar-sync--format-timestamp-normal-includes-weekday () + "Timestamp includes abbreviated weekday name." + (let ((result (calendar-sync--format-timestamp + '(2026 3 15 14 0) '(2026 3 15 15 0)))) + ;; 2026-03-15 is a Sunday + (should (string-match-p "Sun" result)))) + +;;; Boundary Cases + +(ert-deftest test-calendar-sync--format-timestamp-boundary-midnight () + "Midnight start and end are formatted as 00:00." + (let ((result (calendar-sync--format-timestamp + '(2026 1 1 0 0) '(2026 1 1 1 0)))) + (should (string-match-p "00:00-01:00" result)))) + +(ert-deftest test-calendar-sync--format-timestamp-boundary-nil-end () + "Nil end with timed start still produces time range." + (let ((result (calendar-sync--format-timestamp + '(2026 3 15 14 0) nil))) + ;; No end time means no time range + (should (string-match-p "^<2026-03-15" result)))) + +;;; Error Cases + +(ert-deftest test-calendar-sync--format-timestamp-error-start-no-time-end-has-time () + "All-day start with timed end produces date-only (no time range)." + (let ((result (calendar-sync--format-timestamp + '(2026 3 15 nil nil) '(2026 3 15 15 0)))) + ;; start-hour is nil so time-str should be nil + (should-not (string-match-p "[0-9][0-9]:[0-9][0-9]-" result)))) + +(provide 'test-calendar-sync--format-timestamp) +;;; test-calendar-sync--format-timestamp.el ends here diff --git a/tests/test-calendar-sync--format-timezone-offset.el b/tests/test-calendar-sync--format-timezone-offset.el new file mode 100644 index 00000000..cf2655d1 --- /dev/null +++ b/tests/test-calendar-sync--format-timezone-offset.el @@ -0,0 +1,44 @@ +;;; test-calendar-sync--format-timezone-offset.el --- Tests for timezone offset formatting -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for calendar-sync--format-timezone-offset. +;; Converts offset seconds to human-readable "UTC±H:MM" strings. + +;;; Code: + +(require 'ert) +(require 'testutil-calendar-sync) +(require 'calendar-sync) + +;;; Normal Cases + +(ert-deftest test-calendar-sync--format-timezone-offset-normal-negative () + "US Central (-6 hours) formats as UTC-6." + (should (equal "UTC-6" (calendar-sync--format-timezone-offset -21600)))) + +(ert-deftest test-calendar-sync--format-timezone-offset-normal-positive () + "India (+5:30) formats as UTC+5:30." + (should (equal "UTC+5:30" (calendar-sync--format-timezone-offset 19800)))) + +(ert-deftest test-calendar-sync--format-timezone-offset-normal-utc () + "Zero offset formats as UTC+0." + (should (equal "UTC+0" (calendar-sync--format-timezone-offset 0)))) + +;;; Boundary Cases + +(ert-deftest test-calendar-sync--format-timezone-offset-boundary-half-hour () + "Newfoundland (-3:30) includes minutes." + (should (equal "UTC-3:30" (calendar-sync--format-timezone-offset -12600)))) + +(ert-deftest test-calendar-sync--format-timezone-offset-boundary-large-offset () + "UTC+14 (Line Islands) formats correctly." + (should (equal "UTC+14" (calendar-sync--format-timezone-offset 50400)))) + +;;; Error Cases + +(ert-deftest test-calendar-sync--format-timezone-offset-error-nil () + "Nil offset returns 'unknown'." + (should (equal "unknown" (calendar-sync--format-timezone-offset nil)))) + +(provide 'test-calendar-sync--format-timezone-offset) +;;; test-calendar-sync--format-timezone-offset.el ends here diff --git a/tests/test-calendar-sync--localize-parsed-datetime.el b/tests/test-calendar-sync--localize-parsed-datetime.el new file mode 100644 index 00000000..ef9cf099 --- /dev/null +++ b/tests/test-calendar-sync--localize-parsed-datetime.el @@ -0,0 +1,54 @@ +;;; test-calendar-sync--localize-parsed-datetime.el --- Tests for datetime localization -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for calendar-sync--localize-parsed-datetime. +;; Dispatches to UTC or TZID conversion, or passes through unchanged. + +;;; Code: + +(require 'ert) +(require 'testutil-calendar-sync) +(require 'calendar-sync) + +;;; Normal Cases + +(ert-deftest test-calendar-sync--localize-parsed-datetime-normal-utc () + "UTC flag triggers conversion via convert-utc-to-local." + (let ((parsed '(2026 3 15 18 0))) + (let ((result (calendar-sync--localize-parsed-datetime parsed t nil))) + (should (= 5 (length result))) + ;; Hour should differ from 18 unless we're in UTC + (let ((tz-offset (car (current-time-zone)))) + (unless (= tz-offset 0) + (should-not (= 18 (nth 3 result)))))))) + +(ert-deftest test-calendar-sync--localize-parsed-datetime-normal-tzid () + "TZID triggers conversion via convert-tz-to-local." + (let ((parsed '(2026 3 15 14 0))) + (let ((result (calendar-sync--localize-parsed-datetime parsed nil "America/New_York"))) + (should (= 5 (length result)))))) + +(ert-deftest test-calendar-sync--localize-parsed-datetime-normal-local-passthrough () + "No UTC, no TZID — returns parsed unchanged." + (let ((parsed '(2026 3 15 14 0))) + (should (equal parsed (calendar-sync--localize-parsed-datetime parsed nil nil))))) + +;;; Boundary Cases + +(ert-deftest test-calendar-sync--localize-parsed-datetime-boundary-date-only () + "Date-only (nil hour/minute) with UTC uses 0 for time components." + (let ((parsed '(2026 3 15 nil nil))) + (let ((result (calendar-sync--localize-parsed-datetime parsed t nil))) + (should (= 5 (length result)))))) + +;;; Error Cases + +(ert-deftest test-calendar-sync--localize-parsed-datetime-error-bad-tzid-fallback () + "Invalid TZID falls back to returning parsed unchanged." + (let ((parsed '(2026 3 15 14 0))) + (let ((result (calendar-sync--localize-parsed-datetime parsed nil "Fake/Timezone"))) + ;; convert-tz-to-local returns nil for bad TZID, so fallback to parsed + (should (= 5 (length result)))))) + +(provide 'test-calendar-sync--localize-parsed-datetime) +;;; test-calendar-sync--localize-parsed-datetime.el ends here diff --git a/tests/test-calendar-sync--parse-event.el b/tests/test-calendar-sync--parse-event.el new file mode 100644 index 00000000..9c343db2 --- /dev/null +++ b/tests/test-calendar-sync--parse-event.el @@ -0,0 +1,82 @@ +;;; test-calendar-sync--parse-event.el --- Tests for VEVENT parser -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for calendar-sync--parse-event. +;; Parses VEVENT string into plist with :uid :summary :start :end etc. +;; Uses dynamic timestamps via testutil-calendar-sync helpers. + +;;; Code: + +(require 'ert) +(require 'testutil-calendar-sync) +(require 'calendar-sync) + +;;; Normal Cases + +(ert-deftest test-calendar-sync--parse-event-normal-basic-fields () + "Basic event returns plist with summary, start, end." + (let* ((start (test-calendar-sync-time-days-from-now 5 14 0)) + (end (test-calendar-sync-time-days-from-now 5 15 0)) + (vevent (test-calendar-sync-make-vevent "Team Standup" start end))) + (let ((result (calendar-sync--parse-event vevent))) + (should result) + (should (equal "Team Standup" (plist-get result :summary))) + (should (plist-get result :start)) + (should (plist-get result :end))))) + +(ert-deftest test-calendar-sync--parse-event-normal-optional-fields () + "Event with description and location includes them." + (let* ((start (test-calendar-sync-time-days-from-now 5 14 0)) + (end (test-calendar-sync-time-days-from-now 5 15 0)) + (vevent (test-calendar-sync-make-vevent + "Meeting" start end "Discuss roadmap" "Room 42"))) + (let ((result (calendar-sync--parse-event vevent))) + (should (equal "Discuss roadmap" (plist-get result :description))) + (should (equal "Room 42" (plist-get result :location)))))) + +(ert-deftest test-calendar-sync--parse-event-normal-all-day () + "All-day event (date-only) is parsed correctly." + (let* ((start (test-calendar-sync-time-date-only 5)) + (end (test-calendar-sync-time-date-only 6)) + (vevent (test-calendar-sync-make-vevent "Holiday" start end))) + (let ((result (calendar-sync--parse-event vevent))) + (should result) + (should (= 3 (length (plist-get result :start))))))) + +;;; Boundary Cases + +(ert-deftest test-calendar-sync--parse-event-boundary-recurrence-id-skipped () + "Events with RECURRENCE-ID are skipped (return nil)." + (let* ((start (test-calendar-sync-time-days-from-now 5 14 0)) + (vevent (concat "BEGIN:VEVENT\n" + "SUMMARY:Modified Instance\n" + "DTSTART:" (test-calendar-sync-ics-datetime start) "\n" + "RECURRENCE-ID:" (test-calendar-sync-ics-datetime start) "\n" + "END:VEVENT"))) + (should (null (calendar-sync--parse-event vevent))))) + +(ert-deftest test-calendar-sync--parse-event-boundary-no-end-time () + "Event without DTEND still parses (end is nil)." + (let* ((start (test-calendar-sync-time-days-from-now 5 14 0)) + (vevent (concat "BEGIN:VEVENT\n" + "SUMMARY:Open-ended\n" + "DTSTART:" (test-calendar-sync-ics-datetime start) "\n" + "END:VEVENT"))) + (let ((result (calendar-sync--parse-event vevent))) + (should result) + (should (null (plist-get result :end)))))) + +;;; Error Cases + +(ert-deftest test-calendar-sync--parse-event-error-no-summary () + "Event without SUMMARY returns nil." + (let ((vevent "BEGIN:VEVENT\nDTSTART:20260315T140000Z\nEND:VEVENT")) + (should (null (calendar-sync--parse-event vevent))))) + +(ert-deftest test-calendar-sync--parse-event-error-no-dtstart () + "Event without DTSTART returns nil." + (let ((vevent "BEGIN:VEVENT\nSUMMARY:Orphan\nEND:VEVENT")) + (should (null (calendar-sync--parse-event vevent))))) + +(provide 'test-calendar-sync--parse-event) +;;; test-calendar-sync--parse-event.el ends here diff --git a/tests/test-calendar-sync--parse-ics-datetime.el b/tests/test-calendar-sync--parse-ics-datetime.el new file mode 100644 index 00000000..6f1c083b --- /dev/null +++ b/tests/test-calendar-sync--parse-ics-datetime.el @@ -0,0 +1,53 @@ +;;; test-calendar-sync--parse-ics-datetime.el --- Tests for iCal datetime parser -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for calendar-sync--parse-ics-datetime. +;; Covers three formats: UTC datetime (Z suffix), local datetime, date-only. + +;;; Code: + +(require 'ert) +(require 'testutil-calendar-sync) +(require 'calendar-sync) + +;;; Normal Cases + +(ert-deftest test-calendar-sync--parse-ics-datetime-normal-utc () + "UTC datetime (Z suffix) returns (year month day hour minute)." + (let ((result (calendar-sync--parse-ics-datetime "20260203T090000Z"))) + (should (equal '(2026 2 3 9 0) result)))) + +(ert-deftest test-calendar-sync--parse-ics-datetime-normal-local () + "Local datetime (no Z) returns (year month day hour minute)." + (let ((result (calendar-sync--parse-ics-datetime "20260315T143000"))) + (should (equal '(2026 3 15 14 30) result)))) + +(ert-deftest test-calendar-sync--parse-ics-datetime-normal-date-only () + "Date-only returns (year month day nil nil)." + (let ((result (calendar-sync--parse-ics-datetime "20260203"))) + (should (equal '(2026 2 3 nil nil) result)))) + +;;; Boundary Cases + +(ert-deftest test-calendar-sync--parse-ics-datetime-boundary-midnight () + "Midnight is parsed as hour 0, minute 0." + (let ((result (calendar-sync--parse-ics-datetime "20260101T000000Z"))) + (should (equal '(2026 1 1 0 0) result)))) + +(ert-deftest test-calendar-sync--parse-ics-datetime-boundary-end-of-day () + "23:59 is parsed correctly." + (let ((result (calendar-sync--parse-ics-datetime "20261231T235900"))) + (should (equal '(2026 12 31 23 59) result)))) + +;;; Error Cases + +(ert-deftest test-calendar-sync--parse-ics-datetime-error-nil () + "Nil input returns nil." + (should (null (calendar-sync--parse-ics-datetime nil)))) + +(ert-deftest test-calendar-sync--parse-ics-datetime-error-empty-string () + "Empty string returns nil." + (should (null (calendar-sync--parse-ics-datetime "")))) + +(provide 'test-calendar-sync--parse-ics-datetime) +;;; test-calendar-sync--parse-ics-datetime.el ends here diff --git a/tests/test-calendar-sync--parse-timestamp.el b/tests/test-calendar-sync--parse-timestamp.el new file mode 100644 index 00000000..d05540f7 --- /dev/null +++ b/tests/test-calendar-sync--parse-timestamp.el @@ -0,0 +1,59 @@ +;;; test-calendar-sync--parse-timestamp.el --- Tests for timestamp parser -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for calendar-sync--parse-timestamp. +;; Handles UTC conversion, TZID conversion, local passthrough, and date-only. + +;;; Code: + +(require 'ert) +(require 'testutil-calendar-sync) +(require 'calendar-sync) + +;;; Normal Cases + +(ert-deftest test-calendar-sync--parse-timestamp-normal-local-no-tz () + "Local datetime without timezone returns raw values." + (let ((result (calendar-sync--parse-timestamp "20260315T143000"))) + (should (equal '(2026 3 15 14 30) result)))) + +(ert-deftest test-calendar-sync--parse-timestamp-normal-utc-converts () + "UTC datetime (Z suffix) is converted to local time." + (let ((result (calendar-sync--parse-timestamp "20260315T180000Z"))) + ;; Result should be local time — verify it's a valid 5-element list + (should (= 5 (length result))) + ;; The hour should differ from 18 unless we're in UTC + (should (numberp (nth 3 result))))) + +(ert-deftest test-calendar-sync--parse-timestamp-normal-date-only () + "Date-only returns 3-element list (year month day)." + (let ((result (calendar-sync--parse-timestamp "20260315"))) + (should (equal '(2026 3 15) result)))) + +;;; Boundary Cases + +(ert-deftest test-calendar-sync--parse-timestamp-boundary-with-tzid () + "TZID parameter triggers timezone conversion." + (let ((result (calendar-sync--parse-timestamp "20260315T140000" "America/New_York"))) + ;; Should return a 5-element list (converted from Eastern) + (should (= 5 (length result))) + (should (numberp (nth 0 result))))) + +(ert-deftest test-calendar-sync--parse-timestamp-boundary-midnight-utc () + "Midnight UTC converts correctly (may change date for western timezones)." + (let ((result (calendar-sync--parse-timestamp "20260315T000000Z"))) + (should (= 5 (length result))) + (should (numberp (nth 3 result))))) + +;;; Error Cases + +(ert-deftest test-calendar-sync--parse-timestamp-error-garbage () + "Non-datetime string returns nil." + (should (null (calendar-sync--parse-timestamp "not-a-date")))) + +(ert-deftest test-calendar-sync--parse-timestamp-error-partial () + "Truncated datetime returns nil." + (should (null (calendar-sync--parse-timestamp "2026031")))) + +(provide 'test-calendar-sync--parse-timestamp) +;;; test-calendar-sync--parse-timestamp.el ends here diff --git a/tests/test-calendar-sync--split-events.el b/tests/test-calendar-sync--split-events.el new file mode 100644 index 00000000..a5a957c3 --- /dev/null +++ b/tests/test-calendar-sync--split-events.el @@ -0,0 +1,54 @@ +;;; test-calendar-sync--split-events.el --- Tests for VEVENT block splitter -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for calendar-sync--split-events. +;; Splits raw .ics content into individual VEVENT block strings. + +;;; Code: + +(require 'ert) +(require 'testutil-calendar-sync) +(require 'calendar-sync) + +;;; Normal Cases + +(ert-deftest test-calendar-sync--split-events-normal-single () + "Single VEVENT returns one-element list." + (let* ((ics "BEGIN:VCALENDAR\nBEGIN:VEVENT\nSUMMARY:Test\nEND:VEVENT\nEND:VCALENDAR") + (result (calendar-sync--split-events ics))) + (should (= 1 (length result))) + (should (string-match-p "SUMMARY:Test" (car result))))) + +(ert-deftest test-calendar-sync--split-events-normal-multiple () + "Multiple VEVENTs return corresponding list." + (let* ((ics (concat "BEGIN:VCALENDAR\n" + "BEGIN:VEVENT\nSUMMARY:First\nEND:VEVENT\n" + "BEGIN:VEVENT\nSUMMARY:Second\nEND:VEVENT\n" + "END:VCALENDAR")) + (result (calendar-sync--split-events ics))) + (should (= 2 (length result))) + (should (string-match-p "First" (nth 0 result))) + (should (string-match-p "Second" (nth 1 result))))) + +;;; Boundary Cases + +(ert-deftest test-calendar-sync--split-events-boundary-empty-calendar () + "Calendar with no VEVENTs returns empty list." + (let ((ics "BEGIN:VCALENDAR\nVERSION:2.0\nEND:VCALENDAR")) + (should (null (calendar-sync--split-events ics))))) + +(ert-deftest test-calendar-sync--split-events-boundary-preserves-content () + "Each extracted block contains BEGIN:VEVENT through END:VEVENT." + (let* ((ics "BEGIN:VEVENT\nUID:abc\nSUMMARY:Test\nEND:VEVENT") + (result (calendar-sync--split-events ics))) + (should (string-prefix-p "BEGIN:VEVENT" (car result))) + (should (string-suffix-p "END:VEVENT" (car result))))) + +;;; Error Cases + +(ert-deftest test-calendar-sync--split-events-error-empty-string () + "Empty string returns empty list." + (should (null (calendar-sync--split-events "")))) + +(provide 'test-calendar-sync--split-events) +;;; test-calendar-sync--split-events.el ends here diff --git a/tests/test-calendar-sync--weekday-to-number.el b/tests/test-calendar-sync--weekday-to-number.el new file mode 100644 index 00000000..bcad7917 --- /dev/null +++ b/tests/test-calendar-sync--weekday-to-number.el @@ -0,0 +1,37 @@ +;;; test-calendar-sync--weekday-to-number.el --- Tests for weekday mapping -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for calendar-sync--weekday-to-number. Pure string→number mapping. + +;;; Code: + +(require 'ert) +(require 'testutil-calendar-sync) +(require 'calendar-sync) + +;;; Normal Cases + +(ert-deftest test-calendar-sync--weekday-to-number-normal-monday () + "MO maps to 1." + (should (= 1 (calendar-sync--weekday-to-number "MO")))) + +(ert-deftest test-calendar-sync--weekday-to-number-normal-all-days () + "All seven days map to 1-7." + (should (equal '(1 2 3 4 5 6 7) + (mapcar #'calendar-sync--weekday-to-number + '("MO" "TU" "WE" "TH" "FR" "SA" "SU"))))) + +;;; Boundary Cases + +(ert-deftest test-calendar-sync--weekday-to-number-boundary-sunday () + "SU maps to 7 (not 0)." + (should (= 7 (calendar-sync--weekday-to-number "SU")))) + +;;; Error Cases + +(ert-deftest test-calendar-sync--weekday-to-number-error-invalid () + "Invalid weekday string returns nil." + (should (null (calendar-sync--weekday-to-number "XX")))) + +(provide 'test-calendar-sync--weekday-to-number) +;;; test-calendar-sync--weekday-to-number.el ends here |
