aboutsummaryrefslogtreecommitdiff
path: root/tests/test-ai-term--close.el
blob: 8b0283519be8f73dbb382ec7920c4ec6651ddb56 (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
;;; test-ai-term--close.el --- Tests for graceful agent close -*- lexical-binding: t; -*-

;;; Commentary:
;; `cj/ai-term-close' tears an agent down gracefully: kill its tmux
;; session (stopping the agent process), kill the agent buffer, and
;; remove its window.  These tests cover the pure pieces -- the
;; tmux-kill helper, the per-buffer teardown, and the target selection --
;; with `process-file' and the prompt mocked at the boundary.

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

(ert-deftest test-ai-term--kill-tmux-session-runs-kill-session ()
  "Normal: invokes `tmux kill-session -t <session>'."
  (let (captured)
    (cl-letf (((symbol-function 'process-file)
               (lambda (program &rest args)
                 (setq captured (cons program args))
                 0)))
      (cj/--ai-term-kill-tmux-session "aiv-foo"))
    (should (equal (car captured) "tmux"))
    (should (member "kill-session" captured))
    (should (member "-t" captured))
    (should (member "aiv-foo" captured))))

(ert-deftest test-ai-term--kill-tmux-session-swallows-error ()
  "Error: returns nil when tmux is unavailable (process-file signals)."
  (cl-letf (((symbol-function 'process-file)
             (lambda (&rest _) (error "no tmux"))))
    (should (null (cj/--ai-term-kill-tmux-session "aiv-foo")))))

(ert-deftest test-ai-term--close-buffer-session-from-name-after-cd ()
  "Regression: the session name comes from the immutable buffer name.
ghostel retargets `default-directory' via OSC 7 as the shell cds, so
deriving from it after a cd kills the wrong aiv- session (or misses,
orphaning the agent).  The buffer name's basename never changes."
  (let ((buf (get-buffer-create "agent [proj]"))
        captured-session)
    (with-current-buffer buf (setq-local default-directory "/tmp/elsewhere/"))
    (cl-letf (((symbol-function 'cj/--ai-term-kill-tmux-session)
               (lambda (s) (setq captured-session s) 0)))
      (cj/--ai-term-close-buffer buf))
    (should (equal captured-session "aiv-proj"))
    (should-not (buffer-live-p buf))))

(ert-deftest test-ai-term--close-buffer-kills-session-and-buffer ()
  "Normal: derives the session from the buffer name, kills it and the buffer."
  (let ((buf (get-buffer-create "agent [foo]"))
        captured-session)
    (with-current-buffer buf (setq-local default-directory "/tmp/foo/"))
    (cl-letf (((symbol-function 'cj/--ai-term-kill-tmux-session)
               (lambda (s) (setq captured-session s) 0)))
      (cj/--ai-term-close-buffer buf))
    (should (equal captured-session "aiv-foo"))
    (should-not (buffer-live-p buf))))

(ert-deftest test-ai-term--close-buffer-noop-on-non-agent ()
  "Boundary: does nothing for a buffer that is not an agent buffer."
  (let ((buf (get-buffer-create "*not-an-agent*"))
        (called nil))
    (unwind-protect
        (progn
          (cl-letf (((symbol-function 'cj/--ai-term-kill-tmux-session)
                     (lambda (_s) (setq called t) 0)))
            (cj/--ai-term-close-buffer buf))
          (should-not called)
          (should (buffer-live-p buf)))
      (when (buffer-live-p buf) (kill-buffer buf)))))

(ert-deftest test-ai-term--close-buffer-keeps-window-split ()
  "Regression: closing an agent in a split keeps its window in the layout,
showing a non-agent buffer, instead of deleting the split.  Craig's M-F9
annoyance -- a close must not tear down the window arrangement (the F9 hide
toggle is what collapses the split; close should not)."
  (cj/test--kill-agent-buffers)
  (let ((work (get-buffer-create "*test-close-keep-work*"))
        (agent (get-buffer-create "agent [close-keep]")))
    (with-current-buffer agent (setq-local default-directory "/tmp/close-keep/"))
    (unwind-protect
        (save-window-excursion
          (delete-other-windows)
          (set-window-buffer (selected-window) work)
          (let ((agent-win (split-window (selected-window) nil 'below)))
            (set-window-buffer agent-win agent)
            (should-not (one-window-p))
            (cl-letf (((symbol-function 'cj/--ai-term-kill-tmux-session)
                       (lambda (_s) 0)))
              (cj/--ai-term-close-buffer agent))
            ;; The window survives the close ...
            (should (window-live-p agent-win))
            (should-not (one-window-p))
            ;; ... now showing a non-agent buffer ...
            (should-not (cj/--ai-term-buffer-p (window-buffer agent-win)))
            ;; ... and the agent buffer itself is gone.
            (should-not (buffer-live-p agent))))
      (when (get-buffer "*test-close-keep-work*") (kill-buffer "*test-close-keep-work*"))
      (cj/test--kill-agent-buffers))))

(ert-deftest test-ai-term--close-target-current-agent-buffer ()
  "Normal: returns the current buffer when it is an agent buffer."
  (let ((buf (get-buffer-create "agent [cur]")))
    (unwind-protect
        (with-current-buffer buf
          (should (eq (cj/--ai-term-close-target) buf)))
      (kill-buffer buf))))

(ert-deftest test-ai-term--close-target-sole-agent ()
  "Normal: returns the only live agent buffer when current isn't an agent."
  (let ((buf (get-buffer-create "agent [only]")))
    (unwind-protect
        (with-temp-buffer
          (cl-letf (((symbol-function 'cj/--ai-term-agent-buffers)
                     (lambda () (list buf))))
            (should (eq (cj/--ai-term-close-target) buf))))
      (kill-buffer buf))))

(ert-deftest test-ai-term--close-target-none-returns-nil ()
  "Boundary: nil when current buffer isn't an agent and none are alive."
  (with-temp-buffer
    (cl-letf (((symbol-function 'cj/--ai-term-agent-buffers) (lambda () nil)))
      (should (null (cj/--ai-term-close-target))))))

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