diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-20 22:04:24 -0400 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-20 22:04:24 -0400 |
| commit | 93c16699304a9349f9678252b70cbc7efd2a4a1f (patch) | |
| tree | 921448a2d8204e1edb44fec265a6924344bbb55e /tests | |
| parent | 7cad6c584ce3a1ed5b759550264b9c5858deeb3d (diff) | |
| download | dotemacs-93c16699304a9349f9678252b70cbc7efd2a4a1f.tar.gz dotemacs-93c16699304a9349f9678252b70cbc7efd2a4a1f.zip | |
feat(ai-term): add s-F9 step-to-next-agent, drop C-S-F9 close alias
s-F9 (cj/ai-term-next) steps through the open agent buffers in name order. It's the "switch among existing agents" surface F9's toggle never provided. The cycle logic lives in a pure helper (cj/--ai-term-next-agent-buffer) with Normal/Boundary/Error coverage. The command is a thin window-mutating wrapper.
I dropped the C-S-F9 close alias, leaving M-F9 as the sole close binding. I moved cj/server-shutdown off C-<f10> to C-x C so the key keeps forwarding to the terminal program inside an agent buffer. I also removed the now-unused F10 entries from term-config's ghostel exceptions.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test-ai-term--f9-in-term.el | 18 | ||||
| -rw-r--r-- | tests/test-ai-term--next-agent-buffer.el | 73 |
2 files changed, 83 insertions, 8 deletions
diff --git a/tests/test-ai-term--f9-in-term.el b/tests/test-ai-term--f9-in-term.el index dad11ffc0..0477f2517 100644 --- a/tests/test-ai-term--f9-in-term.el +++ b/tests/test-ai-term--f9-in-term.el @@ -26,27 +26,29 @@ (should (eq (keymap-lookup ghostel-mode-map "<f9>") #'cj/ai-term))) (ert-deftest test-ai-term-f9-family-bound-in-ghostel-mode-map () - "Normal: the C-/M-/C-S- F9 variants are bound in `ghostel-mode-map' too. -`M-<f9>' and `C-S-<f9>' both close an agent via `cj/ai-term-close'." + "Normal: the C-/s-/M- F9 variants are bound in `ghostel-mode-map' too. +`s-<f9>' steps to the next agent; `M-<f9>' closes an agent via +`cj/ai-term-close'." (should (eq (keymap-lookup ghostel-mode-map "C-<f9>") #'cj/ai-term-pick-project)) - (should (eq (keymap-lookup ghostel-mode-map "M-<f9>") #'cj/ai-term-close)) - (should (eq (keymap-lookup ghostel-mode-map "C-S-<f9>") #'cj/ai-term-close))) + (should (eq (keymap-lookup ghostel-mode-map "s-<f9>") #'cj/ai-term-next)) + (should (eq (keymap-lookup ghostel-mode-map "M-<f9>") #'cj/ai-term-close))) (ert-deftest test-ai-term-f9-still-bound-globally () "Normal: the global F9 family bindings are intact. `<f9>' toggles the ai-term agent window; `C-<f9>' picks a project -agent; `M-<f9>' and `C-S-<f9>' close an agent via `cj/ai-term-close'." +agent; `s-<f9>' steps to the next agent; `M-<f9>' closes an agent +via `cj/ai-term-close'." (should (eq (lookup-key (current-global-map) (kbd "<f9>")) #'cj/ai-term)) (should (eq (lookup-key (current-global-map) (kbd "C-<f9>")) #'cj/ai-term-pick-project)) - (should (eq (lookup-key (current-global-map) (kbd "M-<f9>")) #'cj/ai-term-close)) - (should (eq (lookup-key (current-global-map) (kbd "C-S-<f9>")) #'cj/ai-term-close))) + (should (eq (lookup-key (current-global-map) (kbd "s-<f9>")) #'cj/ai-term-next)) + (should (eq (lookup-key (current-global-map) (kbd "M-<f9>")) #'cj/ai-term-close))) (ert-deftest test-ai-term-f9-family-in-keymap-exceptions () "Regression: the F9 family is in `ghostel-keymap-exceptions' so semi-char mode lets it reach Emacs instead of forwarding it to the terminal program. Binding in `ghostel-mode-map' alone is not enough -- the semi-char map outranks it and forwards any key not in the exceptions to the pty." - (dolist (key '("<f9>" "C-<f9>" "M-<f9>" "C-S-<f9>")) + (dolist (key '("<f9>" "C-<f9>" "s-<f9>" "M-<f9>")) (should (member key ghostel-keymap-exceptions))) ;; The rebuilt semi-char map must no longer forward <f9> to the pty. (should-not (eq (keymap-lookup ghostel-semi-char-mode-map "<f9>") diff --git a/tests/test-ai-term--next-agent-buffer.el b/tests/test-ai-term--next-agent-buffer.el new file mode 100644 index 000000000..330714a92 --- /dev/null +++ b/tests/test-ai-term--next-agent-buffer.el @@ -0,0 +1,73 @@ +;;; test-ai-term--next-agent-buffer.el --- Tests for cj/--ai-term-next-agent-buffer -*- lexical-binding: t; -*- + +;;; Commentary: +;; The pure decision helper behind `cj/ai-term-next' (s-F9). Given the +;; current agent buffer and the ordered list of live agent buffers, it +;; returns the next buffer in the queue, wrapping after the last. A nil +;; or non-member CURRENT returns the first; an empty list returns nil. +;; No buffer or window side effects -- list logic only. + +;;; Code: + +(require 'ert) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'ai-term) + +(ert-deftest test-ai-term--next-agent-buffer-advances-from-first () + "Normal: current is the first element -> returns the second." + (let ((a (get-buffer-create "agent [a]")) + (b (get-buffer-create "agent [b]")) + (c (get-buffer-create "agent [c]"))) + (unwind-protect + (should (eq b (cj/--ai-term-next-agent-buffer a (list a b c)))) + (mapc #'kill-buffer (list a b c))))) + +(ert-deftest test-ai-term--next-agent-buffer-advances-from-middle () + "Normal: current in the middle -> returns the following element." + (let ((a (get-buffer-create "agent [a]")) + (b (get-buffer-create "agent [b]")) + (c (get-buffer-create "agent [c]"))) + (unwind-protect + (should (eq c (cj/--ai-term-next-agent-buffer b (list a b c)))) + (mapc #'kill-buffer (list a b c))))) + +(ert-deftest test-ai-term--next-agent-buffer-wraps-after-last () + "Boundary: current is the last element -> wraps to the first." + (let ((a (get-buffer-create "agent [a]")) + (b (get-buffer-create "agent [b]")) + (c (get-buffer-create "agent [c]"))) + (unwind-protect + (should (eq a (cj/--ai-term-next-agent-buffer c (list a b c)))) + (mapc #'kill-buffer (list a b c))))) + +(ert-deftest test-ai-term--next-agent-buffer-single-element-returns-itself () + "Boundary: a one-agent queue wraps current back to itself." + (let ((a (get-buffer-create "agent [a]"))) + (unwind-protect + (should (eq a (cj/--ai-term-next-agent-buffer a (list a)))) + (kill-buffer a)))) + +(ert-deftest test-ai-term--next-agent-buffer-nil-current-returns-first () + "Boundary: nil current (no agent displayed) -> returns the first." + (let ((a (get-buffer-create "agent [a]")) + (b (get-buffer-create "agent [b]"))) + (unwind-protect + (should (eq a (cj/--ai-term-next-agent-buffer nil (list a b)))) + (mapc #'kill-buffer (list a b))))) + +(ert-deftest test-ai-term--next-agent-buffer-non-member-current-returns-first () + "Error: current not in the queue -> returns the first rather than nil." + (let ((a (get-buffer-create "agent [a]")) + (b (get-buffer-create "agent [b]")) + (stray (get-buffer-create "agent [stray]"))) + (unwind-protect + (should (eq a (cj/--ai-term-next-agent-buffer stray (list a b)))) + (mapc #'kill-buffer (list a b stray))))) + +(ert-deftest test-ai-term--next-agent-buffer-empty-queue-returns-nil () + "Boundary: an empty queue returns nil (nothing to switch to)." + (should (null (cj/--ai-term-next-agent-buffer nil '())))) + +(provide 'test-ai-term--next-agent-buffer) +;;; test-ai-term--next-agent-buffer.el ends here |
