aboutsummaryrefslogtreecommitdiff
path: root/tests/test-ai-term--display-saved.el
blob: 8b689aa6bc76064c67d5b37f0228871d9e824908 (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
;;; test-ai-term--display-saved.el --- Tests for the display-saved action -*- lexical-binding: t; -*-

;;; Commentary:
;; `cj/--ai-term-display-saved' is the split path of the F9 display
;; chain -- it runs only when no agent window and no reusable edge slot
;; exist (a single-window frame, or a layout split on the other axis).
;; It reads `cj/--ai-term-last-direction' + `cj/--ai-term-last-size'
;; (with default fallbacks), builds an alist with direction + the
;; matching size key, strips any conflicting entries that came in via the
;; rule, and delegates to `display-buffer-in-direction'.
;;
;; Tests stub `display-buffer-in-direction' to capture the alist that
;; would have reached it.
;;
;; Multi-window toggle round-trips no longer resplit -- they reuse the
;; existing half (see test-ai-term--reuse-edge-window.el), so the former
;; resplit/body-width-preservation round-trip tests were retired with the
;; swap-the-slot model.  The buffer-move teardown test stays here because
;; it exercises the split-window delete path on toggle-off.

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

(ert-deftest test-ai-term--display-saved-uses-desktop-defaults-when-state-nil ()
  "Normal: nil state on a desktop -> rightmost, size=cj/ai-term-desktop-width.
The cardinal `right' default maps to the frame-edge variant
`rightmost' so agent lands at the frame's right edge regardless of
which window is selected.  `env-laptop-p' is stubbed nil to pin the
desktop branch."
  (let (received-buf received-alist
        (cj/--ai-term-last-direction nil)
        (cj/--ai-term-last-size nil)
        (cj/ai-term-desktop-width 0.5))
    (cl-letf (((symbol-function 'env-laptop-p) (lambda () nil))
              ((symbol-function 'display-buffer-in-direction)
               (lambda (b a)
                 (setq received-buf b received-alist a)
                 'fake-window)))
      (cj/--ai-term-display-saved 'fake-buf '((inhibit-same-window . t))))
    (should (eq received-buf 'fake-buf))
    (should (eq (cdr (assq 'direction received-alist)) 'rightmost))
    (should (= (cdr (assq 'window-width received-alist)) 0.5))
    (should (eq (cdr (assq 'inhibit-same-window received-alist)) t))))

(ert-deftest test-ai-term--display-saved-uses-laptop-defaults-when-state-nil ()
  "Normal: nil state on a laptop -> bottom, size=cj/ai-term-laptop-height.
The cardinal `below' default maps to the frame-edge variant `bottom'
and the size lands on the `window-height' axis.  `env-laptop-p' is
stubbed t to pin the laptop branch."
  (let (received-alist
        (cj/--ai-term-last-direction nil)
        (cj/--ai-term-last-size nil)
        (cj/ai-term-laptop-height 0.75))
    (cl-letf (((symbol-function 'env-laptop-p) (lambda () t))
              ((symbol-function 'display-buffer-in-direction)
               (lambda (_b a) (setq received-alist a) 'fake-window)))
      (cj/--ai-term-display-saved 'fake-buf '((inhibit-same-window . t))))
    (should (eq (cdr (assq 'direction received-alist)) 'bottom))
    (should (= (cdr (assq 'window-height received-alist)) 0.75))
    (should-not (assq 'window-width received-alist))))

(ert-deftest test-ai-term--display-saved-uses-saved-direction-and-size-below ()
  "Normal: saved direction=below maps to bottom edge; size=0.4 passes through."
  (let (received-alist
        (cj/--ai-term-last-direction 'below)
        (cj/--ai-term-last-size 0.4))
    (cl-letf (((symbol-function 'display-buffer-in-direction)
               (lambda (_b a) (setq received-alist a) 'fake-window)))
      (cj/--ai-term-display-saved 'fake-buf nil))
    (should (eq (cdr (assq 'direction received-alist)) 'bottom))
    (should (= (cdr (assq 'window-height received-alist)) 0.4))
    (should-not (assq 'window-width received-alist))))

(ert-deftest test-ai-term--display-saved-uses-saved-direction-and-size-right ()
  "Normal: saved direction=right maps to rightmost edge; size=0.7 passes through."
  (let (received-alist
        (cj/--ai-term-last-direction 'right)
        (cj/--ai-term-last-size 0.7))
    (cl-letf (((symbol-function 'display-buffer-in-direction)
               (lambda (_b a) (setq received-alist a) 'fake-window)))
      (cj/--ai-term-display-saved 'fake-buf nil))
    (should (eq (cdr (assq 'direction received-alist)) 'rightmost))
    (should (= (cdr (assq 'window-width received-alist)) 0.7))
    (should-not (assq 'window-height received-alist))))

(ert-deftest test-ai-term--display-saved-strips-conflicting-alist-entries ()
  "Boundary: caller-supplied direction/size are stripped, saved values win."
  (let (received-alist
        (cj/--ai-term-last-direction 'right)
        (cj/--ai-term-last-size 0.7))
    (cl-letf (((symbol-function 'display-buffer-in-direction)
               (lambda (_b a) (setq received-alist a) 'fake-window)))
      (cj/--ai-term-display-saved
       'fake-buf
       '((direction . below)
         (window-width . 0.2)
         (window-height . 0.3)
         (inhibit-same-window . t))))
    (should (eq (cdr (assq 'direction received-alist)) 'rightmost))
    (should (= (cdr (assq 'window-width received-alist)) 0.7))
    (should (eq (cdr (assq 'inhibit-same-window received-alist)) t))
    ;; window-height should not be in the alist when direction is right
    ;; -- the action picks the matching size key based on direction.
    (let ((wh-cells (cl-remove-if-not
                     (lambda (cell) (eq (car-safe cell) 'window-height))
                     received-alist)))
      (should (null wh-cells)))))

(ert-deftest test-ai-term--display-saved-passes-buffer-through ()
  "Normal: BUFFER argument reaches display-buffer-in-direction unchanged."
  (let (received-buf
        (cj/--ai-term-last-direction 'right)
        (cj/--ai-term-last-size 0.5))
    (cl-letf (((symbol-function 'display-buffer-in-direction)
               (lambda (b _a) (setq received-buf b) 'fake-window)))
      (cj/--ai-term-display-saved 'sentinel-buffer nil))
    (should (eq received-buf 'sentinel-buffer))))

(ert-deftest test-ai-term--toggle-after-buffer-move-no-extra-window ()
  "Regression: toggle-off must not leak a window even when buffer-move
has cleared the agent window's `quit-restore' parameter.

Reproduces Craig's repro from 2026-05-09: 3 windows, user uses
buffer-move (C-M-arrows) to relocate agent.  buffer-move swaps
buffers between windows and leaves the receiving window with no
record that it was created for the agent buffer.

Assertion: after toggle-off+toggle-on, the agent is displayed exactly
once and no spurious extra window leaks."
  (cj/test--kill-agent-buffers)
  (let ((agent-name "agent [buffer-move-toggle]")
        (left-name "*test-bm-left*")
        (right-name "*test-bm-right*"))
    (unwind-protect
        (save-window-excursion
          (delete-other-windows)
          (let ((left-buf (get-buffer-create left-name))
                (right-buf (get-buffer-create right-name))
                (agent-buf (get-buffer-create agent-name)))
            (set-window-buffer (selected-window) left-buf)
            (let* ((right-win (split-window (selected-window) nil 'right))
                   (agent-win (split-window (selected-window) nil 'right)))
              (set-window-buffer right-win right-buf)
              (set-window-buffer agent-win agent-buf)
              ;; Mimic buffer-move's effect: agent lives in this
              ;; window but quit-restore says nothing about it.
              (set-window-parameter agent-win 'quit-restore nil)
              (let ((display-buffer-alist (cj/--ai-term-display-rule-list))
                    (window-count-before (count-windows)))
                (select-window agent-win)
                (cj/test--call-as-gui #'cj/ai-term) ; off
                (cj/test--call-as-gui #'cj/ai-term) ; on
                (should (<= (count-windows) window-count-before))
                ;; Agent must be displayed exactly once.
                (let ((agent-windows
                       (seq-filter
                        (lambda (w)
                          (eq (window-buffer w) agent-buf))
                        (window-list))))
                  (should (= (length agent-windows) 1)))))))
      (when (get-buffer left-name) (kill-buffer left-name))
      (when (get-buffer right-name) (kill-buffer right-name))
      (cj/test--kill-agent-buffers))))

(provide 'test-ai-term--display-saved)
;;; test-ai-term--display-saved.el ends here