diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-02 13:04:37 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-02 13:04:37 -0500 |
| commit | 38dad925528e05f2084474ead5519a6a6ceb70f7 (patch) | |
| tree | 84f4928e3e9b3fdb88be8d73a4db46728beacc41 /tests/test-ai-vterm--collapse-split.el | |
| parent | 76f983eff93e6d37d0710d2854361a8314f63d79 (diff) | |
| download | dotemacs-38dad925528e05f2084474ead5519a6a6ceb70f7.tar.gz dotemacs-38dad925528e05f2084474ead5519a6a6ceb70f7.zip | |
fix(ai-vterm): make F9 a faithful toggle of the agent split
F9 toggle-off used quit-restore-window to dismiss the agent. With several agents alive sharing one slot, switching among them (C-F9) reuses the window via set-window-buffer, which leaves the window's quit-restore parameter pointing at the first agent shown. Once stale, quit-restore-window falls back to switch-to-prev-buffer and surfaces a different agent instead of removing the window, so F9 appeared to "show another agent" rather than hide the split.
Toggle-off now collapses the split with delete-window, which is independent of the slot's buffer history, so the working buffer reclaims the frame. Geometry is captured first so the next toggle-on re-splits at the same width.
Toggle-on reopens the exact agent that was hidden (new cj/--ai-vterm-last-hidden-buffer), falling back to the most-recent agent only when that buffer has been killed. Hide-then-show is now a faithful round trip, not a jump to whichever agent is most-recent in buffer-list.
Sole-window toggle-off returns to the most-recent non-agent buffer instead of other-buffer, which could land on another agent.
I updated the two reuse-edge-window tests that asserted the old restore-displaced-into-a-kept-slot behavior to match the new always-collapse behavior.
Diffstat (limited to 'tests/test-ai-vterm--collapse-split.el')
| -rw-r--r-- | tests/test-ai-vterm--collapse-split.el | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/tests/test-ai-vterm--collapse-split.el b/tests/test-ai-vterm--collapse-split.el new file mode 100644 index 00000000..ad299e47 --- /dev/null +++ b/tests/test-ai-vterm--collapse-split.el @@ -0,0 +1,171 @@ +;;; test-ai-vterm--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-vterm-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-vterm) +(require 'testutil-vterm-buffers) + +;;; cj/--ai-vterm-most-recent-non-agent-buffer + +(ert-deftest test-ai-vterm--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-vterm-most-recent-non-agent-buffer))) + (should (bufferp result)) + (should (buffer-live-p result)) + (should-not (cj/--ai-vterm-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-vterm--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-vterm-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-vterm) + (should (one-window-p)) + (should-not (cj/--ai-vterm-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-vterm--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-vterm-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-vterm-display-rule-list))) + (cj/test--call-as-gui #'cj/ai-vterm)) + (should (one-window-p)) + (should-not (cj/--ai-vterm-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-vterm--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-vterm-last-hidden-buffer nil)) + (unwind-protect + (cl-letf (((symbol-function 'cj/--ai-vterm-displayed-agent-window) + (lambda (&optional _f) nil)) + ((symbol-function 'cj/--ai-vterm-agent-buffers) + (lambda () (list a1 a2)))) ; a1 is the MRU + ;; No memory yet -> falls back to MRU (a1). + (should (equal (cj/--ai-vterm-dispatch) (cons 'redisplay-recent a1))) + ;; Remember a2 as last hidden -> dispatch prefers it. + (setq cj/--ai-vterm-last-hidden-buffer a2) + (should (equal (cj/--ai-vterm-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-vterm-last-hidden-buffer dead) + (kill-buffer dead)) + (should (equal (cj/--ai-vterm-dispatch) (cons 'redisplay-recent a1)))) + (cj/test--kill-agent-buffers)))) + +(ert-deftest test-ai-vterm--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-vterm-last-was-bury nil) + (cj/--ai-vterm-last-direction nil) + (cj/--ai-vterm-last-size nil) + (cj/--ai-vterm-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-vterm-displayed-agent-window)) a2)) + (let ((display-buffer-alist (cj/--ai-vterm-display-rule-list))) + (cj/test--call-as-gui #'cj/ai-vterm) ; off + (should-not (cj/--ai-vterm-displayed-agent-window)) + (cj/test--call-as-gui #'cj/ai-vterm) ; on -> must be a2 + (should (eq (window-buffer (cj/--ai-vterm-displayed-agent-window)) + a2))))) + (when (get-buffer "*test-roundtrip-work*") (kill-buffer "*test-roundtrip-work*")) + (cj/test--kill-agent-buffers)))) + +(provide 'test-ai-vterm--collapse-split) +;;; test-ai-vterm--collapse-split.el ends here |
