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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
|
;;; test-ai-term--collapse-split.el --- F9 collapses the agent split -*- lexical-binding: t; -*-
;;; Commentary:
;; Regression coverage for the F9 toggle-off behavior Craig reported: with
;; several agents alive, F9 should HIDE the agent split (collapse it back to the
;; working layout) rather than surfacing a different agent. Two cases:
;;
;; - Multi-window: the agent occupies a split. F9 deletes that window so the
;; working buffer reclaims the frame -- never swaps in another agent. The
;; prior `quit-restore-window' path went stale after the slot was reused
;; across agents (C-F9 switching), so it surfaced a different agent.
;; - Single-window: the agent fills the frame. F9 returns to the most-recent
;; NON-agent buffer (the file being worked on), not another agent -- the prior
;; `other-buffer' call could pick another live agent.
;;
;; Also covers the `cj/--ai-term-most-recent-non-agent-buffer' helper.
;;; Code:
(require 'ert)
(require 'cl-lib)
(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
(add-to-list 'load-path (expand-file-name "tests" user-emacs-directory))
(require 'ai-term)
(require 'testutil-ghostel-buffers)
;;; cj/--ai-term-most-recent-non-agent-buffer
(ert-deftest test-ai-term--most-recent-non-agent-buffer-skips-agents ()
"Normal: returns a live non-agent buffer even when agents are most-recent."
(cj/test--kill-agent-buffers)
(let ((work (get-buffer-create "*test-mrna-work*"))
(agent-a (get-buffer-create "agent [mrna-a]"))
(agent-b (get-buffer-create "agent [mrna-b]")))
(unwind-protect
(save-window-excursion
(delete-other-windows)
;; Make the agents most-recent in this window's history.
(set-window-buffer (selected-window) work)
(set-window-buffer (selected-window) agent-b)
(set-window-buffer (selected-window) agent-a)
(let ((result (cj/--ai-term-most-recent-non-agent-buffer)))
(should (bufferp result))
(should (buffer-live-p result))
(should-not (cj/--ai-term-buffer-p result))))
(when (get-buffer "*test-mrna-work*") (kill-buffer "*test-mrna-work*"))
(cj/test--kill-agent-buffers))))
;;; Multi-window: F9 collapses the split
(ert-deftest test-ai-term--collapse-multi-window-deletes-agent-split ()
"Normal/Regression: agent in a bottom split with other agents alive; F9
collapses the split so the working buffer reclaims the frame, and no agent is
surfaced. Before the fix, `quit-restore-window' could switch the slot to a
different agent (stale quit-restore after slot reuse)."
(cj/test--kill-agent-buffers)
(let ((work (get-buffer-create "*test-collapse-work*"))
(agent-a (get-buffer-create "agent [collapse-a]"))
(agent-b (get-buffer-create "agent [collapse-b]"))
(agent-c (get-buffer-create "agent [collapse-c]"))
(cj/--ai-term-last-was-bury nil))
(unwind-protect
(save-window-excursion
(delete-other-windows)
(set-window-buffer (selected-window) work)
(let ((agent-win (split-window (selected-window) nil 'below)))
;; Reuse the slot across agents (as C-F9 switching does) so the
;; window's prev-buffer history holds another agent.
(set-window-buffer agent-win agent-a)
(set-window-buffer agent-win agent-b)
(set-window-buffer agent-win agent-c)
(select-window agent-win)
(should-not (one-window-p))
(cj/test--call-as-gui #'cj/ai-term)
(should (one-window-p))
(should-not (cj/--ai-term-displayed-agent-window))
(should (eq (window-buffer (selected-window)) work))))
(when (get-buffer "*test-collapse-work*") (kill-buffer "*test-collapse-work*"))
(cj/test--kill-agent-buffers))))
;;; Single-window: F9 returns to a non-agent buffer
(ert-deftest test-ai-term--collapse-single-window-returns-non-agent ()
"Normal/Regression: agent fills the frame, other agents alive; F9 toggles back
to a NON-agent buffer (the working file), never another agent. Before the fix,
`other-buffer' could pick another live agent."
(cj/test--kill-agent-buffers)
(let ((work (get-buffer-create "*test-collapse-sw-work*"))
(agent-a (get-buffer-create "agent [collapse-sw-a]"))
(agent-b (get-buffer-create "agent [collapse-sw-b]"))
(cj/--ai-term-last-was-bury nil))
(unwind-protect
(save-window-excursion
(delete-other-windows)
;; MRU: work, then agent-b, then agent-a (current). `other-buffer'
;; would pick agent-b; the fix must skip it for a non-agent.
(set-window-buffer (selected-window) work)
(set-window-buffer (selected-window) agent-b)
(set-window-buffer (selected-window) agent-a)
(should (one-window-p))
(let ((display-buffer-alist (cj/--ai-term-display-rule-list)))
(cj/test--call-as-gui #'cj/ai-term))
(should (one-window-p))
(should-not (cj/--ai-term-buffer-p (window-buffer (selected-window)))))
(when (get-buffer "*test-collapse-sw-work*") (kill-buffer "*test-collapse-sw-work*"))
(cj/test--kill-agent-buffers))))
;;; Faithful toggle: reopen the SAME agent that was hidden
(ert-deftest test-ai-term--dispatch-prefers-last-hidden-agent ()
"Regression: dispatch reopens the last-hidden agent, not the buffer-list MRU.
After F9 hides an agent, the next F9 must reopen the SAME one even when a
different agent is ahead of it in `buffer-list'. Falls back to the MRU when
nothing was hidden yet or the remembered buffer was killed."
(cj/test--kill-agent-buffers)
(let ((a1 (get-buffer-create "agent [disp-mru]"))
(a2 (get-buffer-create "agent [disp-shown]"))
(cj/--ai-term-last-hidden-buffer nil))
(unwind-protect
(cl-letf (((symbol-function 'cj/--ai-term-displayed-agent-window)
(lambda (&optional _f) nil))
((symbol-function 'cj/--ai-term-agent-buffers)
(lambda () (list a1 a2)))) ; a1 is the MRU
;; No memory yet -> falls back to MRU (a1).
(should (equal (cj/--ai-term-dispatch) (cons 'redisplay-recent a1)))
;; Remember a2 as last hidden -> dispatch prefers it.
(setq cj/--ai-term-last-hidden-buffer a2)
(should (equal (cj/--ai-term-dispatch) (cons 'redisplay-recent a2)))
;; A killed last-hidden buffer -> falls back to MRU.
(let ((dead (get-buffer-create "agent [disp-dead]")))
(setq cj/--ai-term-last-hidden-buffer dead)
(kill-buffer dead))
(should (equal (cj/--ai-term-dispatch) (cons 'redisplay-recent a1))))
(cj/test--kill-agent-buffers))))
(ert-deftest test-ai-term--toggle-roundtrip-reopens-same-agent ()
"Regression: hide then show brings back the agent that was on screen.
With several agents alive and a different one most-recent in `buffer-list',
F9 off then F9 on restores the SAME agent that was visible -- not a swap to
another. Reproduces the \"the displayed buffer changes\" report."
(cj/test--kill-agent-buffers)
(let ((work (get-buffer-create "*test-roundtrip-work*"))
(a1 (get-buffer-create "agent [rt-1]"))
(a2 (get-buffer-create "agent [rt-2]"))
(cj/--ai-term-last-was-bury nil)
(cj/--ai-term-last-direction nil)
(cj/--ai-term-last-size nil)
(cj/--ai-term-last-hidden-buffer nil))
(unwind-protect
(save-window-excursion
(delete-other-windows)
(set-window-buffer (selected-window) work)
(let ((agent-win (split-window (selected-window) nil 'below)))
;; a2 is the visible agent; a1 sits ahead of it in buffer-list.
(set-window-buffer agent-win a1)
(bury-buffer a1) ; a1 stays alive, demoted in MRU
(set-window-buffer agent-win a2)
(select-window agent-win)
(should (eq (window-buffer (cj/--ai-term-displayed-agent-window)) a2))
(let ((display-buffer-alist (cj/--ai-term-display-rule-list)))
(cj/test--call-as-gui #'cj/ai-term) ; off
(should-not (cj/--ai-term-displayed-agent-window))
(cj/test--call-as-gui #'cj/ai-term) ; on -> must be a2
(should (eq (window-buffer (cj/--ai-term-displayed-agent-window))
a2)))))
(when (get-buffer "*test-roundtrip-work*") (kill-buffer "*test-roundtrip-work*"))
(cj/test--kill-agent-buffers))))
(provide 'test-ai-term--collapse-split)
;;; test-ai-term--collapse-split.el ends here
|