diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-07 19:25:18 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-07 19:25:18 -0500 |
| commit | 47b218ed15acd00c18cbc3bef604c4f2e0050a08 (patch) | |
| tree | 98c6541327b707e1e3c1f214f8a6dc7d0135a039 /tests/test-ai-vterm--show-or-create.el | |
| parent | 3efaf9b5218fa769a297df5821ec89837207e57d (diff) | |
| download | dotemacs-47b218ed15acd00c18cbc3bef604c4f2e0050a08.tar.gz dotemacs-47b218ed15acd00c18cbc3bef604c4f2e0050a08.zip | |
feat(ai-vterm): add Claude launcher with vertical-split vterm
The new module picks a Claude-template project from a filtered completing-read list. It scans the same roots the `ai` shell launcher uses, then opens or reuses a vterm buffer named `claude [<repo>]` on the right. F9 launches it. The prior `cj/toggle-gptel` binding moves from F9 to C-F9 so both AI tools share the same physical key.
The display rule chains reuse-window -> use-some-window -> in-direction (right). The resulting window isn't dedicated. That matters because side-window dedication was breaking `buffer-move` (C-M-arrows) and `switch-to-buffer` replacement on the claude buffer. I also narrowed `vterm-toggle`'s display rule to skip `claude [` buffers. Otherwise it claimed them first with its bottom-split + dedicated treatment.
I added 23 tests across 5 files: the buffer-name transform, candidate walker, show-or-create dispatch, picker, and display rule. Design lives at docs/design/ai-vterm.org.
Diffstat (limited to 'tests/test-ai-vterm--show-or-create.el')
| -rw-r--r-- | tests/test-ai-vterm--show-or-create.el | 119 |
1 files changed, 119 insertions, 0 deletions
diff --git a/tests/test-ai-vterm--show-or-create.el b/tests/test-ai-vterm--show-or-create.el new file mode 100644 index 00000000..28e0faeb --- /dev/null +++ b/tests/test-ai-vterm--show-or-create.el @@ -0,0 +1,119 @@ +;;; test-ai-vterm--show-or-create.el --- Tests for cj/--ai-vterm-show-or-create -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests the show-or-create branching: +;; +;; - buffer absent -> vterm called, claude command sent +;; - buffer present, live -> vterm not called, buffer displayed +;; - buffer present, dead -> old buffer killed, vterm recreates +;; +;; vterm functions are stubbed so the test does no process spawning. + +;;; Code: + +(require 'ert) +(require 'cl-lib) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'ai-vterm) + +;; vterm isn't loaded in batch -- provide stubs so cl-letf has overrides. +(unless (fboundp 'vterm) + (defun vterm (&optional _name) nil)) +(unless (fboundp 'vterm-send-string) + (defun vterm-send-string (_s &optional _) nil)) +(unless (fboundp 'vterm-send-return) + (defun vterm-send-return () nil)) + +(defmacro test-ai-vterm--with-mock-vterm (vars &rest body) + "Run BODY with vterm + send-string + send-return mocked. + +VARS is a plist of capture variable names: :calls, :strings, :returns, +:default-dir. The test references these names directly inside BODY." + (declare (indent 1) (debug t)) + (let ((calls (plist-get vars :calls)) + (strings (plist-get vars :strings)) + (returns (plist-get vars :returns)) + (ddir (plist-get vars :default-dir))) + `(let ((,calls '()) + (,strings '()) + (,returns 0) + (,ddir nil)) + (cl-letf (((symbol-function 'vterm) + (lambda (&optional name) + (push name ,calls) + (setq ,ddir default-directory) + (with-current-buffer (get-buffer-create name) + (current-buffer)))) + ((symbol-function 'vterm-send-string) + (lambda (s &optional _) (push s ,strings))) + ((symbol-function 'vterm-send-return) + (lambda () (cl-incf ,returns)))) + ,@body)))) + +(defun test-ai-vterm--cleanup (name) + "Kill buffer NAME if it exists." + (when (get-buffer name) + (kill-buffer name))) + +(ert-deftest test-ai-vterm--show-or-create-creates-when-buffer-missing () + "Normal: no existing buffer -> vterm called once, claude cmd sent." + (let ((name "claude [normal-create-test]")) + (test-ai-vterm--cleanup name) + (unwind-protect + (test-ai-vterm--with-mock-vterm (:calls calls :strings strings + :returns returns :default-dir ddir) + (cj/--ai-vterm-show-or-create "/tmp/some-project" name) + (should (equal calls (list name))) + (should (equal strings (list cj/ai-vterm-claude-command))) + (should (= returns 1)) + (should (equal ddir "/tmp/some-project"))) + (test-ai-vterm--cleanup name)))) + +(ert-deftest test-ai-vterm--show-or-create-displays-existing-when-process-live () + "Normal: buffer exists with live process -> vterm not called." + (let ((name "claude [reuse-test]")) + (test-ai-vterm--cleanup name) + (unwind-protect + (let ((buf (get-buffer-create name))) + (cl-letf (((symbol-function 'cj/--ai-vterm-process-live-p) + (lambda (b) (and (eq b buf) t)))) + (test-ai-vterm--with-mock-vterm (:calls calls :strings strings + :returns returns :default-dir _ddir) + (cj/--ai-vterm-show-or-create "/tmp/reuse" name) + (should (null calls)) + (should (null strings)) + (should (= returns 0))))) + (test-ai-vterm--cleanup name)))) + +(ert-deftest test-ai-vterm--show-or-create-recreates-when-process-dead () + "Boundary: buffer exists with dead process -> killed and recreated." + (let ((name "claude [dead-test]")) + (test-ai-vterm--cleanup name) + (unwind-protect + (let ((stale (get-buffer-create name))) + (cl-letf (((symbol-function 'cj/--ai-vterm-process-live-p) + (lambda (_b) nil))) + (test-ai-vterm--with-mock-vterm (:calls calls :strings strings + :returns returns :default-dir _ddir) + (cj/--ai-vterm-show-or-create "/tmp/dead" name) + (should (equal calls (list name))) + (should (equal strings (list cj/ai-vterm-claude-command))) + (should (= returns 1)) + (should-not (buffer-live-p stale))))) + (test-ai-vterm--cleanup name)))) + +(ert-deftest test-ai-vterm--show-or-create-returns-buffer () + "Normal: return value is the vterm buffer." + (let ((name "claude [return-test]")) + (test-ai-vterm--cleanup name) + (unwind-protect + (test-ai-vterm--with-mock-vterm (:calls _c :strings _s + :returns _r :default-dir _d) + (let ((result (cj/--ai-vterm-show-or-create "/tmp/return" name))) + (should (bufferp result)) + (should (equal (buffer-name result) name)))) + (test-ai-vterm--cleanup name)))) + +(provide 'test-ai-vterm--show-or-create) +;;; test-ai-vterm--show-or-create.el ends here |
