aboutsummaryrefslogtreecommitdiff
path: root/tests/test-ai-term--pick-project.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-05 05:28:58 -0500
committerCraig Jennings <c@cjennings.net>2026-06-05 05:28:58 -0500
commitebdf9e466b0e1f86e9b7d76650ac32408273e7a7 (patch)
treedab9b453f3a93c324b5388b3843502a088c7ed46 /tests/test-ai-term--pick-project.el
parentc094b2e4e64530379a9cb273303308a9affcabf6 (diff)
downloaddotemacs-ebdf9e466b0e1f86e9b7d76650ac32408273e7a7.tar.gz
dotemacs-ebdf9e466b0e1f86e9b7d76650ac32408273e7a7.zip
feat(term): replace vterm with ghostel as the terminal engine
I swapped the terminal engine from vterm to ghostel (libghostty-vt) everywhere. term-config replaces vterm-config (the F12 terminal, the C-; x menu, tmux history capture), and ai-term replaces ai-vterm (the F9 Claude-agent launcher). ghostel renders the agent TUI without vterm's flicker under heavy streaming, and one engine now covers every terminal workflow. Two behavior changes fall out of the swap. F9 launches in a terminal frame now: ghostel renders in TTY frames, so the old GUI-only guard is gone. Terminal windows no longer dim when unfocused: ghostel resolves its palette into the native module per-terminal, so there's no per-window color hook to dim through the way vterm had. auto-dim drops its vterm color-advice path, the dashboard Terminal button launches ghostel, and the vterm and vterm-toggle packages are removed. The tmux pane-history and copy-mode machinery carried over unchanged. It keys on the pty tty, which ghostel exposes.
Diffstat (limited to 'tests/test-ai-term--pick-project.el')
-rw-r--r--tests/test-ai-term--pick-project.el117
1 files changed, 117 insertions, 0 deletions
diff --git a/tests/test-ai-term--pick-project.el b/tests/test-ai-term--pick-project.el
new file mode 100644
index 00000000..e6d2f25b
--- /dev/null
+++ b/tests/test-ai-term--pick-project.el
@@ -0,0 +1,117 @@
+;;; test-ai-term--pick-project.el --- Tests for cj/--ai-term-pick-project -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; The picker presents abbreviated paths to `completing-read' (projects
+;; with a live tmux session first, then alphabetical), then returns the
+;; absolute path corresponding to the user's choice. An empty candidate
+;; set raises a `user-error' rather than offering an empty prompt. The
+;; collection is a completion table that pins display order (so Vertico
+;; doesn't re-sort and defeat the active-first grouping).
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+(require 'ai-term)
+
+(defun test-ai-term--collection-strings (collection)
+ "Return the candidate display strings from a completing-read COLLECTION.
+Works whether COLLECTION is an alist or a completion-table function."
+ (all-completions "" collection))
+
+(ert-deftest test-ai-term--pick-project-returns-absolute-path-of-choice ()
+ "Normal: user picks a candidate, picker returns its absolute path."
+ (cl-letf (((symbol-function 'cj/--ai-term-candidates)
+ (lambda () '("/home/u/code/foo" "/home/u/code/bar")))
+ ((symbol-function 'cj/--ai-term-live-tmux-sessions)
+ (lambda () nil))
+ ((symbol-function 'completing-read)
+ (lambda (_p collection &rest _)
+ (seq-find (lambda (s) (string-match-p "bar" s))
+ (test-ai-term--collection-strings collection)))))
+ (should (equal (cj/--ai-term-pick-project) "/home/u/code/bar"))))
+
+(ert-deftest test-ai-term--pick-project-empty-candidates-raises-user-error ()
+ "Error: no candidates -> user-error rather than empty prompt."
+ (cl-letf (((symbol-function 'cj/--ai-term-candidates) (lambda () nil)))
+ (should-error (cj/--ai-term-pick-project) :type 'user-error)))
+
+(ert-deftest test-ai-term--pick-project-presents-abbreviated-paths ()
+ "Normal: the completing-read collection holds abbreviated display forms."
+ (let (received-strings)
+ (cl-letf (((symbol-function 'cj/--ai-term-candidates)
+ (lambda () (list (expand-file-name "~/code/foo"))))
+ ((symbol-function 'cj/--ai-term-live-tmux-sessions)
+ (lambda () nil))
+ ((symbol-function 'completing-read)
+ (lambda (_p collection &rest _)
+ (setq received-strings (test-ai-term--collection-strings collection))
+ (car received-strings))))
+ (cj/--ai-term-pick-project)
+ (should (equal (car received-strings) "~/code/foo")))))
+
+(ert-deftest test-ai-term--pick-project-active-sessions-sort-first ()
+ "Normal: a project with a live tmux session leads; it carries [detached]."
+ (let ((cj/ai-term-tmux-session-prefix "aiv-")
+ received-strings)
+ (cl-letf (((symbol-function 'cj/--ai-term-candidates)
+ (lambda () '("/c/foo" "/c/bar" "/c/baz")))
+ ((symbol-function 'cj/--ai-term-live-tmux-sessions)
+ (lambda () '("aiv-baz")))
+ ((symbol-function 'completing-read)
+ (lambda (_p collection &rest _)
+ (setq received-strings (test-ai-term--collection-strings collection))
+ (car received-strings))))
+ (cj/--ai-term-pick-project)
+ (should (equal received-strings
+ '("/c/baz [detached]" "/c/bar" "/c/foo"))))))
+
+(ert-deftest test-ai-term--format-candidate-flags-running-project ()
+ "Normal: a path whose agent buffer has a live process gets a [running] suffix."
+ (let* ((path (expand-file-name "~/code/already-running"))
+ (buffer-name (cj/--ai-term-buffer-name path))
+ (buf (get-buffer-create buffer-name)))
+ (unwind-protect
+ (cl-letf (((symbol-function 'cj/--ai-term-process-live-p)
+ (lambda (b) (eq b buf))))
+ (should (equal (cj/--ai-term-format-candidate path)
+ (format "%s [running]" (abbreviate-file-name path)))))
+ (kill-buffer buf))))
+
+(ert-deftest test-ai-term--format-candidate-flags-detached-session ()
+ "Normal: no buffer but a matching tmux session -> [detached] suffix."
+ (let* ((cj/ai-term-tmux-session-prefix "aiv-")
+ (path (expand-file-name "~/code/has-session"))
+ (bn (cj/--ai-term-buffer-name path)))
+ (when (get-buffer bn) (kill-buffer bn))
+ (should (equal (cj/--ai-term-format-candidate
+ path (list (cj/--ai-term-tmux-session-name path)))
+ (format "%s [detached]" (abbreviate-file-name path))))))
+
+(ert-deftest test-ai-term--format-candidate-running-beats-detached ()
+ "Boundary: a live buffer wins over a matching session -> [running], not [detached]."
+ (let* ((cj/ai-term-tmux-session-prefix "aiv-")
+ (path (expand-file-name "~/code/both"))
+ (bn (cj/--ai-term-buffer-name path))
+ (buf (get-buffer-create bn)))
+ (unwind-protect
+ (cl-letf (((symbol-function 'cj/--ai-term-process-live-p)
+ (lambda (b) (eq b buf))))
+ (should (equal (cj/--ai-term-format-candidate
+ path (list (cj/--ai-term-tmux-session-name path)))
+ (format "%s [running]" (abbreviate-file-name path)))))
+ (kill-buffer buf))))
+
+(ert-deftest test-ai-term--format-candidate-omits-flag-when-not-running ()
+ "Boundary: a path with no buffer or no live process -> plain abbreviated path."
+ (let ((path (expand-file-name "~/code/not-running")))
+ ;; Make sure no agent buffer exists for this path.
+ (let ((bn (cj/--ai-term-buffer-name path)))
+ (when (get-buffer bn) (kill-buffer bn)))
+ (should (equal (cj/--ai-term-format-candidate path)
+ (abbreviate-file-name path)))))
+
+(provide 'test-ai-term--pick-project)
+;;; test-ai-term--pick-project.el ends here