diff options
Diffstat (limited to 'tests')
27 files changed, 475 insertions, 458 deletions
diff --git a/tests/test-ai-term--agent-buffers.el b/tests/test-ai-term--agent-buffers.el index 20c661c45..e0d8faa79 100644 --- a/tests/test-ai-term--agent-buffers.el +++ b/tests/test-ai-term--agent-buffers.el @@ -14,7 +14,7 @@ (add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) (add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) (require 'ai-term) -(require 'testutil-ghostel-buffers) +(require 'testutil-terminal-buffers) (ert-deftest test-ai-term--agent-buffers-empty-when-none-exist () "Boundary: no agent-prefixed buffers anywhere -> empty list." diff --git a/tests/test-ai-term--close.el b/tests/test-ai-term--close.el index 4098c091e..242bfd749 100644 --- a/tests/test-ai-term--close.el +++ b/tests/test-ai-term--close.el @@ -2,7 +2,7 @@ ;;; Commentary: ;; `cj/ai-term-close' tears an agent down gracefully: kill its tmux -;; session (stopping the agent process), kill the ghostel buffer, and +;; session (stopping the agent process), kill the agent buffer, and ;; remove its window. These tests cover the pure pieces -- the ;; tmux-kill helper, the per-buffer teardown, and the target selection -- ;; with `process-file' and the prompt mocked at the boundary. @@ -15,7 +15,7 @@ (add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) (add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) (require 'ai-term) -(require 'testutil-ghostel-buffers) +(require 'testutil-terminal-buffers) (ert-deftest test-ai-term--kill-tmux-session-runs-kill-session () "Normal: invokes `tmux kill-session -t <session>'." diff --git a/tests/test-ai-term--collapse-split.el b/tests/test-ai-term--collapse-split.el index a09af5598..bae913624 100644 --- a/tests/test-ai-term--collapse-split.el +++ b/tests/test-ai-term--collapse-split.el @@ -23,7 +23,7 @@ (add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) (add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) (require 'ai-term) -(require 'testutil-ghostel-buffers) +(require 'testutil-terminal-buffers) ;;; cj/--ai-term-most-recent-non-agent-buffer diff --git a/tests/test-ai-term--dispatch.el b/tests/test-ai-term--dispatch.el index 91b5e1bc6..129c53cda 100644 --- a/tests/test-ai-term--dispatch.el +++ b/tests/test-ai-term--dispatch.el @@ -16,7 +16,7 @@ (add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) (add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) (require 'ai-term) -(require 'testutil-ghostel-buffers) +(require 'testutil-terminal-buffers) (ert-deftest test-ai-term--dispatch-window-displayed-returns-toggle-off () "Normal: displayed agent window -> (toggle-off . WIN)." diff --git a/tests/test-ai-term--display-saved.el b/tests/test-ai-term--display-saved.el index 51c22fde9..5707bea5b 100644 --- a/tests/test-ai-term--display-saved.el +++ b/tests/test-ai-term--display-saved.el @@ -26,7 +26,7 @@ (add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) (add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) (require 'ai-term) -(require 'testutil-ghostel-buffers) +(require 'testutil-terminal-buffers) (ert-deftest test-ai-term--display-saved-uses-desktop-defaults-when-state-nil () "Normal: nil state on a desktop -> rightmost, size=cj/ai-term-desktop-width. diff --git a/tests/test-ai-term--displayed-agent-window.el b/tests/test-ai-term--displayed-agent-window.el index eeb40ed31..ced3ff414 100644 --- a/tests/test-ai-term--displayed-agent-window.el +++ b/tests/test-ai-term--displayed-agent-window.el @@ -12,7 +12,7 @@ (add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) (add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) (require 'ai-term) -(require 'testutil-ghostel-buffers) +(require 'testutil-terminal-buffers) (ert-deftest test-ai-term--displayed-agent-window-no-buffers-returns-nil () "Boundary: no agent buffers anywhere -> nil." diff --git a/tests/test-ai-term--keybindings.el b/tests/test-ai-term--keybindings.el index a8b92ffa8..6f7f53a5e 100644 --- a/tests/test-ai-term--keybindings.el +++ b/tests/test-ai-term--keybindings.el @@ -4,12 +4,11 @@ ;; ai-term lives under the C-; a prefix (vacated when gptel was archived), with ;; the frequent "swap to the next agent" also on M-SPC for a fast chord. M-SPC ;; must reach Emacs from inside an agent buffer, so it is bound in -;; `ghostel-mode-map' and added to `ghostel-keymap-exceptions' (the semi-char -;; map otherwise forwards it to the pty). C-; is already an exception via -;; term-config, so the C-; a family resolves through the global prefix. These -;; tests require ghostel (so ai-term's `with-eval-after-load' fires) before -;; ai-term, then confirm the bindings landed and the old F9 family is gone. -;; `(require 'ghostel)' does not load the native module, so this stays light. +;; `eat-semi-char-mode-map' (EAT forwards unbound keys to the pty otherwise). +;; C-; is already bound there via eat-config, so the C-; a family resolves +;; through the global prefix. These tests require eat (so ai-term's +;; `with-eval-after-load' fires) before ai-term, then confirm the bindings +;; landed and the old F9 family is gone. ;;; Code: @@ -19,7 +18,7 @@ (setq package-user-dir (expand-file-name "elpa" user-emacs-directory)) (package-initialize) (add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) -(require 'ghostel) +(require 'eat) (require 'ai-term) (ert-deftest test-ai-term-keymap-leaf-bindings () @@ -37,16 +36,11 @@ "Normal: M-SPC runs `cj/ai-term-next' (the fast swap chord)." (should (eq (lookup-key (current-global-map) (kbd "M-SPC")) #'cj/ai-term-next))) -(ert-deftest test-ai-term-meta-space-bound-in-ghostel-mode-map () - "Normal: M-SPC is bound in `ghostel-mode-map' so swap works inside an agent." - (should (eq (keymap-lookup ghostel-mode-map "M-SPC") #'cj/ai-term-next))) - -(ert-deftest test-ai-term-meta-space-in-keymap-exceptions () - "Regression: M-SPC is in `ghostel-keymap-exceptions' so semi-char mode lets it -reach Emacs instead of forwarding it to the pty." - (should (member "M-SPC" ghostel-keymap-exceptions)) - (should-not (eq (keymap-lookup ghostel-semi-char-mode-map "M-SPC") - 'ghostel--send-event))) +(ert-deftest test-ai-term-meta-space-bound-in-eat-semi-char-mode-map () + "Normal: M-SPC is bound in `eat-semi-char-mode-map' so swap works inside an +agent. EAT forwards unbound keys to the pty, so the bind is what lets it reach +Emacs -- no ghostel-style exception list or rebuild is needed." + (should (eq (keymap-lookup eat-semi-char-mode-map "M-SPC") #'cj/ai-term-next))) (ert-deftest test-ai-term-f9-family-removed-globally () "Regression: the old F9 family no longer binds the ai-term commands globally." diff --git a/tests/test-ai-term--launch-command.el b/tests/test-ai-term--launch-command.el index 246e70a3f..e61c0e579 100644 --- a/tests/test-ai-term--launch-command.el +++ b/tests/test-ai-term--launch-command.el @@ -1,7 +1,7 @@ ;;; test-ai-term--launch-command.el --- Tests for cj/--ai-term-launch-command -*- lexical-binding: t; -*- ;;; Commentary: -;; The launch command is what gets typed into a fresh ghostel shell to bring +;; The launch command is what gets typed into a fresh shell to bring ;; up the agent inside a per-project tmux session. The session is named ;; `cj/ai-term-tmux-session-prefix' + the project basename, so a second ;; F9 on the same project reattaches to the running agent rather than diff --git a/tests/test-ai-term--reuse-edge-window.el b/tests/test-ai-term--reuse-edge-window.el index a9a0529e8..8ba2f759f 100644 --- a/tests/test-ai-term--reuse-edge-window.el +++ b/tests/test-ai-term--reuse-edge-window.el @@ -25,7 +25,7 @@ (add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) (add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) (require 'ai-term) -(require 'testutil-ghostel-buffers) +(require 'testutil-terminal-buffers) (defun cj/test--displayed-buffer-names () "Return the buffer names shown in the selected frame, left/top to right/bottom." diff --git a/tests/test-ai-term--reuse-existing-agent.el b/tests/test-ai-term--reuse-existing-agent.el index 3f0c64493..361e94be9 100644 --- a/tests/test-ai-term--reuse-existing-agent.el +++ b/tests/test-ai-term--reuse-existing-agent.el @@ -17,7 +17,7 @@ (add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) (add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) (require 'ai-term) -(require 'testutil-ghostel-buffers) +(require 'testutil-terminal-buffers) (ert-deftest test-ai-term--reuse-existing-agent-swaps-buffer-when-window-exists () "Normal: an agent window exists -> swap its buffer, return the window." diff --git a/tests/test-ai-term--server-display.el b/tests/test-ai-term--server-display.el index b3d32dc83..6db9cf2d3 100644 --- a/tests/test-ai-term--server-display.el +++ b/tests/test-ai-term--server-display.el @@ -16,7 +16,7 @@ (add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) (require 'ai-term) (require 'server) -(require 'testutil-ghostel-buffers) +(require 'testutil-terminal-buffers) (ert-deftest test-ai-term--non-agent-window-finds-code-window () "Normal: agent on the right, code on the left -> returns the code window." diff --git a/tests/test-ai-term--show-or-create.el b/tests/test-ai-term--show-or-create.el index c6653dcdd..4f5f1f67f 100644 --- a/tests/test-ai-term--show-or-create.el +++ b/tests/test-ai-term--show-or-create.el @@ -3,13 +3,13 @@ ;;; Commentary: ;; Tests the show-or-create branching: ;; -;; - buffer absent -> ghostel called, agent command + newline sent -;; - buffer present, live -> ghostel not called, buffer displayed -;; - buffer present, dead -> old buffer killed, ghostel recreates +;; - buffer absent -> eat called, agent command + newline sent +;; - buffer present, live -> eat not called, buffer displayed +;; - buffer present, dead -> old buffer killed, eat recreates ;; -;; ghostel functions are stubbed so the test does no process spawning and -;; never loads the native module. Production calls (ghostel) with no name and -;; relies on the dynamically bound `ghostel-buffer-name'; the mock honors that. +;; eat + the send helper are stubbed so the test does no process spawning. +;; Production calls (eat) and relies on the dynamically bound `eat-buffer-name'; +;; the mock honors that. ;;; Code: @@ -19,19 +19,17 @@ (add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) (require 'ai-term) -;; ghostel isn't loaded in batch -- provide stubs so cl-letf has overrides. -(unless (fboundp 'ghostel) - (defun ghostel (&optional _arg) nil)) -(unless (fboundp 'ghostel-send-string) - (defun ghostel-send-string (_s) nil)) +;; eat isn't loaded in batch -- provide a stub so cl-letf has an override. +(unless (fboundp 'eat) + (defun eat (&optional _program _arg) nil)) -(defmacro test-ai-term--with-mock-ghostel (vars &rest body) - "Run BODY with ghostel + ghostel-send-string mocked. +(defmacro test-ai-term--with-mock-eat (vars &rest body) + "Run BODY with eat + `cj/--ai-term-send-string' mocked. -VARS is a plist of capture variable names: :calls (buffer names ghostel -was asked to create), :strings (sent strings), :default-dir. The mocked -`ghostel' creates and returns a buffer named after the dynamically bound -`ghostel-buffer-name', mirroring the real entry point." +VARS is a plist of capture variable names: :calls (buffer names eat was asked +to create), :strings (sent strings), :default-dir. The mocked `eat' creates +and returns a buffer named after the dynamically bound `eat-buffer-name', +mirroring the real entry point." (declare (indent 1) (debug t)) (let ((calls (plist-get vars :calls)) (strings (plist-get vars :strings)) @@ -39,14 +37,14 @@ was asked to create), :strings (sent strings), :default-dir. The mocked `(let ((,calls '()) (,strings '()) (,ddir nil)) - (cl-letf (((symbol-function 'ghostel) - (lambda (&optional _arg) + (cl-letf (((symbol-function 'eat) + (lambda (&optional _program _arg) (setq ,ddir default-directory) - (let ((b (get-buffer-create ghostel-buffer-name))) + (let ((b (get-buffer-create eat-buffer-name))) (push (buffer-name b) ,calls) b))) - ((symbol-function 'ghostel-send-string) - (lambda (s) (push s ,strings)))) + ((symbol-function 'cj/--ai-term-send-string) + (lambda (_buf s) (push s ,strings)))) ,@body)))) (defun test-ai-term--cleanup (name) @@ -55,33 +53,33 @@ was asked to create), :strings (sent strings), :default-dir. The mocked (kill-buffer name))) (ert-deftest test-ai-term--show-or-create-creates-when-buffer-missing () - "Normal: no existing buffer -> ghostel called once, launch cmd + newline -sent, the project recorded at the front of the MRU list." + "Normal: no existing buffer -> eat called once, launch cmd + newline sent, +the project recorded at the front of the MRU list." (let ((name "agent [normal-create-test]") (cj/--ai-term-mru nil)) (test-ai-term--cleanup name) (unwind-protect - (test-ai-term--with-mock-ghostel (:calls calls :strings strings - :default-dir ddir) + (test-ai-term--with-mock-eat (:calls calls :strings strings + :default-dir ddir) (cj/--ai-term-show-or-create "/tmp/some-project" name) (should (equal calls (list name))) - (should (equal (reverse strings) - (list (cj/--ai-term-launch-command "/tmp/some-project") - "\n"))) + (should (equal strings + (list (concat (cj/--ai-term-launch-command "/tmp/some-project") + "\n")))) (should (equal ddir "/tmp/some-project")) (should (equal (car cj/--ai-term-mru) "/tmp/some-project"))) (test-ai-term--cleanup name)))) (ert-deftest test-ai-term--show-or-create-displays-existing-when-process-live () - "Normal: buffer exists with live process -> ghostel not called." + "Normal: buffer exists with live process -> eat not called." (let ((name "agent [reuse-test]")) (test-ai-term--cleanup name) (unwind-protect (let ((buf (get-buffer-create name))) (cl-letf (((symbol-function 'cj/--ai-term-process-live-p) (lambda (b) (and (eq b buf) t)))) - (test-ai-term--with-mock-ghostel (:calls calls :strings strings - :default-dir _ddir) + (test-ai-term--with-mock-eat (:calls calls :strings strings + :default-dir _ddir) (cj/--ai-term-show-or-create "/tmp/reuse" name) (should (null calls)) (should (null strings))))) @@ -95,27 +93,27 @@ sent, the project recorded at the front of the MRU list." (let ((stale (get-buffer-create name))) (cl-letf (((symbol-function 'cj/--ai-term-process-live-p) (lambda (_b) nil))) - (test-ai-term--with-mock-ghostel (:calls calls :strings strings - :default-dir _ddir) + (test-ai-term--with-mock-eat (:calls calls :strings strings + :default-dir _ddir) (cj/--ai-term-show-or-create "/tmp/dead" name) (should (equal calls (list name))) - (should (equal (reverse strings) - (list (cj/--ai-term-launch-command "/tmp/dead") - "\n"))) + (should (equal strings + (list (concat (cj/--ai-term-launch-command "/tmp/dead") + "\n")))) (should-not (buffer-live-p stale))))) (test-ai-term--cleanup name)))) (ert-deftest test-ai-term--show-or-create-preserves-selected-window () - "Regression: ghostel's same-window switch must not bury the dashboard. + "Regression: eat's same-window switch must not bury the dashboard. -Real `ghostel' switches the selected window to its buffer as a side-effect of +Real `eat' switches the selected window to its buffer as a side-effect of construction. On a fresh-boot frame (one window showing the dashboard), that side-effect would otherwise leave the original window pointing at the new -agent buffer. The wrapper runs `(ghostel)' inside `save-window-excursion' so -the original window state is restored before `display-buffer' fires, leaving -the dashboard put and letting the alist place agent into a fresh split. +agent buffer. The wrapper runs `(eat)' inside `save-window-excursion' so the +original window state is restored before `display-buffer' fires, leaving the +dashboard put and letting the alist place agent into a fresh split. -This test stubs `ghostel' to mimic the same-window side-effect and asserts the +This test stubs `eat' to mimic the same-window side-effect and asserts the originally-selected window still shows its original buffer afterward." (let ((agent-name "agent [preserve-window-test]") (orig-name "*test-original-buffer*")) @@ -128,24 +126,24 @@ originally-selected window still shows its original buffer afterward." (orig-win (selected-window))) (set-window-buffer orig-win orig-buf) (cl-letf - (((symbol-function 'ghostel) - (lambda (&optional _arg) - (let ((buf (get-buffer-create ghostel-buffer-name))) + (((symbol-function 'eat) + (lambda (&optional _program _arg) + (let ((buf (get-buffer-create eat-buffer-name))) (set-window-buffer (selected-window) buf) buf))) - ((symbol-function 'ghostel-send-string) - (lambda (_s) nil))) + ((symbol-function 'cj/--ai-term-send-string) + (lambda (_buf _s) nil))) (cj/--ai-term-show-or-create "/tmp/preserve" agent-name) (should (eq (window-buffer orig-win) orig-buf))))) (test-ai-term--cleanup agent-name) (when (get-buffer orig-name) (kill-buffer orig-name))))) (ert-deftest test-ai-term--show-or-create-returns-buffer () - "Normal: return value is the ghostel buffer named after the project." + "Normal: return value is the eat buffer named after the project." (let ((name "agent [return-test]")) (test-ai-term--cleanup name) (unwind-protect - (test-ai-term--with-mock-ghostel (:calls _c :strings _s :default-dir _d) + (test-ai-term--with-mock-eat (:calls _c :strings _s :default-dir _d) (let ((result (cj/--ai-term-show-or-create "/tmp/return" name))) (should (bufferp result)) (should (equal (buffer-name result) name)))) diff --git a/tests/test-ai-term--single-window-toggle.el b/tests/test-ai-term--single-window-toggle.el index aa507f032..bc6de89f8 100644 --- a/tests/test-ai-term--single-window-toggle.el +++ b/tests/test-ai-term--single-window-toggle.el @@ -19,7 +19,7 @@ (add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) (add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) (require 'ai-term) -(require 'testutil-ghostel-buffers) +(require 'testutil-terminal-buffers) ;;; Normal Cases diff --git a/tests/test-auto-dim-config.el b/tests/test-auto-dim-config.el index 532e7dfae..2686b88f3 100644 --- a/tests/test-auto-dim-config.el +++ b/tests/test-auto-dim-config.el @@ -8,8 +8,9 @@ ;; in ~/code and may be absent on a clean checkout. ;; ;; The vterm dim-integration tests were removed when the terminal engine moved -;; to ghostel: ghostel bakes its palette per-terminal (no per-window color -;; hook), so terminal buffers no longer participate in window dimming. +;; off vterm. EAT (the current engine) renders in real Emacs faces and uses the +;; `default' face for its background, so terminal buffers dim like any other +;; buffer with no dedicated integration. ;;; Code: diff --git a/tests/test-dashboard-config-launchers.el b/tests/test-dashboard-config-launchers.el index a9a871979..e7e5dcd53 100644 --- a/tests/test-dashboard-config-launchers.el +++ b/tests/test-dashboard-config-launchers.el @@ -86,7 +86,7 @@ Slack, Linear, and Signal sharing the last row." (let ((map (make-sparse-keymap)) (calls nil)) (cl-letf (((symbol-function 'projectile-switch-project) (lambda (&rest _) (push 'code calls))) ((symbol-function 'dirvish) (lambda (&rest _) (push 'files calls))) - ((symbol-function 'ghostel) (lambda (&rest _) (push 'term calls))) + ((symbol-function 'cj/term-toggle) (lambda (&rest _) (push 'term calls))) ((symbol-function 'cj/main-agenda-display) (lambda (&rest _) (push 'agenda calls))) ((symbol-function 'cj/elfeed-open) (lambda (&rest _) (push 'feeds calls))) ((symbol-function 'calibredb) (lambda (&rest _) (push 'books calls))) diff --git a/tests/test-dirvish-config--dired-keys.el b/tests/test-dirvish-config--dired-keys.el new file mode 100644 index 000000000..2df0e8db6 --- /dev/null +++ b/tests/test-dirvish-config--dired-keys.el @@ -0,0 +1,23 @@ +;;; test-dirvish-config--dired-keys.el --- dired d=diff / D=delete bindings -*- lexical-binding: t; -*- + +;;; Commentary: +;; Regression: d and D in dired (and dirvish, which uses dired-mode-map) are the +;; diff and delete pair, matching the convention under C-; b and in ibuffer. A +;; mismatch -- or a swapped which-key label -- once led to deleting a file while +;; trying to diff it. + +;;; Code: + +(require 'ert) +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'dired) +(require 'dirvish-config) + +(ert-deftest test-dirvish-dired-d-diffs-D-deletes () + "Normal: dired d runs the ediff diff and D deletes, matching the d=diff / +D=delete convention used under C-; b and in ibuffer." + (should (eq (keymap-lookup dired-mode-map "d") #'cj/dired-ediff-files)) + (should (eq (keymap-lookup dired-mode-map "D") #'dired-do-delete))) + +(provide 'test-dirvish-config--dired-keys) +;;; test-dirvish-config--dired-keys.el ends here diff --git a/tests/test-eshell-config--prompt.el b/tests/test-eshell-config--prompt.el new file mode 100644 index 000000000..7073c7e0b --- /dev/null +++ b/tests/test-eshell-config--prompt.el @@ -0,0 +1,75 @@ +;;; test-eshell-config--prompt.el --- Tests for eshell prompt helpers -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the pure prompt-segment helpers added for zsh parity: the +;; .git/HEAD branch reader and the exit-status segment. + +;;; Code: + +(require 'ert) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'eshell-config) + +(defvar eshell-last-command-status) ; declared special for the status tests + +;;; cj/--eshell-git-branch + +(ert-deftest test-eshell-git-branch-reads-head () + "Normal: a .git/HEAD pointing at a branch returns the branch name." + (let ((dir (make-temp-file "esh-git-" t))) + (unwind-protect + (progn + (make-directory (expand-file-name ".git" dir)) + (with-temp-file (expand-file-name ".git/HEAD" dir) + (insert "ref: refs/heads/feature-x\n")) + (let ((default-directory (file-name-as-directory dir))) + (should (equal (cj/--eshell-git-branch) "feature-x")))) + (delete-directory dir t)))) + +(ert-deftest test-eshell-git-branch-no-repo-nil () + "Boundary: a directory with no .git returns nil." + (let ((dir (make-temp-file "esh-nogit-" t))) + (unwind-protect + (let ((default-directory (file-name-as-directory dir))) + (should-not (cj/--eshell-git-branch))) + (delete-directory dir t)))) + +(ert-deftest test-eshell-git-branch-detached-nil () + "Boundary: a detached HEAD (a raw SHA, no ref) returns nil." + (let ((dir (make-temp-file "esh-detached-" t))) + (unwind-protect + (progn + (make-directory (expand-file-name ".git" dir)) + (with-temp-file (expand-file-name ".git/HEAD" dir) + (insert "a1b2c3d4e5f6\n")) + (let ((default-directory (file-name-as-directory dir))) + (should-not (cj/--eshell-git-branch)))) + (delete-directory dir t)))) + +(ert-deftest test-eshell-git-branch-remote-skipped () + "Boundary: a remote default-directory is skipped (no TRAMP read)." + (let ((default-directory "/ssh:host:/some/path/")) + (should-not (cj/--eshell-git-branch)))) + +;;; cj/--eshell-prompt-status-segment + +(ert-deftest test-eshell-prompt-status-zero-empty () + "Normal: a zero exit status yields an empty segment." + (let ((eshell-last-command-status 0)) + (should (equal (cj/--eshell-prompt-status-segment) "")))) + +(ert-deftest test-eshell-prompt-status-nonzero-bracketed () + "Normal: a non-zero exit status is shown in brackets." + (let ((eshell-last-command-status 1)) + (should (equal (cj/--eshell-prompt-status-segment) " [1]"))) + (let ((eshell-last-command-status 130)) + (should (equal (cj/--eshell-prompt-status-segment) " [130]")))) + +(ert-deftest test-eshell-prompt-status-unset-empty () + "Boundary: an unset status yields an empty segment, no error." + (let ((eshell-last-command-status nil)) + (should (equal (cj/--eshell-prompt-status-segment) "")))) + +(provide 'test-eshell-config--prompt) +;;; test-eshell-config--prompt.el ends here diff --git a/tests/test-external-open-commands.el b/tests/test-external-open-commands.el index c0c83a340..3d8adc15e 100644 --- a/tests/test-external-open-commands.el +++ b/tests/test-external-open-commands.el @@ -81,8 +81,9 @@ ;;; cj/find-file-auto (ert-deftest test-external-open-find-file-auto-routes-media-externally () - "Normal: a `.mp4' filename (in `default-open-extensions') triggers -`cj/xdg-open' instead of the original `find-file'." + "Normal: a non-video external extension (`.docx', in +`default-open-extensions') triggers `cj/xdg-open' instead of the original +`find-file'." (let ((opened nil) (orig-called nil)) (cl-letf (((symbol-function 'cj/xdg-open) @@ -90,8 +91,23 @@ ;; orig-fun replacement -- shouldn't run for a routed extension. ((symbol-function 'cj/find-file-auto--orig-stub) (lambda (&rest _) (setq orig-called t)))) - (cj/find-file-auto #'cj/find-file-auto--orig-stub "/tmp/video.mp4")) - (should (equal opened "/tmp/video.mp4")) + (cj/find-file-auto #'cj/find-file-auto--orig-stub "/tmp/report.docx")) + (should (equal opened "/tmp/report.docx")) + (should-not orig-called))) + +(ert-deftest test-external-open-find-file-auto-routes-video-to-looping-player () + "Normal: a video filename triggers `cj/open-video-looping', not `cj/xdg-open' +or the original `find-file'." + (let ((looped nil) (xdg nil) (orig-called nil)) + (cl-letf (((symbol-function 'cj/open-video-looping) + (lambda (file) (setq looped file))) + ((symbol-function 'cj/xdg-open) + (lambda (_) (setq xdg t))) + ((symbol-function 'cj/find-file-auto--orig-stub) + (lambda (&rest _) (setq orig-called t)))) + (cj/find-file-auto #'cj/find-file-auto--orig-stub "/tmp/clip.mp4")) + (should (equal looped "/tmp/clip.mp4")) + (should-not xdg) (should-not orig-called))) (ert-deftest test-external-open-find-file-auto-passes-through-text-files () @@ -116,5 +132,66 @@ (cj/find-file-auto #'cj/find-file-auto--orig-stub nil)) (should orig-called))) +;;; cj/--video-file-p + +(ert-deftest test-external-open-video-file-p-matches-video () + "Normal: common video extensions match, case-insensitively." + (should (cj/--video-file-p "/tmp/a.mp4")) + (should (cj/--video-file-p "/tmp/a.mkv")) + (should (cj/--video-file-p "/tmp/a.webm")) + (should (cj/--video-file-p "/tmp/A.MP4"))) + +(ert-deftest test-external-open-video-file-p-rejects-non-video () + "Boundary: audio, docs, and nil do not match." + (should-not (cj/--video-file-p "/tmp/a.mp3")) + (should-not (cj/--video-file-p "/tmp/a.txt")) + (should-not (cj/--video-file-p "/tmp/a.docx")) + (should-not (cj/--video-file-p nil))) + +;;; cj/--video-open-arglist + +(ert-deftest test-external-open-video-arglist-appends-file-after-args () + "Normal: the player args precede the file in the argument list." + (let ((cj/video-open-args '("--loop-file=inf"))) + (should (equal (cj/--video-open-arglist "/tmp/a.mp4") + '("--loop-file=inf" "/tmp/a.mp4"))))) + +(ert-deftest test-external-open-video-arglist-respects-custom-args () + "Boundary: custom args are honored; empty args yields just the file." + (let ((cj/video-open-args '("--loop=inf" "--mute=yes"))) + (should (equal (cj/--video-open-arglist "/tmp/a.mkv") + '("--loop=inf" "--mute=yes" "/tmp/a.mkv")))) + (let ((cj/video-open-args nil)) + (should (equal (cj/--video-open-arglist "/tmp/a.mkv") '("/tmp/a.mkv"))))) + +;;; cj/open-video-looping + +(ert-deftest test-external-open-video-looping-calls-player-with-loop-args () + "Normal: posix path calls the player with loop args + file, async (no wait)." + (let ((tmp (make-temp-file "test-ext-video-" nil ".mp4")) + (call nil)) + (unwind-protect + (cl-letf (((symbol-function 'env-windows-p) (lambda () nil)) + ((symbol-function 'call-process) + (lambda (prog _infile dest _disp &rest args) + (setq call (list prog dest args)) + 0))) + (let ((cj/video-open-command "mpv") + (cj/video-open-args '("--loop-file=inf"))) + (cj/open-video-looping tmp))) + (delete-file tmp)) + (should (equal (nth 0 call) "mpv")) + (should (equal (nth 1 call) 0)) ; async destination: don't wait + (should (member "--loop-file=inf" (nth 2 call))) + (should (cl-find-if (lambda (a) (and (stringp a) + (string-match-p "\\.mp4\\'" a))) + (nth 2 call))))) + +(ert-deftest test-external-open-video-looping-errors-when-no-file () + "Error: a buffer with no associated file signals user-error." + (with-temp-buffer + (cl-letf (((symbol-function 'cj/file-from-context) (lambda (_) nil))) + (should-error (cj/open-video-looping) :type 'user-error)))) + (provide 'test-external-open-commands) ;;; test-external-open-commands.el ends here diff --git a/tests/test-init-module-headers.el b/tests/test-init-module-headers.el index 478819b89..22dec1d5f 100644 --- a/tests/test-init-module-headers.el +++ b/tests/test-init-module-headers.el @@ -129,7 +129,6 @@ "tramp-config" "transcription-config" "video-audio-recording" - "term-config" "weather-config" "wrap-up") "Modules annotated with the load-graph header contract. diff --git a/tests/test-system-utils-commands.el b/tests/test-system-utils-commands.el index b7b61dc22..6f2099a24 100644 --- a/tests/test-system-utils-commands.el +++ b/tests/test-system-utils-commands.el @@ -90,5 +90,14 @@ and lands in a dedicated output buffer." (should saved) (should killed))) +;;; ibuffer delete/diff keybinding swap + +(ert-deftest test-system-utils-ibuffer-d-diffs-D-deletes () + "Normal: in the ibuffer list, d diffs the buffer at point against its file and +D marks it for deletion (the swap of ibuffer's default d/= bindings)." + (require 'ibuffer) + (should (eq (keymap-lookup ibuffer-mode-map "d") #'ibuffer-diff-with-file)) + (should (eq (keymap-lookup ibuffer-mode-map "D") #'ibuffer-mark-for-delete))) + (provide 'test-system-utils-commands) ;;; test-system-utils-commands.el ends here diff --git a/tests/test-term-config--f8-in-term.el b/tests/test-term-config--f8-in-term.el deleted file mode 100644 index 6cee4ff46..000000000 --- a/tests/test-term-config--f8-in-term.el +++ /dev/null @@ -1,42 +0,0 @@ -;;; test-term-config--f8-in-term.el --- F8 reaches Emacs from inside a ghostel buffer -*- lexical-binding: t; -*- - -;;; Commentary: -;; <f8> is a global binding (`cj/main-agenda-display', set in org-agenda-config). -;; ghostel's semi-char mode forwards every key NOT in `ghostel-keymap-exceptions' -;; to the terminal program, so a plain <f8> typed while point is in a ghostel -;; buffer would be sent to the program instead of opening the agenda. Unlike the -;; F9 family, F8 is NOT re-bound in `ghostel-mode-map' -- it simply falls through -;; to the global map once the semi-char map stops forwarding it, so the only -;; wiring term-config.el adds is the keymap-exceptions entry plus the rebuild. -;; These tests require ghostel (so term-config's `with-eval-after-load' fires) -;; BEFORE term-config, then confirm the exception landed and the rebuilt -;; semi-char map no longer forwards <f8>. `(require 'ghostel)' does not load the -;; native module, so this stays light. - -;;; Code: - -(require 'ert) -(require 'package) - -(setq package-user-dir (expand-file-name "elpa" user-emacs-directory)) -(package-initialize) -(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) -(require 'ghostel) -(require 'term-config) - -(ert-deftest test-term-config-f8-in-keymap-exceptions () - "Regression: <f8> is in `ghostel-keymap-exceptions' so semi-char mode lets it -reach Emacs instead of forwarding it to the terminal program. This is what lets -the global agenda binding work from inside a ghostel buffer." - (should (member "<f8>" ghostel-keymap-exceptions))) - -(ert-deftest test-term-config-f8-not-forwarded-by-semi-char-map () - "Regression: the rebuilt semi-char map must no longer forward <f8> to the pty. -`add-to-list' updates the exceptions list but not the already-built map -- only -`ghostel--rebuild-semi-char-keymap' (run in term-config's :init) drops the -forwarding binding so <f8> falls through to the global agenda command." - (should-not (eq (keymap-lookup ghostel-semi-char-mode-map "<f8>") - 'ghostel--send-event))) - -(provide 'test-term-config--f8-in-term) -;;; test-term-config--f8-in-term.el ends here diff --git a/tests/test-term-tmux-history.el b/tests/test-term-tmux-history.el index 0ea7cf37d..a5f5c93cd 100644 --- a/tests/test-term-tmux-history.el +++ b/tests/test-term-tmux-history.el @@ -1,14 +1,13 @@ -;;; test-term-tmux-history.el --- Tests for term-config tmux history + menu UX -*- lexical-binding: t; -*- +;;; test-term-tmux-history.el --- Tests for the EAT terminal copy-mode + tmux history -*- lexical-binding: t; -*- ;;; Commentary: -;; Exercises the term-config (ghostel) terminal UX: the Emacs-owned tmux -;; history buffer, the copy-mode-dwim engine pick, the tmux pane-id / -;; attached-client predicates, and the C-; x menu bindings. +;; Exercises the terminal UX carried into eat-config for the EAT agent +;; terminals: the Emacs-owned tmux history buffer, the copy-mode-dwim engine +;; pick, the tmux pane-id / attached-client predicates, and the C-; x menu +;; bindings. Agents run EAT over tmux, so copy-mode is tmux's own copy-mode. ;; -;; ghostel is required (which defines `ghostel-mode-map' / -;; `ghostel-keymap-exceptions' and lets term-config's `with-eval-after-load' -;; fire) before term-config. `(require 'ghostel)' does not load the native -;; module; tmux is mocked via `process-file', so nothing spawns. +;; eat is required (so eat-config's `with-eval-after-load' fires for the C-<up> +;; bind) before eat-config; tmux is mocked via `process-file', so nothing spawns. ;;; Code: @@ -21,9 +20,9 @@ (add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) (add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) (setq load-prefer-newer t) -(require 'ghostel) -(require 'term-config) -(require 'testutil-ghostel-buffers) +(require 'eat) +(require 'eat-config) +(require 'testutil-terminal-buffers) (defmacro test-term-tmux-history--with-tmux-mock (responses &rest body) "Run BODY with `process-file' mocked for tmux RESPONSES. @@ -51,6 +50,8 @@ RESPONSES is an alist of (ARGS EXIT-CODE OUTPUT)." exit-code)))) ,@body))) +;;; tmux helpers + (ert-deftest test-term-tmux-history--pane-id-for-tty-matches-client () "Normal: current terminal pty maps to the active pane for that tmux client." (test-term-tmux-history--with-tmux-mock @@ -66,9 +67,32 @@ RESPONSES is an alist of (ARGS EXIT-CODE OUTPUT)." (should (equal (cj/term--tmux-capture-pane "%8") "first line\nsecond line\n")))) +(ert-deftest test-term-current-tmux-pane-id-rejects-non-eat-buffer () + "Error: pane-id lookup refuses a buffer that is not in `eat-mode'." + (with-temp-buffer + (should-error (cj/term--current-tmux-pane-id) :type 'user-error))) + +(ert-deftest test-term-current-tmux-pane-id-accepts-agent-named-buffer () + "Normal: an agent-named eat buffer resolves by process TTY, not buffer name." + (let ((agent (cj/test--make-fake-eat-buffer "agent [emacs.d]"))) + (unwind-protect + (with-current-buffer agent + (cl-letf (((symbol-function 'get-buffer-process) + (lambda (_buffer) 'fake-process)) + ((symbol-function 'process-tty-name) + (lambda (_process &rest _) "/dev/pts/8"))) + (test-term-tmux-history--with-tmux-mock + '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0 + "/dev/pts/1\t%1\n/dev/pts/8\t%8\n")) + (should (equal (cj/term--current-tmux-pane-id) "%8"))))) + (when (buffer-live-p agent) + (kill-buffer agent))))) + +;;; tmux history buffer + (ert-deftest test-term-tmux-history-open-renders-read-only-history-buffer () - "Normal: command renders tmux history in a normal Emacs buffer." - (let ((origin (cj/test--make-fake-ghostel-buffer "*test-term-history-origin*"))) + "Normal: the command renders tmux history in a normal Emacs buffer." + (let ((origin (cj/test--make-fake-eat-buffer "*test-term-history-origin*"))) (unwind-protect (save-window-excursion (switch-to-buffer origin) @@ -90,41 +114,8 @@ RESPONSES is an alist of (ARGS EXIT-CODE OUTPUT)." (when (buffer-live-p origin) (kill-buffer origin))))) -(ert-deftest test-term-tmux-history-replaces-origin-buffer-in-same-window () - "Normal: the history view replaces the origin in the selected window. - -`cj/term-tmux-history' uses `switch-to-buffer' so reading scrollback keeps -the terminal's frame slot rather than splitting or popping a new window." - (let ((origin (cj/test--make-fake-ghostel-buffer "*test-term-history-inplace*"))) - (unwind-protect - (save-window-excursion - (delete-other-windows) - (switch-to-buffer origin) - (let ((win (selected-window))) - (should (eq (window-buffer win) origin)) - (should (one-window-p)) - (cl-letf (((symbol-function 'get-buffer-process) - (lambda (_buffer) 'fake-process)) - ((symbol-function 'process-tty-name) - (lambda (_process &rest _) "/dev/pts/8"))) - (test-term-tmux-history--with-tmux-mock - '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0 - "/dev/pts/8\t%8\n") - (("capture-pane" "-p" "-J" "-S" "-" "-E" "-" "-t" "%8") 0 - "scrollback line\n")) - (cj/term-tmux-history))) - (should (one-window-p)) - (should (eq (selected-window) win)) - (should (string-prefix-p - "*terminal tmux history:" - (buffer-name (window-buffer win)))))) - (cj/test--kill-buffers-matching-prefix "*terminal tmux history") - (when (buffer-live-p origin) - (kill-buffer origin))))) - (ert-deftest test-term-tmux-history-quit-returns-to-origin () - "Normal: q / <escape> / C-g (cj/term-tmux-history-quit) kills the history -buffer and restores the origin buffer, window, and point." + "Normal: quit kills the history buffer and restores origin buffer/window/point." (let ((origin (get-buffer-create "*test-term-history-return*"))) (unwind-protect (let ((history (get-buffer-create "*terminal tmux history: test*"))) @@ -149,10 +140,8 @@ buffer and restores the origin buffer, window, and point." (kill-buffer origin))))) (ert-deftest test-term-tmux-history-mode-keymap () - "Normal: in the history buffer M-w copies without quitting; q, <escape>, -and C-g quit back to the terminal; RET is left unbound (no special exit)." - (should (eq (keymap-lookup cj/term-tmux-history-mode-map "M-w") - #'kill-ring-save)) + "Normal: M-w copies; q/<escape>/C-g quit; RET is left unbound." + (should (eq (keymap-lookup cj/term-tmux-history-mode-map "M-w") #'kill-ring-save)) (should (eq (keymap-lookup cj/term-tmux-history-mode-map "q") #'cj/term-tmux-history-quit)) (should (eq (keymap-lookup cj/term-tmux-history-mode-map "<escape>") @@ -161,50 +150,11 @@ and C-g quit back to the terminal; RET is left unbound (no special exit)." #'cj/term-tmux-history-quit)) (should-not (keymap-lookup cj/term-tmux-history-mode-map "RET"))) -(ert-deftest test-term-keymap-includes-history-and-copy-bindings () - "Normal: the personal terminal map owns the high-level UX commands, and C-; -reaches Emacs inside ghostel buffers so the prefix works there." - (should (member "C-;" ghostel-keymap-exceptions)) - (should (eq (keymap-lookup cj/custom-keymap "x h") #'cj/term-tmux-history)) - (should (eq (keymap-lookup cj/custom-keymap "x c") #'cj/term-copy-mode-dwim)) - (should (equal (keymap-lookup ghostel-mode-map "C-;") cj/custom-keymap)) - (should (eq (keymap-lookup ghostel-mode-map "C-; x h") #'cj/term-tmux-history)) - (should (eq (keymap-lookup ghostel-mode-map "C-; x c") #'cj/term-copy-mode-dwim))) - -(ert-deftest test-term-keymap-prompt-navigation () - "Normal: n/p navigate prompts, capital N creates a new terminal buffer." - (should (eq (keymap-lookup cj/custom-keymap "x n") #'ghostel-next-prompt)) - (should (eq (keymap-lookup cj/custom-keymap "x p") #'ghostel-previous-prompt)) - (should (eq (keymap-lookup cj/custom-keymap "x N") #'ghostel))) - -(ert-deftest test-term-current-tmux-pane-id-rejects-non-ghostel-buffer () - "Error: pane-id lookup refuses a buffer that is not in `ghostel-mode'." - (with-temp-buffer - (should-error (cj/term--current-tmux-pane-id) :type 'user-error))) - -(ert-deftest test-term-current-tmux-pane-id-accepts-agent-named-buffer () - "Normal: an agent-named ghostel buffer resolves by process TTY. - -The pane lookup keys off the live process TTY, never the buffer name, so a -buffer named `agent [repo]' (ai-term.el's naming) resolves like any other -ghostel-mode terminal." - (let ((agent (cj/test--make-fake-ghostel-buffer "agent [emacs.d]"))) - (unwind-protect - (with-current-buffer agent - (cl-letf (((symbol-function 'get-buffer-process) - (lambda (_buffer) 'fake-process)) - ((symbol-function 'process-tty-name) - (lambda (_process &rest _) "/dev/pts/8"))) - (test-term-tmux-history--with-tmux-mock - '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0 - "/dev/pts/1\t%1\n/dev/pts/8\t%8\n")) - (should (equal (cj/term--current-tmux-pane-id) "%8"))))) - (when (buffer-live-p agent) - (kill-buffer agent))))) +;;; in-tmux-p predicate (ert-deftest test-term-in-tmux-p-true-when-client-attached () "Normal: predicate returns t when tmux reports a client for our tty." - (let ((agent (cj/test--make-fake-ghostel-buffer "agent [emacs.d]"))) + (let ((agent (cj/test--make-fake-eat-buffer "agent [emacs.d]"))) (unwind-protect (with-current-buffer agent (cl-letf (((symbol-function 'get-buffer-process) @@ -218,25 +168,18 @@ ghostel-mode terminal." (when (buffer-live-p agent) (kill-buffer agent))))) -(ert-deftest test-term-in-tmux-p-nil-when-no-matching-client () - "Boundary: predicate returns nil when tmux runs but our tty has no client." - (let ((agent (cj/test--make-fake-ghostel-buffer "agent [emacs.d]"))) - (unwind-protect - (with-current-buffer agent - (cl-letf (((symbol-function 'get-buffer-process) - (lambda (_buffer) 'fake-process)) - ((symbol-function 'process-tty-name) - (lambda (_process &rest _) "/dev/pts/8"))) - (test-term-tmux-history--with-tmux-mock - '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0 - "/dev/pts/1\t%1\n")) - (should-not (cj/term--in-tmux-p))))) - (when (buffer-live-p agent) - (kill-buffer agent))))) +(ert-deftest test-term-in-tmux-p-nil-when-not-eat-mode () + "Boundary: predicate refuses non-eat buffers without calling tmux." + (with-temp-buffer + (let ((tmux-called nil)) + (cl-letf (((symbol-function 'process-file) + (lambda (&rest _) (setq tmux-called t) 0))) + (should-not (cj/term--in-tmux-p)) + (should-not tmux-called))))) (ert-deftest test-term-in-tmux-p-nil-when-tmux-fails () "Error: predicate swallows tmux failures and returns nil." - (let ((agent (cj/test--make-fake-ghostel-buffer "agent [emacs.d]"))) + (let ((agent (cj/test--make-fake-eat-buffer "agent [emacs.d]"))) (unwind-protect (with-current-buffer agent (cl-letf (((symbol-function 'get-buffer-process) @@ -250,117 +193,33 @@ ghostel-mode terminal." (when (buffer-live-p agent) (kill-buffer agent))))) -(ert-deftest test-term-in-tmux-p-nil-when-not-ghostel-mode () - "Boundary: predicate refuses non-ghostel buffers without calling tmux." - (with-temp-buffer - (let ((tmux-called nil)) - (cl-letf (((symbol-function 'process-file) - (lambda (&rest _) (setq tmux-called t) 0))) - (should-not (cj/term--in-tmux-p)) - (should-not tmux-called))))) +;;; copy-mode (tmux path -- the agent terminal case) (ert-deftest test-term-copy-mode-dwim-sends-tmux-prefix-when-attached () - "Normal: with tmux attached, dwim writes C-b [ then C-a into the pty so -tmux enters its own copy-mode and lands the cursor at the start of the -line. Without the trailing C-a the cursor inherits the live column (far -right after a prompt) and scrolling up runs up the right edge; start-of-line -puts it at column 0 so it runs up the left." - (let ((agent (cj/test--make-fake-ghostel-buffer "agent [emacs.d]")) - (sent nil) - (copy-mode-called nil)) + "Normal: with tmux attached, dwim writes C-b [ then C-a into the pty so tmux +enters copy-mode with the cursor at column 0." + (let ((agent (cj/test--make-fake-eat-buffer "agent [emacs.d]")) + (sent nil)) (unwind-protect (with-current-buffer agent (cl-letf (((symbol-function 'get-buffer-process) (lambda (_buffer) 'fake-process)) ((symbol-function 'process-tty-name) (lambda (_process &rest _) "/dev/pts/8")) - ((symbol-function 'ghostel-send-string) - (lambda (s) (push s sent))) - ((symbol-function 'ghostel-copy-mode) - (lambda () (setq copy-mode-called t)))) + ((symbol-function 'cj/--term-send-string) + (lambda (s) (push s sent)))) (test-term-tmux-history--with-tmux-mock '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0 "/dev/pts/8\t%8\n")) (cj/term-copy-mode-dwim) - (should (equal sent '("\C-b[\C-a"))) - (should-not copy-mode-called)))) - (when (buffer-live-p agent) - (kill-buffer agent))))) - -(ert-deftest test-term-copy-mode-dwim-falls-back-without-tmux () - "Boundary: without tmux, dwim calls `ghostel-copy-mode' then moves point -to the start of the line and sends nothing to the pty. The -`beginning-of-line' must run after `ghostel-copy-mode' so it repositions -inside the copy view; column 0 keeps the cursor on the left edge while -scrolling, parity with the tmux branch's trailing C-a." - (let ((agent (cj/test--make-fake-ghostel-buffer "agent [emacs.d]")) - (sent nil) - (dwim-order nil)) - (unwind-protect - (with-current-buffer agent - (cl-letf (((symbol-function 'get-buffer-process) - (lambda (_buffer) 'fake-process)) - ((symbol-function 'process-tty-name) - (lambda (_process &rest _) "/dev/pts/8")) - ((symbol-function 'ghostel-send-string) - (lambda (s) (push s sent))) - ((symbol-function 'ghostel-copy-mode) - (lambda () (push 'copy-mode dwim-order))) - ((symbol-function 'beginning-of-line) - (lambda (&optional _n) (push 'beginning-of-line dwim-order)))) - (test-term-tmux-history--with-tmux-mock - '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 1 - "no server running")) - (cj/term-copy-mode-dwim) - (should-not sent) - (should (equal (reverse dwim-order) '(copy-mode beginning-of-line)))))) + (should (equal sent '("\C-b[\C-a")))))) (when (buffer-live-p agent) (kill-buffer agent))))) -(ert-deftest test-term-prefix-and-f12-in-keymap-exceptions () - "Regression: C-; and F12 are in `ghostel-keymap-exceptions' and the rebuilt -semi-char map no longer forwards them to the pty, so the prefix keymap and the -F12 toggle reach Emacs inside ghostel buffers." - (dolist (key '("C-;" "<f12>")) - (should (member key ghostel-keymap-exceptions))) - (should-not (eq (keymap-lookup ghostel-semi-char-mode-map "<f12>") - 'ghostel--send-event))) - -(ert-deftest test-term-window-nav-keys-in-keymap-exceptions () - "Regression: windmove (S-arrows) and buffer-move (C-M-arrows) are in -`ghostel-keymap-exceptions' so they reach Emacs from inside a ghostel buffer -instead of being forwarded to the terminal program." - (dolist (key '("S-<up>" "S-<down>" "S-<left>" "S-<right>" - "C-M-<up>" "C-M-<down>" "C-M-<left>" "C-M-<right>")) - (should (member key ghostel-keymap-exceptions))) - (should-not (eq (keymap-lookup ghostel-semi-char-mode-map "C-M-<left>") - 'ghostel--send-event))) - -(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))) - -(ert-deftest test-term-c-spc-forwarded-not-set-mark () - "Regression: C-SPC is forwarded to the terminal, not bound to the global -`set-mark-command'. ghostel only forwards the `C-@' event, so without this an -Emacs region gets stuck in the ghostel buffer and tmux copy-mode's -begin-selection never starts." - (should (eq (keymap-lookup ghostel-mode-map "C-SPC") #'cj/term-send-C-SPC))) - -;; ----------------------------- copy-mode scroll ------------------------------ - (ert-deftest test-term-copy-mode-up-tmux-enters-then-scrolls-up () "Normal: from a live (non-copy) tmux pane, C-<up> enters copy-mode then sends the up-arrow, so one stroke both enters copy-mode and scrolls up." - (let ((agent (cj/test--make-fake-ghostel-buffer "agent [emacs.d]")) + (let ((agent (cj/test--make-fake-eat-buffer "agent [emacs.d]")) (sent nil)) (unwind-protect (with-current-buffer agent @@ -368,7 +227,7 @@ the up-arrow, so one stroke both enters copy-mode and scrolls up." (lambda (_buffer) 'fake-process)) ((symbol-function 'process-tty-name) (lambda (_process &rest _) "/dev/pts/8")) - ((symbol-function 'ghostel-send-string) + ((symbol-function 'cj/--term-send-string) (lambda (s) (push s sent)))) (test-term-tmux-history--with-tmux-mock '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0 @@ -381,8 +240,8 @@ the up-arrow, so one stroke both enters copy-mode and scrolls up." (ert-deftest test-term-copy-mode-up-tmux-already-in-mode-just-scrolls () "Normal: when the tmux pane is already in copy-mode, C-<up> only sends the -up-arrow -- it does not re-enter (which would reset the cursor)." - (let ((agent (cj/test--make-fake-ghostel-buffer "agent [emacs.d]")) +up-arrow -- it does not re-enter and reset the cursor." + (let ((agent (cj/test--make-fake-eat-buffer "agent [emacs.d]")) (sent nil)) (unwind-protect (with-current-buffer agent @@ -390,7 +249,7 @@ up-arrow -- it does not re-enter (which would reset the cursor)." (lambda (_buffer) 'fake-process)) ((symbol-function 'process-tty-name) (lambda (_process &rest _) "/dev/pts/8")) - ((symbol-function 'ghostel-send-string) + ((symbol-function 'cj/--term-send-string) (lambda (s) (push s sent)))) (test-term-tmux-history--with-tmux-mock '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0 @@ -401,54 +260,61 @@ up-arrow -- it does not re-enter (which would reset the cursor)." (when (buffer-live-p agent) (kill-buffer agent))))) -(ert-deftest test-term-copy-mode-up-nontmux-enters-then-moves-up () - "Boundary: without tmux and not yet in copy-mode, C-<up> enters -ghostel-copy-mode then moves point up a line, sending nothing to the pty." - (with-temp-buffer - (insert "abc\ndef\nghi\n") - (goto-char (point-min)) - (forward-line 2) ; land on line 3 - (let ((sent nil) (entered nil)) - (cl-letf (((symbol-function 'ghostel-send-string) (lambda (s) (push s sent))) - ((symbol-function 'ghostel-copy-mode) (lambda () (setq entered t)))) - (cj/term-copy-mode-up) - (should entered) - (should-not sent) - (should (= (line-number-at-pos) 2)))))) - -(ert-deftest test-term-copy-mode-up-nontmux-already-in-copy-just-moves () - "Normal: when ghostel is already in copy-mode, C-<up> just moves point up -- -it does not call `ghostel-copy-mode' again (which would toggle copy-mode off)." +;;; bindings + +(ert-deftest test-term-keymap-history-and-copy-bindings () + "Normal: the C-; x terminal map owns the tmux-history and copy-mode commands." + (should (eq (keymap-lookup cj/custom-keymap "x h") #'cj/term-tmux-history)) + (should (eq (keymap-lookup cj/custom-keymap "x c") #'cj/term-copy-mode-dwim)) + (should (eq (keymap-lookup cj/custom-keymap "x t") #'cj/term-toggle))) + +(ert-deftest test-term-copy-mode-up-bound-in-eat-semi-char-map () + "Normal: C-<up> enters copy-mode + scrolls up from inside an EAT terminal." + (should (eq (keymap-lookup eat-semi-char-mode-map "C-<up>") + #'cj/term-copy-mode-up))) + +(ert-deftest test-term-escape-bound-as-unified-exit () + "Normal: Escape sends ESC in semi-char mode (cancels tmux copy-mode) and +returns to semi-char from EAT's emacs/char mode -- one exit key for both." + (should (eq (keymap-lookup eat-semi-char-mode-map "<escape>") + #'cj/term-send-escape)) + (should (eq (keymap-lookup eat-mode-map "<escape>") #'eat-semi-char-mode))) + +(ert-deftest test-term-send-escape-writes-esc-to-pty () + "Normal: `cj/term-send-escape' sends a bare ESC to the terminal process." + (let ((sent nil)) + (cl-letf (((symbol-function 'cj/--term-send-string) + (lambda (s) (push s sent)))) + (cj/term-send-escape) + (should (equal sent '("\e")))))) + +(ert-deftest test-term-word-motion-arrows-forwarded-not-window-arrows () + "Normal: C-/M-left/right forward to the terminal (word motion in the program's +input) instead of moving Emacs point; windmove's S-arrows still reach Emacs." + (dolist (key '("C-<left>" "C-<right>" "M-<left>" "M-<right>")) + (should (eq (keymap-lookup eat-semi-char-mode-map key) #'eat-self-input))) + (dolist (key '("S-<left>" "S-<right>")) + (should-not (eq (keymap-lookup eat-semi-char-mode-map key) #'eat-self-input)))) + +(ert-deftest test-term-eat-tame-scroll-sets-minimal-scroll () + "Normal: `cj/--eat-tame-scroll' sets buffer-local minimal-scroll behavior so +the EAT window line-scrolls instead of recentering on full-frame redraws." (with-temp-buffer - (insert "abc\ndef\nghi\n") - (goto-char (point-min)) - (forward-line 2) ; land on line 3 - (setq-local ghostel--input-mode 'copy) - (let ((sent nil) (entered nil)) - (cl-letf (((symbol-function 'ghostel-send-string) (lambda (s) (push s sent))) - ((symbol-function 'ghostel-copy-mode) (lambda () (setq entered t)))) - (cj/term-copy-mode-up) - (should-not entered) - (should-not sent) - (should (= (line-number-at-pos) 2)))))) - -(ert-deftest test-term-copy-mode-only-c-up-bound () - "Normal/Regression: only C-<up> enters copy-mode in ghostel-mode-map; the -other arrows are not bound to it, so they pass through to the terminal." - (should (eq (keymap-lookup ghostel-mode-map "C-<up>") #'cj/term-copy-mode-up)) - (dolist (key '("C-<down>" "C-<left>" "C-<right>" - "M-<up>" "M-<down>" "M-<left>" "M-<right>")) - (should-not (eq (keymap-lookup ghostel-mode-map key) #'cj/term-copy-mode-up)))) - -(ert-deftest test-term-copy-mode-only-c-up-in-keymap-exceptions () - "Regression (C-arrow copy-mode bug): only C-<up> is in -`ghostel-keymap-exceptions'. C-<left>/<right>/<down> are readline word-motion -at the shell prompt and the M-arrows have no copy-mode role, so none are -exceptions -- they reach the terminal program instead of Emacs." - (should (member "C-<up>" ghostel-keymap-exceptions)) - (dolist (key '("C-<down>" "C-<left>" "C-<right>" - "M-<up>" "M-<down>" "M-<left>" "M-<right>")) - (should-not (member key ghostel-keymap-exceptions)))) + (cj/--eat-tame-scroll) + (should (= scroll-conservatively 101)) + (should (= scroll-margin 0)) + (should (null auto-window-vscroll)))) + +(ert-deftest test-term-eat-reset-sgr-at-newline () + "Normal: the SGR-reset advice injects a reset before each newline when enabled +\(containing an unterminated color), and passes output through unchanged when +disabled." + (let ((cj/eat-reset-sgr-at-newline t)) + (should (equal (cj/--eat-reset-sgr-at-newline (list (quote term) "a\nb\n")) + (list (quote term) "a\e[0m\nb\e[0m\n")))) + (let ((cj/eat-reset-sgr-at-newline nil)) + (should (equal (cj/--eat-reset-sgr-at-newline (list (quote term) "a\nb\n")) + (list (quote term) "a\nb\n"))))) (provide 'test-term-tmux-history) ;;; test-term-tmux-history.el ends here diff --git a/tests/test-term-toggle--buffer-filter.el b/tests/test-term-toggle--buffer-filter.el index 2c96ecb38..f56459a06 100644 --- a/tests/test-term-toggle--buffer-filter.el +++ b/tests/test-term-toggle--buffer-filter.el @@ -1,11 +1,12 @@ ;;; test-term-toggle--buffer-filter.el --- Tests for F12's buffer filter -*- lexical-binding: t; -*- ;;; Commentary: -;; Three closely-related helpers determine which terminal buffers F12 -;; manages: the predicate `cj/--term-toggle-buffer-p', the MRU list +;; Three closely-related helpers determine which terminal buffer F12 +;; manages: the predicate `cj/--term-toggle-buffer-p', the list ;; `cj/--term-toggle-buffers', and the per-frame window finder -;; `cj/--term-toggle-displayed-window'. All three exclude agent- -;; prefixed buffers so agent has its own F9 surface. +;; `cj/--term-toggle-displayed-window'. F12 opens eshell (run through EAT via +;; eat-eshell-mode), so it manages eshell-mode buffers. Standalone eat buffers +;; and ai-term's agent buffers (also eat) are NOT F12-managed. ;;; Code: @@ -13,32 +14,40 @@ (add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) (add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) -(require 'term-config) -(require 'testutil-ghostel-buffers) +(require 'eat-config) +(require 'testutil-terminal-buffers) (defun test-term-toggle--cleanup () "Kill leftover agent- and *test-term- prefixed buffers." (cj/test--kill-agent-buffers) (cj/test--kill-test-term-buffers)) -(ert-deftest test-term-toggle--buffer-p-accepts-ghostel-mode () - "Normal: a ghostel-mode buffer with non-agent name qualifies." +(ert-deftest test-term-toggle--buffer-p-accepts-eshell-mode () + "Normal: an eshell-mode buffer qualifies as the F12 terminal." (test-term-toggle--cleanup) - (let ((buf (cj/test--make-fake-ghostel-buffer "*test-term-1*"))) + (let ((buf (cj/test--make-fake-eshell-buffer "*test-term-1*"))) (unwind-protect (should (cj/--term-toggle-buffer-p buf)) (kill-buffer buf)))) +(ert-deftest test-term-toggle--buffer-p-rejects-eat () + "Boundary: a standalone eat buffer is NOT F12-managed (F12 opens eshell)." + (test-term-toggle--cleanup) + (let ((buf (cj/test--make-fake-eat-buffer "*test-term-eat*"))) + (unwind-protect + (should-not (cj/--term-toggle-buffer-p buf)) + (kill-buffer buf)))) + (ert-deftest test-term-toggle--buffer-p-rejects-agent () - "Boundary: agent-prefixed terminal buffers are excluded from F12's set." + "Boundary: ai-term agent buffers are excluded from F12's set." (test-term-toggle--cleanup) - (let ((buf (cj/test--make-fake-ghostel-buffer "agent [project-a]"))) + (let ((buf (cj/test--make-fake-eat-buffer "agent [project-a]"))) (unwind-protect (should-not (cj/--term-toggle-buffer-p buf)) (kill-buffer buf)))) (ert-deftest test-term-toggle--buffer-p-rejects-non-terminal () - "Boundary: a regular buffer (not ghostel-mode, no terminal name prefix) -> nil." + "Boundary: a regular buffer (not eshell-mode) -> nil." (test-term-toggle--cleanup) (let ((buf (get-buffer-create "*test-term-regular*"))) (unwind-protect @@ -48,40 +57,40 @@ (ert-deftest test-term-toggle--buffer-p-rejects-dead-buffer () "Boundary: nil and dead buffers -> nil." (should-not (cj/--term-toggle-buffer-p nil)) - (let ((buf (cj/test--make-fake-ghostel-buffer "*test-term-dead*"))) + (let ((buf (cj/test--make-fake-eshell-buffer "*test-term-dead*"))) (kill-buffer buf) (should-not (cj/--term-toggle-buffer-p buf)))) -(ert-deftest test-term-toggle--buffers-filters-agent () - "Normal: returns terminal buffers but excludes agent-prefixed ones." +(ert-deftest test-term-toggle--buffers-returns-eshell-excludes-others () + "Normal: returns the eshell terminal but not eat/agent buffers." (test-term-toggle--cleanup) - (let ((normal (cj/test--make-fake-ghostel-buffer "*test-term-normal*")) - (agent (cj/test--make-fake-ghostel-buffer "agent [for-test]"))) + (let ((esh (cj/test--make-fake-eshell-buffer "*test-term-esh*")) + (agent (cj/test--make-fake-eat-buffer "agent [for-test]"))) (unwind-protect (let ((result (cj/--term-toggle-buffers))) - (should (memq normal result)) + (should (memq esh result)) (should-not (memq agent result))) - (kill-buffer normal) + (kill-buffer esh) (kill-buffer agent)))) (ert-deftest test-term-toggle--displayed-window-finds-terminal () - "Normal: terminal in a window -> returns that window." + "Normal: the eshell terminal in a window -> returns that window." (test-term-toggle--cleanup) - (let ((vt (cj/test--make-fake-ghostel-buffer "*test-term-shown*"))) + (let ((esh (cj/test--make-fake-eshell-buffer "*test-term-shown*"))) (unwind-protect (save-window-excursion (delete-other-windows) (let ((win (split-window-right))) - (set-window-buffer win vt) + (set-window-buffer win esh) (let ((result (cj/--term-toggle-displayed-window))) (should (windowp result)) - (should (eq (window-buffer result) vt))))) - (kill-buffer vt)))) + (should (eq (window-buffer result) esh))))) + (kill-buffer esh)))) (ert-deftest test-term-toggle--displayed-window-skips-agent () "Boundary: only an agent terminal is displayed -> nil (agent not F12-managed)." (test-term-toggle--cleanup) - (let ((agent (cj/test--make-fake-ghostel-buffer "agent [skip-test]"))) + (let ((agent (cj/test--make-fake-eat-buffer "agent [skip-test]"))) (unwind-protect (save-window-excursion (delete-other-windows) diff --git a/tests/test-term-toggle--dispatch.el b/tests/test-term-toggle--dispatch.el index f13c2840b..43db4c3fe 100644 --- a/tests/test-term-toggle--dispatch.el +++ b/tests/test-term-toggle--dispatch.el @@ -14,8 +14,8 @@ (add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) (add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) -(require 'term-config) -(require 'testutil-ghostel-buffers) +(require 'eat-config) +(require 'testutil-terminal-buffers) (ert-deftest test-term-toggle--dispatch-window-displayed-returns-toggle-off () "Normal: displayed terminal window -> (toggle-off . WIN)." diff --git a/tests/test-term-toggle--display.el b/tests/test-term-toggle--display.el index d6dd33da2..d59d23b15 100644 --- a/tests/test-term-toggle--display.el +++ b/tests/test-term-toggle--display.el @@ -14,7 +14,7 @@ (require 'cl-lib) (add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) -(require 'term-config) +(require 'eat-config) (ert-deftest test-term-toggle--capture-state-records-direction-and-size () "Normal: capture-state writes direction and integer size. diff --git a/tests/testutil-ghostel-buffers.el b/tests/testutil-ghostel-buffers.el deleted file mode 100644 index 52fb27e00..000000000 --- a/tests/testutil-ghostel-buffers.el +++ /dev/null @@ -1,49 +0,0 @@ -;;; testutil-ghostel-buffers.el --- Shared helpers for ghostel/agent buffer tests -*- lexical-binding: t; -*- - -;;; Commentary: -;; Cleanup helpers and a fake-ghostel constructor used across the -;; ai-term and term-toggle test files. Replaces the older -;; testutil-vterm-buffers helpers when the terminal engine moved from -;; vterm to ghostel. - -;;; Code: - -(require 'cl-lib) - -(defun cj/test--call-as-gui (fn) - "Call FN, stubbing `env-terminal-p' to return nil (a GUI frame). - -The terminal refuse-guard was dropped when ghostel replaced vterm (ghostel -renders in TTY frames too), so this no longer gates behavior; it is kept as a -thin passthrough so window-behavior tests written against the old guard keep -working unchanged." - (cl-letf (((symbol-function 'env-terminal-p) (lambda () nil))) - (funcall fn))) - -(defun cj/test--kill-buffers-matching-prefix (prefix) - "Kill all live buffers whose name starts with PREFIX." - (dolist (b (buffer-list)) - (when (string-prefix-p prefix (buffer-name b)) - (kill-buffer b)))) - -(defun cj/test--kill-agent-buffers () - "Kill all live buffers whose name matches the AI-term prefix \"agent [\"." - (cj/test--kill-buffers-matching-prefix "agent [")) - -(defun cj/test--kill-test-term-buffers () - "Kill all live buffers whose name starts with \"*test-term\"." - (cj/test--kill-buffers-matching-prefix "*test-term")) - -(defun cj/test--make-fake-ghostel-buffer (name) - "Return a buffer named NAME with `major-mode' set to `ghostel-mode'. - -Avoids actually launching a ghostel process by setting the mode -buffer-locally. Used by tests that need a buffer satisfying the -ghostel-mode predicate without the side-effects of `(ghostel)'." - (let ((buf (get-buffer-create name))) - (with-current-buffer buf - (setq-local major-mode 'ghostel-mode)) - buf)) - -(provide 'testutil-ghostel-buffers) -;;; testutil-ghostel-buffers.el ends here diff --git a/tests/testutil-terminal-buffers.el b/tests/testutil-terminal-buffers.el new file mode 100644 index 000000000..c2a43a3c7 --- /dev/null +++ b/tests/testutil-terminal-buffers.el @@ -0,0 +1,57 @@ +;;; testutil-terminal-buffers.el --- Shared helpers for terminal/agent buffer tests -*- lexical-binding: t; -*- + +;;; Commentary: +;; Cleanup helpers and fake-terminal-buffer constructors (eat, eshell) used +;; across the ai-term and term-toggle test files. + +;;; Code: + +(require 'cl-lib) + +(defun cj/test--call-as-gui (fn) + "Call FN, stubbing `env-terminal-p' to return nil (a GUI frame). + +The terminal refuse-guard was dropped when the terminal engine moved off vterm +(EAT and eshell render in TTY frames too), so this no longer gates behavior; it +is kept as a thin passthrough so window-behavior tests written against the old +guard keep working unchanged." + (cl-letf (((symbol-function 'env-terminal-p) (lambda () nil))) + (funcall fn))) + +(defun cj/test--kill-buffers-matching-prefix (prefix) + "Kill all live buffers whose name starts with PREFIX." + (dolist (b (buffer-list)) + (when (string-prefix-p prefix (buffer-name b)) + (kill-buffer b)))) + +(defun cj/test--kill-agent-buffers () + "Kill all live buffers whose name matches the AI-term prefix \"agent [\"." + (cj/test--kill-buffers-matching-prefix "agent [")) + +(defun cj/test--kill-test-term-buffers () + "Kill all live buffers whose name starts with \"*test-term\"." + (cj/test--kill-buffers-matching-prefix "*test-term")) + +(defun cj/test--make-fake-eat-buffer (name) + "Return a buffer named NAME with `major-mode' set to `eat-mode'. + +Avoids actually launching an EAT process by setting the mode buffer-locally. +Used by the F12 toggle tests that need a buffer satisfying the eat-mode +predicate without the side-effects of `(eat)'." + (let ((buf (get-buffer-create name))) + (with-current-buffer buf + (setq-local major-mode 'eat-mode)) + buf)) + +(defun cj/test--make-fake-eshell-buffer (name) + "Return a buffer named NAME with `major-mode' set to `eshell-mode'. + +Avoids starting a real eshell by setting the mode buffer-locally. Used by the +F12 toggle tests that need a buffer satisfying the eshell-mode predicate." + (let ((buf (get-buffer-create name))) + (with-current-buffer buf + (setq-local major-mode 'eshell-mode)) + buf)) + +(provide 'testutil-terminal-buffers) +;;; testutil-terminal-buffers.el ends here |
