diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test-org-capture-config-popup-window.el | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/tests/test-org-capture-config-popup-window.el b/tests/test-org-capture-config-popup-window.el new file mode 100644 index 00000000..34f67b36 --- /dev/null +++ b/tests/test-org-capture-config-popup-window.el @@ -0,0 +1,281 @@ +;;; test-org-capture-config-popup-window.el --- Quick-capture popup single-window tests -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the pure predicate behind the quick-capture popup single-window +;; fix. The Hyprland Super+Shift+N popup opens an emacsclient frame named +;; "org-capture"; in that frame the *Org Select* template menu and the +;; CAPTURE-* buffer must fill the frame's sole window instead of splitting it. +;; `cj/org-capture--popup-sole-window-p' is the frame+buffer decision; the +;; display-buffer action that acts on it is exercised by hand (window ops), +;; not here. + +;;; Code: + +(require 'ert) +(require 'cl-lib) +(require 'org) +(require 'org-capture) ; makes `org-capture-templates' a real special var +(require 'user-constants) +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'org-capture-config) + +(defconst test-org-capture-popup--sample-templates + '(("t" "Task" entry (function cj/--org-capture-project-location) + "* TODO %?" :prepend t) + ("b" "Bug" entry (function cj/--org-capture-project-location) + "* TODO [#C] %?" :prepend t) + ("e" "Event" entry (file+headline schedule-file "Scheduled Events") + "* %?" :prepend t :prepare-finalize cj/org-capture-format-event-headline) + ("m" "Mu4e Email" entry (file+headline inbox-file "Inbox") "* TODO %?" :prepend t) + ("L" "Link" entry (file+headline inbox-file "Inbox") "* %?" :immediate-finish t) + ("d" "Drill Question" entry (file ignore) "* Item :drill:\n%?" :prepend t)) + "A representative org-capture-templates list for popup-subset tests.") + +;;; cj/org-capture--popup-sole-window-p + +(ert-deftest test-org-capture-config-popup-sole-window-p-select-menu () + "Normal: the *Org Select* menu in the popup frame wants the sole window." + (should (cj/org-capture--popup-sole-window-p "org-capture" "*Org Select*"))) + +(ert-deftest test-org-capture-config-popup-sole-window-p-capture-buffer () + "Normal: a CAPTURE-* buffer in the popup frame wants the sole window." + (should (cj/org-capture--popup-sole-window-p "org-capture" "CAPTURE-todo.org"))) + +(ert-deftest test-org-capture-config-popup-sole-window-p-capture-prefix-only () + "Boundary: the bare \"CAPTURE-\" prefix still matches." + (should (cj/org-capture--popup-sole-window-p "org-capture" "CAPTURE-"))) + +(ert-deftest test-org-capture-config-popup-sole-window-p-other-frame () + "Boundary: the same menu in a normal frame is left alone." + (should-not (cj/org-capture--popup-sole-window-p "emacs" "*Org Select*")) + (should-not (cj/org-capture--popup-sole-window-p nil "CAPTURE-todo.org"))) + +(ert-deftest test-org-capture-config-popup-sole-window-p-other-buffer () + "Boundary: an unrelated buffer in the popup frame is left alone." + (should-not (cj/org-capture--popup-sole-window-p "org-capture" "todo.org")) + (should-not (cj/org-capture--popup-sole-window-p "org-capture" "*scratch*"))) + +(ert-deftest test-org-capture-config-popup-sole-window-p-nil-buffer () + "Error: a nil or non-string buffer name returns nil without raising." + (should-not (cj/org-capture--popup-sole-window-p "org-capture" nil)) + (should-not (cj/org-capture--popup-sole-window-p "org-capture" 42))) + +;;; Integration: the display-buffer-alist entry routes to a sole window + +(ert-deftest test-integration-org-capture-popup-display-sole-window () + "Integration: in an \"org-capture\"-named frame, displaying a CAPTURE-* +buffer fills the frame's sole window via the registered display-buffer-alist +entry, instead of splitting. + +Components integrated: +- cj/org-capture--popup-display-condition (real) +- cj/org-capture--display-sole-window (real) +- display-buffer / display-buffer-alist (real) + +Validates the popup frame ends with one window showing the CAPTURE buffer." + ;; The batch frame is auto-named (\"F1\"), which cannot be restored by name + ;; (\"F<num> usurped by Emacs\"); reset to nil to return it to auto-naming, + ;; keeping the test independent of execution order. + (let ((buf (get-buffer-create "CAPTURE-itest"))) + (unwind-protect + (progn + (set-frame-parameter nil 'name "org-capture") + (delete-other-windows) + (display-buffer buf) + (should (= (length (window-list)) 1)) + (should (eq (window-buffer (selected-window)) buf))) + (set-frame-parameter nil 'name nil) + (when (buffer-live-p buf) (kill-buffer buf))))) + +;;; cj/--org-capture-popup-templates (pure subset/retarget) + +(ert-deftest test-org-capture-config-popup-templates-keeps-tbe () + "Normal: only Task, Bug, Event survive, preserving order." + (should (equal (mapcar #'car (cj/--org-capture-popup-templates + test-org-capture-popup--sample-templates "/inbox.org")) + '("t" "b" "e")))) + +(ert-deftest test-org-capture-config-popup-templates-retargets-task-bug () + "Normal: Task and Bug retarget to the inbox \"Inbox\" headline; body + props kept." + (let* ((result (cj/--org-capture-popup-templates + test-org-capture-popup--sample-templates "/inbox.org")) + (task (assoc "t" result)) + (bug (assoc "b" result))) + (should (equal (nth 3 task) '(file+headline "/inbox.org" "Inbox"))) + (should (equal (nth 3 bug) '(file+headline "/inbox.org" "Inbox"))) + (should (equal (nth 4 task) "* TODO %?")) + (should (equal (nth 4 bug) "* TODO [#C] %?")) + (should (memq :prepend task)))) + +(ert-deftest test-org-capture-config-popup-templates-event-unchanged () + "Boundary: Event passes through untouched, schedule-file target and props intact." + (let ((event (assoc "e" (cj/--org-capture-popup-templates + test-org-capture-popup--sample-templates "/inbox.org")))) + (should (equal (nth 3 event) '(file+headline schedule-file "Scheduled Events"))) + (should (memq :prepare-finalize event)))) + +(ert-deftest test-org-capture-config-popup-templates-drops-context-templates () + "Boundary: context-dependent templates (mu4e, link, drill) are dropped." + (let ((result (cj/--org-capture-popup-templates + test-org-capture-popup--sample-templates "/inbox.org"))) + (should-not (assoc "m" result)) + (should-not (assoc "L" result)) + (should-not (assoc "d" result)))) + +(ert-deftest test-org-capture-config-popup-templates-empty () + "Error/Boundary: empty or all-dropped input yields nil without raising." + (should-not (cj/--org-capture-popup-templates nil "/inbox.org")) + (should-not (cj/--org-capture-popup-templates + '(("L" "Link" entry (file+headline f "Inbox") "* %?")) "/inbox.org"))) + +;;; cj/quick-capture (binds the subset; integration with a stubbed org-capture) + +(ert-deftest test-integration-org-capture-quick-capture-binds-subset () + "Integration: cj/quick-capture runs org-capture with only Task/Bug/Event, +Task and Bug retargeted to the inbox. + +Components integrated: +- cj/quick-capture (real) +- cj/--org-capture-popup-templates (real) +- org-capture (MOCKED — records the dynamically-bound templates)" + (let ((org-capture-templates test-org-capture-popup--sample-templates) + captured) + (cl-letf (((symbol-function 'org-capture) + (lambda (&rest _) (setq captured org-capture-templates)))) + (cj/quick-capture)) + (should (equal (mapcar #'car captured) '("t" "b" "e"))) + (should (equal (nth 3 (assoc "t" captured)) (list 'file+headline inbox-file "Inbox"))) + (should (equal (nth 3 (assoc "b" captured)) (list 'file+headline inbox-file "Inbox"))))) + +(ert-deftest test-integration-org-capture-quick-capture-closes-frame-on-abort () + "Integration: when selection aborts (org-capture signals), cj/quick-capture +deletes the popup frame instead of leaving it orphaned. + +Components integrated: +- cj/quick-capture (real) +- org-capture (MOCKED — signals user-error \"Abort\") +- cj/org-capture--delete-popup-frame (MOCKED — records the call)" + (let ((org-capture-templates test-org-capture-popup--sample-templates) + (deleted 0)) + (cl-letf (((symbol-function 'org-capture) + (lambda (&rest _) (user-error "Abort"))) + ((symbol-function 'cj/org-capture--delete-popup-frame) + (lambda () (cl-incf deleted)))) + (cj/quick-capture)) + (should (= deleted 1)))) + +(ert-deftest test-integration-org-capture-quick-capture-closes-frame-on-quit () + "Integration: a C-g (quit) during capture also closes the popup frame." + (let ((org-capture-templates test-org-capture-popup--sample-templates) + (deleted 0)) + (cl-letf (((symbol-function 'org-capture) + (lambda (&rest _) (signal 'quit nil))) + ((symbol-function 'cj/org-capture--delete-popup-frame) + (lambda () (cl-incf deleted)))) + (cj/quick-capture)) + (should (= deleted 1)))) + +(ert-deftest test-integration-org-capture-quick-capture-keeps-frame-on-success () + "Integration: a successful capture (no signal) does NOT delete the frame — +the finalize hook owns that." + (let ((org-capture-templates test-org-capture-popup--sample-templates) + (deleted 0)) + (cl-letf (((symbol-function 'org-capture) (lambda (&rest _) nil)) + ((symbol-function 'cj/org-capture--delete-popup-frame) + (lambda () (cl-incf deleted)))) + (cj/quick-capture)) + (should (= deleted 0)))) + +;;; cj/--org-capture-popup-strip-specials (drop the Customize menu entry) + +(ert-deftest test-org-capture-config-popup-strip-specials-removes-customize () + "Normal: the \"C\" Customize entry is removed, \"q\" Abort kept, order intact." + (should (equal (cj/--org-capture-popup-strip-specials + '(("C" "Customize org-capture-templates") ("q" "Abort"))) + '(("q" "Abort"))))) + +(ert-deftest test-org-capture-config-popup-strip-specials-no-customize () + "Boundary: specials without a \"C\" entry pass through unchanged." + (should (equal (cj/--org-capture-popup-strip-specials '(("q" "Abort"))) + '(("q" "Abort"))))) + +(ert-deftest test-org-capture-config-popup-strip-specials-empty () + "Error/Boundary: nil specials yields nil without raising." + (should-not (cj/--org-capture-popup-strip-specials nil))) + +;;; cj/org-capture--popup-frame-p + +(ert-deftest test-org-capture-config-popup-frame-p () + "Normal/Boundary: true only when the selected frame is named \"org-capture\"." + (cl-letf (((symbol-function 'frame-parameter) (lambda (&rest _) "org-capture"))) + (should (cj/org-capture--popup-frame-p))) + (cl-letf (((symbol-function 'frame-parameter) (lambda (&rest _) "emacs"))) + (should-not (cj/org-capture--popup-frame-p)))) + +;;; cj/org-capture--popup-mks-advice (frame-gated specials stripping) + +(ert-deftest test-org-capture-config-popup-mks-advice-strips-in-popup () + "Integration: in the popup frame, org-mks receives specials without \"C\"." + (let (seen) + (cl-letf (((symbol-function 'cj/org-capture--popup-frame-p) (lambda () t))) + (cj/org-capture--popup-mks-advice + (lambda (_table _title _prompt specials) (setq seen specials)) + nil nil nil '(("C" "Customize org-capture-templates") ("q" "Abort")))) + (should (equal seen '(("q" "Abort")))))) + +(ert-deftest test-org-capture-config-popup-mks-advice-keeps-elsewhere () + "Integration: in a normal frame, org-mks receives the specials untouched." + (let (seen) + (cl-letf (((symbol-function 'cj/org-capture--popup-frame-p) (lambda () nil))) + (cj/org-capture--popup-mks-advice + (lambda (_table _title _prompt specials) (setq seen specials)) + nil nil nil '(("C" "Customize org-capture-templates") ("q" "Abort")))) + (should (equal seen '(("C" "Customize org-capture-templates") ("q" "Abort")))))) + +;;; cj/org-capture--popup-frame (find the popup frame by name) + +(ert-deftest test-org-capture-config-popup-frame-found () + "Normal: returns the live frame whose name is \"org-capture\"." + (cl-letf (((symbol-function 'frame-list) (lambda () '(fa fb fc))) + ((symbol-function 'frame-live-p) (lambda (_f) t)) + ((symbol-function 'frame-parameter) + (lambda (f _p) (if (eq f 'fb) "org-capture" "other")))) + (should (eq (cj/org-capture--popup-frame) 'fb)))) + +(ert-deftest test-org-capture-config-popup-frame-none () + "Boundary: no popup frame present yields nil." + (cl-letf (((symbol-function 'frame-list) (lambda () '(fa fc))) + ((symbol-function 'frame-live-p) (lambda (_f) t)) + ((symbol-function 'frame-parameter) (lambda (_f _p) "other"))) + (should-not (cj/org-capture--popup-frame)))) + +;;; cj/quick-capture targets the popup frame + +(ert-deftest test-integration-org-capture-quick-capture-selects-named-frame () + "Integration: cj/quick-capture selects the \"org-capture\" frame found by name, +not whatever frame happens to be selected (the emacsclient -c focus race)." + (let ((org-capture-templates test-org-capture-popup--sample-templates) + (focused nil)) + (cl-letf (((symbol-function 'cj/org-capture--popup-frame) (lambda () 'popup-frame)) + ((symbol-function 'select-frame-set-input-focus) + (lambda (f) (setq focused f))) + ((symbol-function 'org-capture) (lambda (&rest _) nil))) + (cj/quick-capture)) + (should (eq focused 'popup-frame)))) + +(ert-deftest test-integration-org-capture-quick-capture-no-frame-still-captures () + "Integration: when no popup frame is found, cj/quick-capture skips the focus +call and still runs the capture (no error)." + (let ((org-capture-templates test-org-capture-popup--sample-templates) + (focused 'unset) + (captured nil)) + (cl-letf (((symbol-function 'cj/org-capture--popup-frame) (lambda () nil)) + ((symbol-function 'select-frame-set-input-focus) + (lambda (f) (setq focused f))) + ((symbol-function 'org-capture) (lambda (&rest _) (setq captured t)))) + (cj/quick-capture)) + (should (eq focused 'unset)) + (should captured))) + +(provide 'test-org-capture-config-popup-window) +;;; test-org-capture-config-popup-window.el ends here |
