diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test-calendar-sync--parse-exception-event.el | 22 | ||||
| -rw-r--r-- | tests/test-calendar-sync--syncing-p.el | 84 | ||||
| -rw-r--r-- | tests/test-integration-calendar-sync-recurrence-exceptions.el | 44 | ||||
| -rw-r--r-- | tests/test-markdown-config.el | 21 | ||||
| -rw-r--r-- | tests/test-prog-shell--make-script-executable.el | 14 | ||||
| -rw-r--r-- | tests/test-undead-buffers--save-some-buffers-override-no-conflict.el | 34 | ||||
| -rw-r--r-- | tests/test-undead-buffers-kill-all-other-buffers-and-windows.el | 17 |
7 files changed, 228 insertions, 8 deletions
diff --git a/tests/test-calendar-sync--parse-exception-event.el b/tests/test-calendar-sync--parse-exception-event.el index 1935d3ebb..a26a7418c 100644 --- a/tests/test-calendar-sync--parse-exception-event.el +++ b/tests/test-calendar-sync--parse-exception-event.el @@ -47,6 +47,28 @@ (event (test-calendar-sync-make-vevent "Regular Event" start end))) (should-not (calendar-sync--parse-exception-event event)))) +;;; Normal Cases — attendee extraction (singly-declined occurrence) + +(ert-deftest test-calendar-sync--parse-exception-event-extracts-attendees () + "Normal: a RECURRENCE-ID override carrying an ATTENDEE block parses :attendees, +so a singly-declined occurrence can have its user status re-derived downstream +by `calendar-sync--apply-single-exception'." + (let* ((start (test-calendar-sync-time-days-from-now 7 10 0)) + (end (test-calendar-sync-time-days-from-now 7 11 0)) + (event (concat "BEGIN:VEVENT\n" + "UID:override@google.com\n" + "RECURRENCE-ID:20260203T090000Z\n" + "SUMMARY:1:1 with Hayk\n" + "ATTENDEE;CN=Craig;PARTSTAT=DECLINED:mailto:craig@example.com\n" + "DTSTART:" (test-calendar-sync-ics-datetime start) "\n" + "DTEND:" (test-calendar-sync-ics-datetime end) "\n" + "END:VEVENT")) + (plist (calendar-sync--parse-exception-event event)) + (attendees (plist-get plist :attendees))) + (should attendees) + (should (equal "craig@example.com" (plist-get (car attendees) :email))) + (should (equal "DECLINED" (plist-get (car attendees) :partstat))))) + ;;; Error Cases (ert-deftest test-calendar-sync--parse-exception-event-error-unparseable-times () diff --git a/tests/test-calendar-sync--syncing-p.el b/tests/test-calendar-sync--syncing-p.el new file mode 100644 index 000000000..b346bf776 --- /dev/null +++ b/tests/test-calendar-sync--syncing-p.el @@ -0,0 +1,84 @@ +;;; test-calendar-sync--syncing-p.el --- Tests for the in-flight sync guard -*- lexical-binding: t; -*- + +;;; Commentary: +;; Unit tests for `calendar-sync--syncing-p' (the per-calendar in-flight check +;; that lets the dispatcher skip an overlapping timer tick) and for the +;; load-state sanitize that clears a stale `syncing' status in a fresh process. + +;;; Code: + +(require 'ert) +(require 'calendar-sync) + +(defun test-cs-syncing--reset () + "Clear the module's per-calendar state hash." + (clrhash calendar-sync--calendar-states)) + +;;; calendar-sync--syncing-p + +(ert-deftest test-calendar-sync--syncing-p-normal-true-when-syncing () + "Normal: a calendar whose status is `syncing' reads as in-flight." + (test-cs-syncing--reset) + (calendar-sync--set-calendar-state "google" '(:status syncing)) + (should (calendar-sync--syncing-p "google"))) + +(ert-deftest test-calendar-sync--syncing-p-boundary-nil-when-no-state () + "Boundary: a calendar with no recorded state is not in-flight." + (test-cs-syncing--reset) + (should-not (calendar-sync--syncing-p "never-seen"))) + +(ert-deftest test-calendar-sync--syncing-p-error-nil-for-terminal-status () + "Error: a terminal status (ok / error) is not in-flight." + (test-cs-syncing--reset) + (calendar-sync--set-calendar-state "google" '(:status ok)) + (should-not (calendar-sync--syncing-p "google")) + (calendar-sync--set-calendar-state "proton" '(:status error)) + (should-not (calendar-sync--syncing-p "proton"))) + +;;; Dispatcher guard: an in-flight calendar skips both leaf syncers + +(ert-deftest test-calendar-sync--sync-calendar-skips-when-in-flight () + "Normal: `calendar-sync--sync-calendar' does not launch a second sync for a +calendar already marked syncing, so an overlapping timer tick is a no-op." + (test-cs-syncing--reset) + (let ((api-calls '()) (ics-calls '())) + (cl-letf (((symbol-function 'calendar-sync--sync-calendar-api) + (lambda (cal) (push cal api-calls))) + ((symbol-function 'calendar-sync--sync-calendar-ics) + (lambda (cal) (push cal ics-calls)))) + (calendar-sync--set-calendar-state "proton" '(:status syncing)) + (calendar-sync--sync-calendar '(:name "proton" :url "https://x/y.ics" + :file "/tmp/c.org")) + (should (null api-calls)) + (should (null ics-calls))))) + +(ert-deftest test-calendar-sync--sync-calendar-dispatches-when-idle () + "Boundary: an idle calendar (no in-flight status) still dispatches normally." + (test-cs-syncing--reset) + (let ((ics-calls '())) + (cl-letf (((symbol-function 'calendar-sync--sync-calendar-ics) + (lambda (cal) (push cal ics-calls)))) + (calendar-sync--sync-calendar '(:name "proton" :url "https://x/y.ics" + :file "/tmp/c.org")) + (should (= 1 (length ics-calls)))))) + +;;; load-state sanitize: a persisted `syncing' status is cleared on load + +(ert-deftest test-calendar-sync--load-state-clears-stale-syncing () + "Error: a `syncing' status persisted before a crash is reset on load, so the +in-flight guard cannot skip that calendar forever in the new session." + (test-cs-syncing--reset) + (let* ((dir (make-temp-file "cs-state-" t)) + (calendar-sync--state-file (expand-file-name "state.el" dir))) + (unwind-protect + (progn + (with-temp-file calendar-sync--state-file + (prin1 '((timezone-offset . nil) + (calendar-states . (("google" . (:status syncing))))) + (current-buffer))) + (calendar-sync--load-state) + (should-not (calendar-sync--syncing-p "google"))) + (delete-directory dir t)))) + +(provide 'test-calendar-sync--syncing-p) +;;; test-calendar-sync--syncing-p.el ends here diff --git a/tests/test-integration-calendar-sync-recurrence-exceptions.el b/tests/test-integration-calendar-sync-recurrence-exceptions.el index 0a9b5af1f..dde0603a2 100644 --- a/tests/test-integration-calendar-sync-recurrence-exceptions.el +++ b/tests/test-integration-calendar-sync-recurrence-exceptions.el @@ -162,5 +162,49 @@ Weekly meeting with one instance rescheduled from 09:00 to 10:00." (should (string-match-p "10:00" org-output)) (should (string-match-p "15:00" org-output)))) +(ert-deftest test-integration-declined-single-occurrence-is-dropped () + "A recurring event with one occurrence declined via a RECURRENCE-ID override +is filtered out end-to-end, while the other occurrences survive. + +This is the singly-declined-occurrence case: declining one instance of a series +in Google Calendar emits a RECURRENCE-ID override carrying the user's +PARTSTAT=DECLINED. The override must carry its attendee block all the way from +`calendar-sync--parse-exception-event' through +`calendar-sync--apply-single-exception' (status re-derivation) to +`calendar-sync--filter-declined' for the drop to happen." + (let* ((calendar-sync-skip-declined t) + (calendar-sync-user-emails '("craig@example.com")) + (base-start (test-calendar-sync-time-days-from-now 0 9 0)) + (base-end (test-calendar-sync-time-days-from-now 0 10 0)) + (rec-id (test-calendar-sync-time-days-from-now 7 9 0)) + (decl-start (test-calendar-sync-time-days-from-now 7 9 0)) + (decl-end (test-calendar-sync-time-days-from-now 7 10 0)) + (ics-content + (concat "BEGIN:VCALENDAR\n" + "VERSION:2.0\n" + "BEGIN:VEVENT\n" + "UID:1on1@google.com\n" + "SUMMARY:1on1 with Hayk\n" + "DTSTART:" (test-calendar-sync-ics-datetime-local base-start) "\n" + "DTEND:" (test-calendar-sync-ics-datetime-local base-end) "\n" + "RRULE:FREQ=WEEKLY;COUNT=4\n" + "END:VEVENT\n" + ;; Week 2 declined: RECURRENCE-ID override with PARTSTAT=DECLINED + "BEGIN:VEVENT\n" + "UID:1on1@google.com\n" + "RECURRENCE-ID:" (test-calendar-sync-ics-datetime-local rec-id) "\n" + "SUMMARY:1on1 with Hayk DECLINEDWEEK\n" + "ATTENDEE;CN=Craig;PARTSTAT=DECLINED:mailto:craig@example.com\n" + "DTSTART:" (test-calendar-sync-ics-datetime-local decl-start) "\n" + "DTEND:" (test-calendar-sync-ics-datetime-local decl-end) "\n" + "END:VEVENT\n" + "END:VCALENDAR")) + (org-output (calendar-sync--parse-ics ics-content))) + (should (stringp org-output)) + ;; The non-declined occurrences survive. + (should (string-match-p "1on1 with Hayk" org-output)) + ;; The declined occurrence (unique marker) is dropped. + (should-not (string-match-p "DECLINEDWEEK" org-output)))) + (provide 'test-integration-calendar-sync-recurrence-exceptions) ;;; test-integration-calendar-sync-recurrence-exceptions.el ends here diff --git a/tests/test-markdown-config.el b/tests/test-markdown-config.el index edb20d357..e079a8b44 100644 --- a/tests/test-markdown-config.el +++ b/tests/test-markdown-config.el @@ -37,10 +37,29 @@ (let ((html (buffer-string))) (should (string-match-p "<!DOCTYPE html>" html)) (should (string-match-p "<xmp" html)) - (should (string-match-p "strapdown\\.js" html)) (should (string-match-p "some \\*\\*markdown\\*\\*" html))))) (kill-buffer src)))) +(ert-deftest test-markdown-html-vendors-strapdown-no-external-cdn () + "Normal: the preview embeds the vendored strapdown inline and references no +external CDN, so the preview works offline and doesn't load third-party JS over +plain HTTP." + (let ((src (generate-new-buffer " *md-cdn*"))) + (unwind-protect + (progn + (with-current-buffer src (insert "# Hello")) + (with-temp-buffer + (cj/markdown-html src) + (let ((html (buffer-string))) + ;; No external CDN of any kind. + (should-not (string-match-p "ndossougbe" html)) + (should-not (string-match-p "src=\"https?://" html)) + ;; Vendored strapdown is embedded inline (a bare <script> with the + ;; ~121KB bundle, not a <script src=...>). + (should (string-match-p "<script>" html)) + (should (> (length html) 100000))))) + (kill-buffer src)))) + (ert-deftest test-markdown-html-empty-source-buffer () "Boundary: an empty source buffer still yields the HTML shell." (let ((src (generate-new-buffer " *md-empty*"))) diff --git a/tests/test-prog-shell--make-script-executable.el b/tests/test-prog-shell--make-script-executable.el index e2bb0e6de..0f220b19b 100644 --- a/tests/test-prog-shell--make-script-executable.el +++ b/tests/test-prog-shell--make-script-executable.el @@ -106,6 +106,20 @@ (kill-buffer)) (delete-file temp-file)))) +(ert-deftest test-make-script-executable-non-prog-mode-skipped () + "Boundary: a shebang file visited in a non-prog-mode buffer (a script being +read, quoted, or reviewed) is NOT silently made executable. The auto-exec hook +runs on every save globally, so it must only act on actual script buffers." + (let ((temp-file (test--create-temp-script "#!/bin/bash\necho hello"))) + (unwind-protect + (with-current-buffer (find-file-noselect temp-file) + (text-mode) + (should-not (test--file-executable-p temp-file)) + (cj/make-script-executable) + (should-not (test--file-executable-p temp-file)) + (kill-buffer)) + (delete-file temp-file)))) + ;;; Edge Cases (ert-deftest test-make-script-executable-edge-no-buffer-file () diff --git a/tests/test-undead-buffers--save-some-buffers-override-no-conflict.el b/tests/test-undead-buffers--save-some-buffers-override-no-conflict.el new file mode 100644 index 000000000..c0dca6e0a --- /dev/null +++ b/tests/test-undead-buffers--save-some-buffers-override-no-conflict.el @@ -0,0 +1,34 @@ +;;; test-undead-buffers--save-some-buffers-override-no-conflict.el --- Regression: save override vs undead -*- lexical-binding: t; -*- + +;;; Commentary: +;; Regression guard for a launch crash. custom-buffer-file.el installs a +;; legible `save-some-buffers' override named `cj/save-some-buffers' (arity +;; arg + pred). undead-buffers.el used to define its own 1-arg +;; `cj/save-some-buffers' that called `save-some-buffers' internally. In prod +;; load order (custom-buffer-file before undead-buffers) the 1-arg version won +;; the symbol, so the override re-entered it with two args and signalled +;; wrong-number-of-arguments — crashing the kill-all-other-buffers startup path. +;; +;; The requires below reproduce that prod order on purpose. + +;;; Code: + +(require 'ert) +;; Prod order: override installed first, undead-buffers loaded second. +(require 'custom-buffer-file) +(require 'undead-buffers) + +(ert-deftest test-undead-buffers-save-override-accepts-predicate () + "Normal: calling `save-some-buffers' with the undead predicate (the +kill-all-other-buffers path) goes through the legible override without a +wrong-number-of-arguments crash. No modified file buffers exist in batch, so +the override returns a count rather than prompting." + (should (numberp (save-some-buffers nil #'cj/undead-buffer-p)))) + +(ert-deftest test-undead-buffers-save-some-buffers-not-arity-shadowed () + "Boundary: `cj/save-some-buffers' must accept the PRED argument the override +forwards (max arity >= 2), so undead-buffers can't reintroduce a 1-arg shadow." + (should (>= (cdr (func-arity #'cj/save-some-buffers)) 2))) + +(provide 'test-undead-buffers--save-some-buffers-override-no-conflict) +;;; test-undead-buffers--save-some-buffers-override-no-conflict.el ends here diff --git a/tests/test-undead-buffers-kill-all-other-buffers-and-windows.el b/tests/test-undead-buffers-kill-all-other-buffers-and-windows.el index dcd08e966..36d82add0 100644 --- a/tests/test-undead-buffers-kill-all-other-buffers-and-windows.el +++ b/tests/test-undead-buffers-kill-all-other-buffers-and-windows.el @@ -133,23 +133,26 @@ (test-kill-all-other-buffers-and-windows-teardown))) (ert-deftest test-kill-all-other-buffers-and-windows-should-prompt-for-modified-buffers () - "Should call cj/save-some-buffers to handle modified buffers." + "Should call save-some-buffers with the undead predicate to handle modified buffers." (test-kill-all-other-buffers-and-windows-setup) (unwind-protect (let ((main (current-buffer)) (file (cj/create-temp-test-file-with-content "original")) - save-called) - ;; Mock cj/save-some-buffers to track if it's called - (cl-letf (((symbol-function 'cj/save-some-buffers) - (lambda (&optional arg) - (setq save-called t)))) + save-called save-pred) + ;; Mock save-some-buffers (the standard API the override hooks) to track + ;; the call and the predicate passed. + (cl-letf (((symbol-function 'save-some-buffers) + (lambda (&optional _arg pred &rest _) + (setq save-called t + save-pred pred)))) (let ((buf (find-file-noselect file))) (unwind-protect (progn (with-current-buffer buf (insert "modified")) (cj/kill-all-other-buffers-and-windows) - (should save-called)) + (should save-called) + (should (eq save-pred #'cj/undead-buffer-p))) (when (buffer-live-p buf) (set-buffer-modified-p nil) (kill-buffer buf)))))) |
