aboutsummaryrefslogtreecommitdiff
path: root/tests/test-ai-term--candidates.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--candidates.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--candidates.el')
-rw-r--r--tests/test-ai-term--candidates.el139
1 files changed, 139 insertions, 0 deletions
diff --git a/tests/test-ai-term--candidates.el b/tests/test-ai-term--candidates.el
new file mode 100644
index 000000000..a9a392f31
--- /dev/null
+++ b/tests/test-ai-term--candidates.el
@@ -0,0 +1,139 @@
+;;; test-ai-term--candidates.el --- Tests for cj/--ai-term-candidates -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for the project-candidate walker. Two kinds of search root:
+;;
+;; - project root (a single project dir, e.g. ~/.emacs.d) -- include if it
+;; itself contains .ai/protocols.org
+;; - container root (e.g. ~/code, ~/projects) -- scan immediate children;
+;; include each child that contains .ai/protocols.org
+;;
+;; Tests build a temp directory tree with fake .ai/protocols.org markers
+;; and let-bind the search-root customs at it.
+
+;;; Code:
+
+(require 'ert)
+
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+(require 'ai-term)
+
+(defun test-ai-term--make-marker (dir)
+ "Create DIR/.ai/protocols.org so DIR registers as an AI-agent project."
+ (let ((ai-dir (expand-file-name ".ai" dir)))
+ (make-directory ai-dir t)
+ (write-region "" nil (expand-file-name "protocols.org" ai-dir))))
+
+(defmacro test-ai-term--with-fixture (root &rest body)
+ "Bind ROOT to a fresh temp directory; remove on exit; run BODY."
+ (declare (indent 1) (debug t))
+ `(let ((,root (make-temp-file "ai-term-test-" t)))
+ (unwind-protect
+ (progn ,@body)
+ (delete-directory ,root t))))
+
+(ert-deftest test-ai-term--candidates-project-root-with-marker ()
+ "Normal: a project root containing .ai/protocols.org is included."
+ (test-ai-term--with-fixture root
+ (let ((proj (expand-file-name "emacs-d-fake" root)))
+ (make-directory proj)
+ (test-ai-term--make-marker proj)
+ (let ((cj/ai-term-project-roots (list proj))
+ (cj/ai-term-container-roots nil))
+ (should (equal (cj/--ai-term-candidates)
+ (list (expand-file-name proj))))))))
+
+(ert-deftest test-ai-term--candidates-project-root-without-marker ()
+ "Boundary: a project root without .ai/protocols.org is excluded."
+ (test-ai-term--with-fixture root
+ (let ((proj (expand-file-name "no-ai" root)))
+ (make-directory proj)
+ (let ((cj/ai-term-project-roots (list proj))
+ (cj/ai-term-container-roots nil))
+ (should (null (cj/--ai-term-candidates)))))))
+
+(ert-deftest test-ai-term--candidates-container-includes-children-with-marker ()
+ "Normal: a container's children with .ai/protocols.org are included."
+ (test-ai-term--with-fixture root
+ (let ((container (expand-file-name "code" root))
+ (foo (expand-file-name "code/foo" root))
+ (bar (expand-file-name "code/bar" root)))
+ (make-directory container)
+ (make-directory foo)
+ (make-directory bar)
+ (test-ai-term--make-marker foo)
+ (test-ai-term--make-marker bar)
+ (let* ((cj/ai-term-project-roots nil)
+ (cj/ai-term-container-roots (list container))
+ (got (sort (cj/--ai-term-candidates) #'string<)))
+ (should (equal got
+ (sort (list (expand-file-name foo)
+ (expand-file-name bar))
+ #'string<)))))))
+
+(ert-deftest test-ai-term--candidates-container-skips-children-without-marker ()
+ "Boundary: a container's children without .ai/protocols.org are skipped."
+ (test-ai-term--with-fixture root
+ (let ((container (expand-file-name "code" root))
+ (foo (expand-file-name "code/foo" root))
+ (bare (expand-file-name "code/bare" root)))
+ (make-directory container)
+ (make-directory foo)
+ (make-directory bare)
+ (test-ai-term--make-marker foo)
+ (let ((cj/ai-term-project-roots nil)
+ (cj/ai-term-container-roots (list container)))
+ (should (equal (cj/--ai-term-candidates)
+ (list (expand-file-name foo))))))))
+
+(ert-deftest test-ai-term--candidates-container-skips-non-directory-entries ()
+ "Boundary: a container's non-directory entries are ignored."
+ (test-ai-term--with-fixture root
+ (let ((container (expand-file-name "code" root))
+ (foo (expand-file-name "code/foo" root))
+ (stray (expand-file-name "code/README.txt" root)))
+ (make-directory container)
+ (make-directory foo)
+ (test-ai-term--make-marker foo)
+ (write-region "" nil stray)
+ (let ((cj/ai-term-project-roots nil)
+ (cj/ai-term-container-roots (list container)))
+ (should (equal (cj/--ai-term-candidates)
+ (list (expand-file-name foo))))))))
+
+(ert-deftest test-ai-term--candidates-nonexistent-root-is-skipped ()
+ "Error: a nonexistent search root is skipped silently, no error raised."
+ (test-ai-term--with-fixture root
+ (let ((cj/ai-term-project-roots
+ (list (expand-file-name "does-not-exist" root)))
+ (cj/ai-term-container-roots
+ (list (expand-file-name "also-missing" root))))
+ (should (null (cj/--ai-term-candidates))))))
+
+(ert-deftest test-ai-term--candidates-empty-roots-yield-empty-list ()
+ "Boundary: nil roots yield nil."
+ (let ((cj/ai-term-project-roots nil)
+ (cj/ai-term-container-roots nil))
+ (should (null (cj/--ai-term-candidates)))))
+
+(ert-deftest test-ai-term--candidates-mixed-roots ()
+ "Normal: project + container roots combine in one result list."
+ (test-ai-term--with-fixture root
+ (let ((emacs-d (expand-file-name "emacs-d" root))
+ (container (expand-file-name "code" root))
+ (foo (expand-file-name "code/foo" root)))
+ (make-directory emacs-d)
+ (make-directory container)
+ (make-directory foo)
+ (test-ai-term--make-marker emacs-d)
+ (test-ai-term--make-marker foo)
+ (let* ((cj/ai-term-project-roots (list emacs-d))
+ (cj/ai-term-container-roots (list container))
+ (got (sort (cj/--ai-term-candidates) #'string<)))
+ (should (equal got
+ (sort (list (expand-file-name emacs-d)
+ (expand-file-name foo))
+ #'string<)))))))
+
+(provide 'test-ai-term--candidates)
+;;; test-ai-term--candidates.el ends here