1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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
|