diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test-ai-term--capture-state.el | 6 | ||||
| -rw-r--r-- | tests/test-ai-term--f9-in-term.el | 18 | ||||
| -rw-r--r-- | tests/test-ai-term--next-agent-buffer.el | 73 | ||||
| -rw-r--r-- | tests/test-ai-term--reuse-edge-window.el | 41 | ||||
| -rw-r--r-- | tests/test-calendar-sync--parse-exception-event.el | 64 | ||||
| -rw-r--r-- | tests/test-cj-window-geometry-lib.el | 20 | ||||
| -rw-r--r-- | tests/test-cj-window-toggle-lib.el | 13 | ||||
| -rw-r--r-- | tests/test-dirvish-config-playlist.el | 55 | ||||
| -rw-r--r-- | tests/test-dwim-shell-config-command-fixes.el | 55 | ||||
| -rw-r--r-- | tests/test-init-defer-games.el | 38 | ||||
| -rw-r--r-- | tests/test-system-defaults-functions.el | 14 | ||||
| -rw-r--r-- | tests/test-system-defaults.el | 13 | ||||
| -rw-r--r-- | tests/test-term-tmux-history.el | 17 | ||||
| -rw-r--r-- | tests/test-term-toggle--display.el | 13 |
14 files changed, 376 insertions, 64 deletions
diff --git a/tests/test-ai-term--capture-state.el b/tests/test-ai-term--capture-state.el index 543f83ad7..aa7421350 100644 --- a/tests/test-ai-term--capture-state.el +++ b/tests/test-ai-term--capture-state.el @@ -27,7 +27,9 @@ (should (= cj/--ai-term-last-size (window-body-width right)))))) (ert-deftest test-ai-term--capture-state-below-split-sets-direction () - "Normal: below-split window -> direction=below, integer body-lines matching window." + "Normal: below-split window -> direction=below, integer total-lines matching window. +The vertical axis captures total-height (not body-height) so the toggle +round-trip is immune to the mode line's pixel height." (save-window-excursion (delete-other-windows) (let ((below (split-window (selected-window) nil 'below)) @@ -36,7 +38,7 @@ (cj/--ai-term-capture-state below) (should (eq cj/--ai-term-last-direction 'below)) (should (integerp cj/--ai-term-last-size)) - (should (= cj/--ai-term-last-size (window-body-height below)))))) + (should (= cj/--ai-term-last-size (window-total-height below)))))) (ert-deftest test-ai-term--capture-state-noop-on-dead-window () "Boundary: nil window -> state remains unchanged." diff --git a/tests/test-ai-term--f9-in-term.el b/tests/test-ai-term--f9-in-term.el index dad11ffc0..0477f2517 100644 --- a/tests/test-ai-term--f9-in-term.el +++ b/tests/test-ai-term--f9-in-term.el @@ -26,27 +26,29 @@ (should (eq (keymap-lookup ghostel-mode-map "<f9>") #'cj/ai-term))) (ert-deftest test-ai-term-f9-family-bound-in-ghostel-mode-map () - "Normal: the C-/M-/C-S- F9 variants are bound in `ghostel-mode-map' too. -`M-<f9>' and `C-S-<f9>' both close an agent via `cj/ai-term-close'." + "Normal: the C-/s-/M- F9 variants are bound in `ghostel-mode-map' too. +`s-<f9>' steps to the next agent; `M-<f9>' closes an agent via +`cj/ai-term-close'." (should (eq (keymap-lookup ghostel-mode-map "C-<f9>") #'cj/ai-term-pick-project)) - (should (eq (keymap-lookup ghostel-mode-map "M-<f9>") #'cj/ai-term-close)) - (should (eq (keymap-lookup ghostel-mode-map "C-S-<f9>") #'cj/ai-term-close))) + (should (eq (keymap-lookup ghostel-mode-map "s-<f9>") #'cj/ai-term-next)) + (should (eq (keymap-lookup ghostel-mode-map "M-<f9>") #'cj/ai-term-close))) (ert-deftest test-ai-term-f9-still-bound-globally () "Normal: the global F9 family bindings are intact. `<f9>' toggles the ai-term agent window; `C-<f9>' picks a project -agent; `M-<f9>' and `C-S-<f9>' close an agent via `cj/ai-term-close'." +agent; `s-<f9>' steps to the next agent; `M-<f9>' closes an agent +via `cj/ai-term-close'." (should (eq (lookup-key (current-global-map) (kbd "<f9>")) #'cj/ai-term)) (should (eq (lookup-key (current-global-map) (kbd "C-<f9>")) #'cj/ai-term-pick-project)) - (should (eq (lookup-key (current-global-map) (kbd "M-<f9>")) #'cj/ai-term-close)) - (should (eq (lookup-key (current-global-map) (kbd "C-S-<f9>")) #'cj/ai-term-close))) + (should (eq (lookup-key (current-global-map) (kbd "s-<f9>")) #'cj/ai-term-next)) + (should (eq (lookup-key (current-global-map) (kbd "M-<f9>")) #'cj/ai-term-close))) (ert-deftest test-ai-term-f9-family-in-keymap-exceptions () "Regression: the F9 family is in `ghostel-keymap-exceptions' so semi-char mode lets it reach Emacs instead of forwarding it to the terminal program. Binding in `ghostel-mode-map' alone is not enough -- the semi-char map outranks it and forwards any key not in the exceptions to the pty." - (dolist (key '("<f9>" "C-<f9>" "M-<f9>" "C-S-<f9>")) + (dolist (key '("<f9>" "C-<f9>" "s-<f9>" "M-<f9>")) (should (member key ghostel-keymap-exceptions))) ;; The rebuilt semi-char map must no longer forward <f9> to the pty. (should-not (eq (keymap-lookup ghostel-semi-char-mode-map "<f9>") diff --git a/tests/test-ai-term--next-agent-buffer.el b/tests/test-ai-term--next-agent-buffer.el new file mode 100644 index 000000000..330714a92 --- /dev/null +++ b/tests/test-ai-term--next-agent-buffer.el @@ -0,0 +1,73 @@ +;;; test-ai-term--next-agent-buffer.el --- Tests for cj/--ai-term-next-agent-buffer -*- lexical-binding: t; -*- + +;;; Commentary: +;; The pure decision helper behind `cj/ai-term-next' (s-F9). Given the +;; current agent buffer and the ordered list of live agent buffers, it +;; returns the next buffer in the queue, wrapping after the last. A nil +;; or non-member CURRENT returns the first; an empty list returns nil. +;; No buffer or window side effects -- list logic only. + +;;; Code: + +(require 'ert) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'ai-term) + +(ert-deftest test-ai-term--next-agent-buffer-advances-from-first () + "Normal: current is the first element -> returns the second." + (let ((a (get-buffer-create "agent [a]")) + (b (get-buffer-create "agent [b]")) + (c (get-buffer-create "agent [c]"))) + (unwind-protect + (should (eq b (cj/--ai-term-next-agent-buffer a (list a b c)))) + (mapc #'kill-buffer (list a b c))))) + +(ert-deftest test-ai-term--next-agent-buffer-advances-from-middle () + "Normal: current in the middle -> returns the following element." + (let ((a (get-buffer-create "agent [a]")) + (b (get-buffer-create "agent [b]")) + (c (get-buffer-create "agent [c]"))) + (unwind-protect + (should (eq c (cj/--ai-term-next-agent-buffer b (list a b c)))) + (mapc #'kill-buffer (list a b c))))) + +(ert-deftest test-ai-term--next-agent-buffer-wraps-after-last () + "Boundary: current is the last element -> wraps to the first." + (let ((a (get-buffer-create "agent [a]")) + (b (get-buffer-create "agent [b]")) + (c (get-buffer-create "agent [c]"))) + (unwind-protect + (should (eq a (cj/--ai-term-next-agent-buffer c (list a b c)))) + (mapc #'kill-buffer (list a b c))))) + +(ert-deftest test-ai-term--next-agent-buffer-single-element-returns-itself () + "Boundary: a one-agent queue wraps current back to itself." + (let ((a (get-buffer-create "agent [a]"))) + (unwind-protect + (should (eq a (cj/--ai-term-next-agent-buffer a (list a)))) + (kill-buffer a)))) + +(ert-deftest test-ai-term--next-agent-buffer-nil-current-returns-first () + "Boundary: nil current (no agent displayed) -> returns the first." + (let ((a (get-buffer-create "agent [a]")) + (b (get-buffer-create "agent [b]"))) + (unwind-protect + (should (eq a (cj/--ai-term-next-agent-buffer nil (list a b)))) + (mapc #'kill-buffer (list a b))))) + +(ert-deftest test-ai-term--next-agent-buffer-non-member-current-returns-first () + "Error: current not in the queue -> returns the first rather than nil." + (let ((a (get-buffer-create "agent [a]")) + (b (get-buffer-create "agent [b]")) + (stray (get-buffer-create "agent [stray]"))) + (unwind-protect + (should (eq a (cj/--ai-term-next-agent-buffer stray (list a b)))) + (mapc #'kill-buffer (list a b stray))))) + +(ert-deftest test-ai-term--next-agent-buffer-empty-queue-returns-nil () + "Boundary: an empty queue returns nil (nothing to switch to)." + (should (null (cj/--ai-term-next-agent-buffer nil '())))) + +(provide 'test-ai-term--next-agent-buffer) +;;; test-ai-term--next-agent-buffer.el ends here diff --git a/tests/test-ai-term--reuse-edge-window.el b/tests/test-ai-term--reuse-edge-window.el index f6259ae50..a9a0529e8 100644 --- a/tests/test-ai-term--reuse-edge-window.el +++ b/tests/test-ai-term--reuse-edge-window.el @@ -269,5 +269,46 @@ most-recent agent, which would now be the other one." (when (get-buffer right-name) (kill-buffer right-name)) (cj/test--kill-agent-buffers)))) +(ert-deftest test-ai-term--reuse-edge-window-3win-toggle-restores-own-window () + "Regression: in a 3-window layout the agent has its own split, so toggling it +off then on restores it as its own window without displacing a working window. +Before the fix, toggle-on reused the bottom edge (the user's main window), +collapsing three windows to two and hiding the main buffer. A toggle must be +reversible: off then on returns to the same layout." + (cj/test--kill-agent-buffers) + (let ((agent-name "agent [3win-toggle]") + (code-name "*test-3win-code*") + (main-name "*test-3win-main*") + (cj/--ai-term-last-direction nil) + (cj/--ai-term-last-size nil) + (cj/--ai-term-last-was-bury nil)) + (unwind-protect + (save-window-excursion + (delete-other-windows) + (cl-letf (((symbol-function 'cj/--ai-term-default-direction) (lambda (&rest _) 'below))) + (let ((code-buf (get-buffer-create code-name)) + (main-buf (get-buffer-create main-name)) + (agent-buf (get-buffer-create agent-name))) + (set-window-buffer (selected-window) code-buf) + (let* ((main-win (split-window (selected-window) nil 'below)) + (agent-win (split-window main-win nil 'below))) + (set-window-buffer main-win main-buf) + (set-window-buffer agent-win agent-buf) + (should (= (count-windows) 3)) + (let ((display-buffer-alist (cj/--ai-term-display-rule-list))) + (select-window agent-win) + (cj/test--call-as-gui #'cj/ai-term) ; off -> code | main + (should (= (count-windows) 2)) + (should-not (member agent-name (cj/test--displayed-buffer-names))) + (cj/test--call-as-gui #'cj/ai-term) ; on -> back to 3 windows + (should (= (count-windows) 3)) + (let ((bufs (cj/test--displayed-buffer-names))) + (should (member agent-name bufs)) + (should (member code-name bufs)) + (should (member main-name bufs)))))))) + (when (get-buffer code-name) (kill-buffer code-name)) + (when (get-buffer main-name) (kill-buffer main-name)) + (cj/test--kill-agent-buffers)))) + (provide 'test-ai-term--reuse-edge-window) ;;; test-ai-term--reuse-edge-window.el ends here diff --git a/tests/test-calendar-sync--parse-exception-event.el b/tests/test-calendar-sync--parse-exception-event.el new file mode 100644 index 000000000..1935d3ebb --- /dev/null +++ b/tests/test-calendar-sync--parse-exception-event.el @@ -0,0 +1,64 @@ +;;; test-calendar-sync--parse-exception-event.el --- Tests for one-event exception parsing -*- lexical-binding: t; -*- + +;;; Commentary: +;; Unit tests for calendar-sync--parse-exception-event, the per-VEVENT half of +;; calendar-sync--collect-recurrence-exceptions: it turns a single RECURRENCE-ID +;; override VEVENT into an exception plist (or nil). One function per file. + +;;; Code: + +(require 'ert) +(add-to-list 'load-path (expand-file-name "." (file-name-directory load-file-name))) +(add-to-list 'load-path (expand-file-name "../modules" (file-name-directory load-file-name))) +(require 'testutil-calendar-sync) +(require 'calendar-sync) + +(defun test-cs-parse-exc--override-event (start end) + "Return a RECURRENCE-ID override VEVENT string for START..END." + (concat "BEGIN:VEVENT\n" + "UID:override@google.com\n" + "RECURRENCE-ID:20260203T090000Z\n" + "SUMMARY:Rescheduled Meeting\n" + "DTSTART:" (test-calendar-sync-ics-datetime start) "\n" + "DTEND:" (test-calendar-sync-ics-datetime end) "\n" + "END:VEVENT")) + +;;; Normal Cases + +(ert-deftest test-calendar-sync--parse-exception-event-normal-returns-plist () + "Normal: a RECURRENCE-ID override parses into a plist with its overridden times." + (let* ((start (test-calendar-sync-time-days-from-now 7 10 0)) + (end (test-calendar-sync-time-days-from-now 7 11 0)) + (plist (calendar-sync--parse-exception-event + (test-cs-parse-exc--override-event start end)))) + (should plist) + (should (plist-get plist :recurrence-id)) + (should (equal "20260203T090000Z" (plist-get plist :recurrence-id-raw))) + (should (plist-get plist :start)) + (should (plist-get plist :end)) + (should (equal "Rescheduled Meeting" (plist-get plist :summary))))) + +;;; Boundary Cases + +(ert-deftest test-calendar-sync--parse-exception-event-boundary-no-recurrence-id () + "Boundary: a VEVENT with no RECURRENCE-ID is not an override and returns nil." + (let* ((start (test-calendar-sync-time-days-from-now 7 10 0)) + (end (test-calendar-sync-time-days-from-now 7 11 0)) + (event (test-calendar-sync-make-vevent "Regular Event" start end))) + (should-not (calendar-sync--parse-exception-event event)))) + +;;; Error Cases + +(ert-deftest test-calendar-sync--parse-exception-event-error-unparseable-times () + "Error: a RECURRENCE-ID override whose times do not parse returns nil rather +than a half-built plist." + (let ((event (concat "BEGIN:VEVENT\n" + "UID:broken@google.com\n" + "RECURRENCE-ID:not-a-timestamp\n" + "SUMMARY:Broken Override\n" + "DTSTART:also-garbage\n" + "END:VEVENT"))) + (should-not (calendar-sync--parse-exception-event event)))) + +(provide 'test-calendar-sync--parse-exception-event) +;;; test-calendar-sync--parse-exception-event.el ends here diff --git a/tests/test-cj-window-geometry-lib.el b/tests/test-cj-window-geometry-lib.el index 938749f21..d32a48a92 100644 --- a/tests/test-cj-window-geometry-lib.el +++ b/tests/test-cj-window-geometry-lib.el @@ -2,7 +2,7 @@ ;;; Commentary: ;; Tests the pure helpers in `cj-window-geometry-lib.el': -;; `cj/window-direction', `cj/window-body-size', +;; `cj/window-direction', `cj/window-replay-size', ;; `cj/cardinal-to-edge-direction', and `cj/window-at-edge'. ;;; Code: @@ -52,30 +52,32 @@ (delete-other-windows) (should (eq (cj/window-direction (selected-window) 'below) 'below)))) -(ert-deftest test-cj-window-geometry--body-size-right-returns-body-cols () +(ert-deftest test-cj-window-geometry--replay-size-right-returns-body-cols () "Normal: right window with direction='right -> body-width in cols." (save-window-excursion (delete-other-windows) (let ((right (split-window (selected-window) nil 'right))) - (should (= (cj/window-body-size right 'right) + (should (= (cj/window-replay-size right 'right) (window-body-width right)))))) -(ert-deftest test-cj-window-geometry--body-size-below-returns-body-lines () - "Normal: below window with direction='below -> body-height in lines." +(ert-deftest test-cj-window-geometry--replay-size-below-returns-total-lines () + "Normal: below window with direction='below -> total-height in lines. +The vertical axis captures total-height (not body-height) so the capture/ +replay round-trip is immune to the mode line's pixel height." (save-window-excursion (delete-other-windows) (let ((below (split-window (selected-window) nil 'below))) - (should (= (cj/window-body-size below 'below) - (window-body-height below)))))) + (should (= (cj/window-replay-size below 'below) + (window-total-height below)))))) -(ert-deftest test-cj-window-geometry--body-size-narrow-window () +(ert-deftest test-cj-window-geometry--replay-size-narrow-window () "Normal: deliberately narrow right window -> matching body cols." (save-window-excursion (delete-other-windows) (let* ((frame-w (frame-width)) (target-cols (/ frame-w 4)) (right (split-window (selected-window) (- target-cols) 'right))) - (should (= (cj/window-body-size right 'right) + (should (= (cj/window-replay-size right 'right) (window-body-width right)))))) (ert-deftest test-cj-window-geometry--cardinal-to-edge-right () diff --git a/tests/test-cj-window-toggle-lib.el b/tests/test-cj-window-toggle-lib.el index 0762e255c..5edd06e96 100644 --- a/tests/test-cj-window-toggle-lib.el +++ b/tests/test-cj-window-toggle-lib.el @@ -36,7 +36,9 @@ (window-body-width right)))))) (ert-deftest test-cj-window-toggle-capture-records-below-split () - "Normal: below-split window writes direction=below and integer body-lines." + "Normal: below-split window writes direction=below and integer total-lines. +The vertical axis captures total-height, not body-height, so the round-trip +is immune to the mode line's pixel height (see `cj/window-replay-size')." (save-window-excursion (delete-other-windows) (let ((below (split-window (selected-window) nil 'below)) @@ -49,7 +51,7 @@ (should (eq test-cj-window-toggle--last-direction 'below)) (should (integerp test-cj-window-toggle--last-size)) (should (= test-cj-window-toggle--last-size - (window-body-height below)))))) + (window-total-height below)))))) (ert-deftest test-cj-window-toggle-capture-falls-back-to-default-direction () "Boundary: window filling the frame uses the supplied default direction." @@ -156,7 +158,9 @@ transfer; clearing it lets the consumer's default size apply." (should (eq (cdr (assq 'inhibit-same-window received-alist)) t)))) (ert-deftest test-cj-window-toggle-display-saved-maps-below-to-bottom () - "Normal: saved below + integer size -> bottom edge, body-lines cons." + "Normal: saved below + integer size -> bottom edge, plain total-line count. +The height axis replays a total-line integer (not a body-lines cons) so the +round-trip is immune to the mode line's pixel height." (let (received-alist (test-cj-window-toggle--last-direction 'below) (test-cj-window-toggle--last-size 12)) @@ -169,8 +173,7 @@ transfer; clearing it lets the consumer's default size apply." 'test-cj-window-toggle--last-size 0.7)) (should (eq (cdr (assq 'direction received-alist)) 'bottom)) - (should (equal (cdr (assq 'window-height received-alist)) - '(body-lines . 12))) + (should (equal (cdr (assq 'window-height received-alist)) 12)) (should-not (assq 'window-width received-alist)))) (ert-deftest test-cj-window-toggle-display-saved-maps-right-to-rightmost () diff --git a/tests/test-dirvish-config-playlist.el b/tests/test-dirvish-config-playlist.el index d059a899a..14bb94ac7 100644 --- a/tests/test-dirvish-config-playlist.el +++ b/tests/test-dirvish-config-playlist.el @@ -10,6 +10,7 @@ ;;; Code: (require 'ert) +(require 'cl-lib) (require 'package) (setq package-user-dir (expand-file-name "elpa" user-emacs-directory)) @@ -93,5 +94,59 @@ lowercase extension list." (dolist (bad '("../evil" "../../etc/cron" "/etc/passwd" "sub/dir/name")) (should-not (cj/--playlist-name-safe-p bad)))) +;;; cj/--playlist-resolve-target +;; +;; Drives the real `file-exists-p' against a temp `music-dir' (mocking a C +;; primitive triggers a native-comp trampoline rebuild that fails under +;; --batch); only the ordinary `read-string' / `read-char-choice' prompts are +;; stubbed. + +(ert-deftest test-cj--playlist-resolve-target-returns-path-for-new-name () + "Normal: a safe name with no existing file returns its .m3u path under music-dir." + (let* ((music-dir (make-temp-file "cj-playlist-" t))) + (unwind-protect + (cl-letf (((symbol-function 'read-string) (lambda (&rest _) "roadtrip"))) + (should (equal (expand-file-name "roadtrip.m3u" music-dir) + (cj/--playlist-resolve-target)))) + (delete-directory music-dir t)))) + +(ert-deftest test-cj--playlist-resolve-target-reprompts-on-unsafe-name () + "Boundary: an unsafe name (with `/') re-prompts until a safe name is given." + (let* ((music-dir (make-temp-file "cj-playlist-" t)) + (answers '("../escape" "safe")) + (asked 0)) + (unwind-protect + (cl-letf (((symbol-function 'read-string) + (lambda (&rest _) (prog1 (nth asked answers) (cl-incf asked)))) + ((symbol-function 'message) (lambda (&rest _) nil))) + (should (equal (expand-file-name "safe.m3u" music-dir) + (cj/--playlist-resolve-target))) + (should (= 2 asked))) + (delete-directory music-dir t)))) + +(ert-deftest test-cj--playlist-resolve-target-overwrite-returns-existing-path () + "Normal: when the target exists, choosing overwrite returns the same path." + (let* ((music-dir (make-temp-file "cj-playlist-" t)) + (existing (expand-file-name "mix.m3u" music-dir))) + (unwind-protect + (progn + (with-temp-file existing (insert "old\n")) + (cl-letf (((symbol-function 'read-string) (lambda (&rest _) "mix")) + ((symbol-function 'read-char-choice) (lambda (&rest _) ?o))) + (should (equal existing (cj/--playlist-resolve-target))))) + (delete-directory music-dir t)))) + +(ert-deftest test-cj--playlist-resolve-target-cancel-signals-user-error () + "Error: when the target exists, choosing cancel aborts with a `user-error'." + (let* ((music-dir (make-temp-file "cj-playlist-" t)) + (existing (expand-file-name "mix.m3u" music-dir))) + (unwind-protect + (progn + (with-temp-file existing (insert "old\n")) + (cl-letf (((symbol-function 'read-string) (lambda (&rest _) "mix")) + ((symbol-function 'read-char-choice) (lambda (&rest _) ?c))) + (should-error (cj/--playlist-resolve-target) :type 'user-error))) + (delete-directory music-dir t)))) + (provide 'test-dirvish-config-playlist) ;;; test-dirvish-config-playlist.el ends here diff --git a/tests/test-dwim-shell-config-command-fixes.el b/tests/test-dwim-shell-config-command-fixes.el index 2f49a868f..2cc3ae72b 100644 --- a/tests/test-dwim-shell-config-command-fixes.el +++ b/tests/test-dwim-shell-config-command-fixes.el @@ -29,5 +29,60 @@ so the substitution can't sit dead inside single quotes." (should (string-match-p "\\.[0-9]\\{8\\}_[0-9]\\{6\\}\\.bak'" cmd)) (should-not (string-match-p "\\$(date" cmd)))) +;;; ----------------------- tar-gzip command builder -------------------------- + +(ert-deftest test-dwim-tar-gzip-command-single-names-after-file () + "Normal: a single marked file names the archive <fne>.tar.gz over <<f>>." + (let ((cmd (cj/dwim-shell--tar-gzip-command t))) + (should (string-match-p "'<<fne>>\\.tar\\.gz'" cmd)) + (should (string-match-p "'<<f>>'" cmd)))) + +(ert-deftest test-dwim-tar-gzip-command-multi-uses-shared-archive () + "Boundary: multiple files tar into a shared archive.tar.gz over <<*>>." + (let ((cmd (cj/dwim-shell--tar-gzip-command nil))) + (should (string-match-p "archive\\.tar\\.gz" cmd)) + (should (string-match-p "'<<\\*>>'" cmd)))) + +;;; --------------------- text-to-speech command builder ---------------------- + +(ert-deftest test-dwim-text-to-speech-command-darwin-uses-say-voice () + "Normal: on darwin the command uses `say' with the chosen voice." + (let ((cmd (cj/dwim-shell--text-to-speech-command 'darwin "Samantha"))) + (should (string-match-p "\\`say -v Samantha " cmd)) + (should (string-match-p "'<<fne>>\\.aiff'" cmd)))) + +(ert-deftest test-dwim-text-to-speech-command-linux-uses-espeak () + "Boundary: a non-darwin system uses `espeak' and ignores the voice." + (let ((cmd (cj/dwim-shell--text-to-speech-command 'gnu/linux "ignored"))) + (should (string-match-p "\\`espeak " cmd)) + (should (string-match-p "'<<fne>>\\.wav'" cmd)) + (should-not (string-match-p "ignored" cmd)))) + +;;; ----------------------- video-trim command builder ------------------------ + +(ert-deftest test-dwim-video-trim-command-beginning-uses-ss () + "Normal: trimming the beginning emits a leading -ss with the start seconds." + (let ((cmd (cj/dwim-shell--video-trim-command "Beginning" 7 0))) + (should (string-match-p "-ss 7 " cmd)) + (should-not (string-match-p "-sseof" cmd)))) + +(ert-deftest test-dwim-video-trim-command-end-uses-sseof () + "Normal: trimming the end emits -sseof with the end seconds, no -ss." + (let ((cmd (cj/dwim-shell--video-trim-command "End" 0 9))) + (should (string-match-p "-sseof -9 " cmd)) + (should-not (string-match-p "-ss [0-9]" cmd)))) + +(ert-deftest test-dwim-video-trim-command-both-uses-ss-and-sseof () + "Normal: trimming both ends emits both -ss start and -sseof end." + (let ((cmd (cj/dwim-shell--video-trim-command "Both" 3 4))) + (should (string-match-p "-ss 3 " cmd)) + (should (string-match-p "-sseof -4 " cmd)))) + +(ert-deftest test-dwim-video-trim-command-negative-seconds-errors () + "Error: a negative second count for the used side signals a user-error." + (should-error (cj/dwim-shell--video-trim-command "Beginning" -1 0) :type 'user-error) + (should-error (cj/dwim-shell--video-trim-command "End" 0 -1) :type 'user-error) + (should-error (cj/dwim-shell--video-trim-command "Both" 0 -2) :type 'user-error)) + (provide 'test-dwim-shell-config-command-fixes) ;;; test-dwim-shell-config-command-fixes.el ends here diff --git a/tests/test-init-defer-games.el b/tests/test-init-defer-games.el new file mode 100644 index 000000000..0b85a1ea7 --- /dev/null +++ b/tests/test-init-defer-games.el @@ -0,0 +1,38 @@ +;;; test-init-defer-games.el --- games-config Phase 4 deferral -*- lexical-binding: t; -*- + +;;; Commentary: +;; games-config is deferred (load-graph Phase 4): init.el autoloads `malyon' +;; and `2048-game' instead of requiring the module eagerly. These tests guard +;; that the game commands stay reachable with the module unloaded, and that +;; loading the module still applies the one setting it owns. + +;;; Code: + +(require 'ert) +(require 'package) + +(ert-deftest test-init-defer-games-commands-autoload-without-module () + "Normal: the game commands resolve with games-config unloaded. +This is the safety net for the deferral -- dropping the eager require keeps +malyon and 2048-game reachable only because the packages autoload their own +commands, so assert that holds." + (package-initialize) + (should-not (featurep 'games-config)) + (should (commandp 'malyon)) + (should (commandp '2048-game))) + +(ert-deftest test-init-defer-games-config-applies-malyon-stories-dir () + "Normal: loading games-config still applies malyon's stories directory. +The module is the config owner; deferring it must not drop the one setting it +adds (`malyon-stories-directory'), which use-package applies when malyon loads." + (package-initialize) + (add-to-list 'load-path (expand-file-name "modules" default-directory)) + (require 'user-constants) + (let ((org-dir "/tmp/games-defer-test/")) + (load "games-config" nil t) + (unless (require 'malyon nil t) + (ert-skip "malyon package not available")) + (should (equal malyon-stories-directory "/tmp/games-defer-test/text.games/")))) + +(provide 'test-init-defer-games) +;;; test-init-defer-games.el ends here diff --git a/tests/test-system-defaults-functions.el b/tests/test-system-defaults-functions.el index a5210be01..2562ff6aa 100644 --- a/tests/test-system-defaults-functions.el +++ b/tests/test-system-defaults-functions.el @@ -79,20 +79,6 @@ (should (eq (cj/disabled) nil)) (should (commandp #'cj/disabled))) -;;; cj/minibuffer-setup-hook / cj/minibuffer-exit-hook - -(ert-deftest test-system-defaults-minibuffer-setup-inflates-gc-threshold () - "Normal: entering the minibuffer raises `gc-cons-threshold' to most-positive-fixnum." - (let ((gc-cons-threshold 800000)) - (cj/minibuffer-setup-hook) - (should (= gc-cons-threshold most-positive-fixnum)))) - -(ert-deftest test-system-defaults-minibuffer-exit-restores-gc-threshold () - "Normal: leaving the minibuffer restores `gc-cons-threshold' to 800000." - (let ((gc-cons-threshold most-positive-fixnum)) - (cj/minibuffer-exit-hook) - (should (= gc-cons-threshold 800000)))) - ;;; unpropertize-kill-ring (ert-deftest test-system-defaults-unpropertize-kill-ring-strips-properties () diff --git a/tests/test-system-defaults.el b/tests/test-system-defaults.el index 928124f56..f653e1fbb 100644 --- a/tests/test-system-defaults.el +++ b/tests/test-system-defaults.el @@ -63,19 +63,6 @@ test clears it first to capture the path derived from the sandbox." (expand-file-name dir))) (should (string-suffix-p "backups" (directory-file-name dir))))))) -;;; minibuffer GC hooks - -(ert-deftest test-system-defaults-minibuffer-gc-hooks-registered () - "Normal: the minibuffer GC raise/restore hooks are installed. -Their bodies are tested in test-system-defaults-functions.el; this asserts -they are actually wired onto the minibuffer hooks." - (test-system-defaults--with-load-environment - (let ((minibuffer-setup-hook nil) - (minibuffer-exit-hook nil)) - (test-system-defaults--load) - (should (memq 'cj/minibuffer-setup-hook minibuffer-setup-hook)) - (should (memq 'cj/minibuffer-exit-hook minibuffer-exit-hook))))) - ;;; Customize-save warning (ert-deftest test-system-defaults-customize-save-warns-once () diff --git a/tests/test-term-tmux-history.el b/tests/test-term-tmux-history.el index 51e9725c4..e36b3e98e 100644 --- a/tests/test-term-tmux-history.el +++ b/tests/test-term-tmux-history.el @@ -336,14 +336,15 @@ instead of being forwarded to the terminal program." (should-not (eq (keymap-lookup ghostel-semi-char-mode-map "C-M-<left>") 'ghostel--send-event))) -(ert-deftest test-term-f10-music-and-shutdown-in-keymap-exceptions () - "Regression: F10 (music playlist toggle) and C-F10 (server shutdown) are in -`ghostel-keymap-exceptions' so they reach Emacs from inside a ghostel buffer -instead of being forwarded to the terminal program. Both are global bindings, -so dropping them from the semi-char map lets the lookup fall through to the -global map." - (dolist (key '("<f10>" "C-<f10>")) - (should (member key ghostel-keymap-exceptions))) +(ert-deftest test-term-f10-music-in-keymap-exceptions () + "Regression: F10 (music playlist toggle) is in `ghostel-keymap-exceptions' +so it reaches Emacs from inside a ghostel buffer instead of being forwarded +to the terminal program. It is a global binding, so dropping it from the +semi-char map lets the lookup fall through to the global map. Server +shutdown moved off C-F10 to C-x C, which is deliberately NOT an exception +(C-x C stays forwarding to the terminal program inside an agent buffer)." + (should (member "<f10>" ghostel-keymap-exceptions)) + (should-not (member "C-<f10>" ghostel-keymap-exceptions)) (should-not (eq (keymap-lookup ghostel-semi-char-mode-map "<f10>") 'ghostel--send-event))) diff --git a/tests/test-term-toggle--display.el b/tests/test-term-toggle--display.el index 7fa7f0a98..d6dd33da2 100644 --- a/tests/test-term-toggle--display.el +++ b/tests/test-term-toggle--display.el @@ -17,7 +17,9 @@ (require 'term-config) (ert-deftest test-term-toggle--capture-state-records-direction-and-size () - "Normal: capture-state writes direction and integer body size." + "Normal: capture-state writes direction and integer size. +The vertical axis captures total-height (not body-height) so the toggle +round-trip is immune to the mode line's pixel height." (save-window-excursion (delete-other-windows) (let ((below (split-window (selected-window) nil 'below)) @@ -26,7 +28,7 @@ (cj/--term-toggle-capture-state below) (should (eq cj/--term-toggle-last-direction 'below)) (should (integerp cj/--term-toggle-last-size)) - (should (= cj/--term-toggle-last-size (window-body-height below)))))) + (should (= cj/--term-toggle-last-size (window-total-height below)))))) (ert-deftest test-term-toggle--capture-state-noop-on-dead-window () "Boundary: nil window -> state remains unchanged." @@ -50,7 +52,9 @@ (should (eq (cdr (assq 'inhibit-same-window received-alist)) t)))) (ert-deftest test-term-toggle--display-saved-maps-cardinal-to-edge () - "Normal: saved 'below maps to bottom edge; integer size wraps in body-lines." + "Normal: saved 'below maps to bottom edge; integer size is a plain total-line count. +The height axis replays a total-line integer (not a body-lines cons) so the +round-trip is immune to the mode line's pixel height." (let (received-alist (cj/--term-toggle-last-direction 'below) (cj/--term-toggle-last-size 12)) @@ -58,8 +62,7 @@ (lambda (_b a) (setq received-alist a) 'fake-window))) (cj/--term-toggle-display-saved 'fake-buf nil)) (should (eq (cdr (assq 'direction received-alist)) 'bottom)) - (should (equal (cdr (assq 'window-height received-alist)) - '(body-lines . 12))) + (should (equal (cdr (assq 'window-height received-alist)) 12)) (should-not (assq 'window-width received-alist)))) (ert-deftest test-term-toggle--display-saved-strips-conflicting-alist-entries () |
