aboutsummaryrefslogtreecommitdiff
path: root/tests/test-ai-term--show-or-create.el
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test-ai-term--show-or-create.el')
-rw-r--r--tests/test-ai-term--show-or-create.el155
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