diff options
Diffstat (limited to 'tests/test-ai-term--show-or-create.el')
| -rw-r--r-- | tests/test-ai-term--show-or-create.el | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/tests/test-ai-term--show-or-create.el b/tests/test-ai-term--show-or-create.el new file mode 100644 index 00000000..c6653dcd --- /dev/null +++ b/tests/test-ai-term--show-or-create.el @@ -0,0 +1,155 @@ +;;; test-ai-term--show-or-create.el --- Tests for cj/--ai-term-show-or-create -*- lexical-binding: t; -*- + +;;; 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 +;; +;; 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. + +;;; Code: + +(require 'ert) +(require 'cl-lib) + +(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)) + +(defmacro test-ai-term--with-mock-ghostel (vars &rest body) + "Run BODY with ghostel + ghostel-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." + (declare (indent 1) (debug t)) + (let ((calls (plist-get vars :calls)) + (strings (plist-get vars :strings)) + (ddir (plist-get vars :default-dir))) + `(let ((,calls '()) + (,strings '()) + (,ddir nil)) + (cl-letf (((symbol-function 'ghostel) + (lambda (&optional _arg) + (setq ,ddir default-directory) + (let ((b (get-buffer-create ghostel-buffer-name))) + (push (buffer-name b) ,calls) + b))) + ((symbol-function 'ghostel-send-string) + (lambda (s) (push s ,strings)))) + ,@body)))) + +(defun test-ai-term--cleanup (name) + "Kill buffer NAME if it exists." + (when (get-buffer name) + (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." + (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) + (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 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." + (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) + (cj/--ai-term-show-or-create "/tmp/reuse" name) + (should (null calls)) + (should (null strings))))) + (test-ai-term--cleanup name)))) + +(ert-deftest test-ai-term--show-or-create-recreates-when-process-dead () + "Boundary: buffer exists with dead process -> killed and recreated." + (let ((name "agent [dead-test]")) + (test-ai-term--cleanup name) + (unwind-protect + (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) + (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-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. + +Real `ghostel' 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. + +This test stubs `ghostel' 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*")) + (test-ai-term--cleanup agent-name) + (when (get-buffer orig-name) (kill-buffer orig-name)) + (unwind-protect + (save-window-excursion + (delete-other-windows) + (let ((orig-buf (get-buffer-create orig-name)) + (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))) + (set-window-buffer (selected-window) buf) + buf))) + ((symbol-function 'ghostel-send-string) + (lambda (_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." + (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) + (let ((result (cj/--ai-term-show-or-create "/tmp/return" name))) + (should (bufferp result)) + (should (equal (buffer-name result) name)))) + (test-ai-term--cleanup name)))) + +(provide 'test-ai-term--show-or-create) +;;; test-ai-term--show-or-create.el ends here |
