aboutsummaryrefslogtreecommitdiff
path: root/tests/test-ai-term--single-window-toggle.el
blob: aa507f03269d6c90260b42543804137535987037 (plain)
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
;;; test-ai-term--single-window-toggle.el --- F9 toggle round-trip when agent is the only window -*- lexical-binding: t; -*-

;;; Commentary:
;; Regression coverage for the bug where toggling off a single-window
;; agent (bury) then toggling on again redisplays the agent in a side
;; split instead of restoring the full-frame layout.
;;
;; The fix introduces a `cj/--ai-term-last-was-bury' flag set at
;; toggle-off when `one-window-p' was true.  At toggle-on the display
;; action consumes the flag and, if the frame is still single-window,
;; replaces the current window's buffer in place rather than calling
;; `display-buffer-in-direction'.

;;; 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)

;;; Normal Cases

(ert-deftest test-ai-term--single-window-toggle-normal-roundtrip-preserves-fullscreen ()
  "Normal: agent in the only window, F9 (off), F9 (on) -> still single window with agent.

Reproduces Craig's report.  Before the original fix the toggle-on path
fell through to `display-buffer-in-direction', which split the lone
window into two and left the agent in a side panel.  Before the
follow-up fix the toggle-off path could no-op entirely when
`bury-buffer' couldn't find a buffer to switch to, so the user saw
\"F9 does nothing\".  The dispatcher now forces the swap to a non-
agent buffer after bury so the toggle-off is observable in real and
batch use both."
  (cj/test--kill-agent-buffers)
  (let ((agent-name "agent [single-window-roundtrip]")
        (cj/--ai-term-last-was-bury nil)
        (cj/--ai-term-last-direction nil)
        (cj/--ai-term-last-size nil))
    (unwind-protect
        (save-window-excursion
          (delete-other-windows)
          (let ((agent-buf (get-buffer-create agent-name)))
            (set-window-buffer (selected-window) agent-buf)
            (should (one-window-p))
            (let ((display-buffer-alist (cj/--ai-term-display-rule-list)))
              ;; Toggle off -- the dispatcher's force-swap should put the
              ;; window on a non-agent buffer.
              (cj/test--call-as-gui #'cj/ai-term)
              (should (one-window-p))
              (should-not (cj/--ai-term-displayed-agent-window))
              (should (eq cj/--ai-term-last-was-bury t))
              ;; Toggle on -- should restore agent in the same lone window.
              (cj/test--call-as-gui #'cj/ai-term)
              (should (one-window-p))
              (let ((win (cj/--ai-term-displayed-agent-window)))
                (should (windowp win))
                (should (eq (window-buffer win) agent-buf)))
              ;; Flag consumed by the display-saved action.
              (should-not cj/--ai-term-last-was-bury))))
      (cj/test--kill-agent-buffers))))

(ert-deftest test-ai-term--single-window-toggle-off-swaps-window-buffer ()
  "Normal: toggle-off in single-window state forces the window onto a non-
agent buffer when `bury-buffer' itself didn't swap.

Catches the regression Craig reported after the original fix shipped:
F9 in a lone-window agent did nothing visible.  The fix layer here
ensures the displayed buffer changes -- so the next F9 sees an empty
agent-window state and can route through the display-saved path."
  (cj/test--kill-agent-buffers)
  (let ((agent-name "agent [bury-swap-observable]")
        (cj/--ai-term-last-was-bury nil))
    (unwind-protect
        (save-window-excursion
          (delete-other-windows)
          (let* ((agent-buf (get-buffer-create agent-name))
                 (win (selected-window)))
            (set-window-buffer win agent-buf)
            (let ((display-buffer-alist (cj/--ai-term-display-rule-list)))
              (cj/test--call-as-gui #'cj/ai-term))
            (should (window-live-p win))
            (should-not (cj/--ai-term-buffer-p (window-buffer win)))))
      (cj/test--kill-agent-buffers))))

(ert-deftest test-ai-term--single-window-toggle-normal-flag-set-on-bury ()
  "Normal: single-window toggle-off sets the bury flag."
  (cj/test--kill-agent-buffers)
  (let ((agent-name "agent [bury-flag-set]")
        (cj/--ai-term-last-was-bury nil))
    (unwind-protect
        (save-window-excursion
          (delete-other-windows)
          (let ((agent-buf (get-buffer-create agent-name)))
            (set-window-buffer (selected-window) agent-buf)
            (let ((display-buffer-alist (cj/--ai-term-display-rule-list)))
              (cj/test--call-as-gui #'cj/ai-term)
              (should (eq cj/--ai-term-last-was-bury t)))))
      (cj/test--kill-agent-buffers))))

(ert-deftest test-ai-term--single-window-toggle-normal-flag-cleared-on-multi-window-off ()
  "Normal: multi-window toggle-off clears the bury flag.
Mirrors the existing `delete-window' branch of the dispatcher --
the flag should not carry over a prior bury into a delete-window
toggle-off."
  (cj/test--kill-agent-buffers)
  (let ((agent-name "agent [bury-flag-clear]")
        (left-name "*test-sw-left*")
        (cj/--ai-term-last-was-bury t))    ; stale t from prior bury
    (unwind-protect
        (save-window-excursion
          (delete-other-windows)
          (let ((agent-buf (get-buffer-create agent-name))
                (left-buf (get-buffer-create left-name)))
            (set-window-buffer (selected-window) left-buf)
            (let* ((agent-win (split-window (selected-window) nil 'right))
                   (display-buffer-alist (cj/--ai-term-display-rule-list)))
              (set-window-buffer agent-win agent-buf)
              (select-window agent-win)
              (cj/test--call-as-gui #'cj/ai-term)
              (should-not cj/--ai-term-last-was-bury))))
      (when (get-buffer left-name) (kill-buffer left-name))
      (cj/test--kill-agent-buffers))))

;;; Boundary Cases

(ert-deftest test-ai-term--single-window-toggle-boundary-flag-respected-only-when-still-one-window ()
  "Boundary: if the frame got split between toggle-off and toggle-on, the
saved-direction split applies as usual.  The flag is a fast-path for the
genuine single-window case, not an override for every redisplay."
  (cj/test--kill-agent-buffers)
  (let ((agent-name "agent [flag-fallback]")
        (cj/--ai-term-last-was-bury t)        ; flag pretends prior bury
        (cj/--ai-term-last-direction 'right)
        (cj/--ai-term-last-size 40))
    (unwind-protect
        (save-window-excursion
          (delete-other-windows)
          (let* ((other-buf (get-buffer-create "*test-sw-other*"))
                 (agent-buf (get-buffer-create agent-name)))
            (set-window-buffer (selected-window) other-buf)
            ;; Frame is split (two windows) -- single-window precondition
            ;; for the flag no longer holds.
            (split-window-right)
            (should-not (one-window-p))
            (let (received-buf
                  (display-buffer-alist (cj/--ai-term-display-rule-list)))
              (cl-letf (((symbol-function 'display-buffer-in-direction)
                         (lambda (b _a)
                           (setq received-buf b)
                           (selected-window))))
                (cj/--ai-term-display-saved agent-buf nil))
              ;; The saved-direction split path ran (display-buffer-in-direction
              ;; was called) rather than the in-place fast path.
              (should (eq received-buf agent-buf))
              ;; And the flag is cleared either way.
              (should-not cj/--ai-term-last-was-bury))))
      (when (get-buffer "*test-sw-other*") (kill-buffer "*test-sw-other*"))
      (cj/test--kill-agent-buffers))))

(ert-deftest test-ai-term--single-window-toggle-boundary-flag-not-set-when-bury-not-used ()
  "Boundary: a fresh dispatcher run with the agent displayed multi-window leaves
the flag nil (no spurious set)."
  (cj/test--kill-agent-buffers)
  (let ((agent-name "agent [bury-flag-untouched]")
        (cj/--ai-term-last-was-bury nil))
    (unwind-protect
        (save-window-excursion
          (delete-other-windows)
          (let ((agent-buf (get-buffer-create agent-name))
                (left-buf (get-buffer-create "*test-sw-untouched-left*")))
            (set-window-buffer (selected-window) left-buf)
            (let* ((agent-win (split-window (selected-window) nil 'right))
                   (display-buffer-alist (cj/--ai-term-display-rule-list)))
              (set-window-buffer agent-win agent-buf)
              (select-window agent-win)
              (cj/test--call-as-gui #'cj/ai-term)
              (should-not cj/--ai-term-last-was-bury))))
      (when (get-buffer "*test-sw-untouched-left*")
        (kill-buffer "*test-sw-untouched-left*"))
      (cj/test--kill-agent-buffers))))

(provide 'test-ai-term--single-window-toggle)
;;; test-ai-term--single-window-toggle.el ends here