aboutsummaryrefslogtreecommitdiff
path: root/tests/test-ai-config-commands.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-14 04:17:32 -0500
committerCraig Jennings <c@cjennings.net>2026-05-14 04:17:32 -0500
commit7994ee3d0f68d1cda261b4b6ed2a82671d9095eb (patch)
treed25b1e881ad0d12b05ddd899e904a6fedce31c36 /tests/test-ai-config-commands.el
parentb517ca7ea5f3e09203bc5a15c14194061d848fa9 (diff)
downloaddotemacs-7994ee3d0f68d1cda261b4b6ed2a82671d9095eb.tar.gz
dotemacs-7994ee3d0f68d1cda261b4b6ed2a82671d9095eb.zip
test(ai-config): cover available-backends, change-model, add-file, toggle, context-clear
Diffstat (limited to 'tests/test-ai-config-commands.el')
-rw-r--r--tests/test-ai-config-commands.el160
1 files changed, 160 insertions, 0 deletions
diff --git a/tests/test-ai-config-commands.el b/tests/test-ai-config-commands.el
new file mode 100644
index 00000000..8da2e4b0
--- /dev/null
+++ b/tests/test-ai-config-commands.el
@@ -0,0 +1,160 @@
+;;; test-ai-config-commands.el --- Tests for ai-config interactive commands -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Sibling tests cover the pure helpers (model-to-string, build-model-list,
+;; current-model-selection, fresh-org-prefix, backend-and-model). This
+;; file covers the user-facing wrappers:
+;;
+;; cj/gptel--available-backends
+;; cj/gptel-change-model
+;; cj/gptel-add-file
+;; cj/gptel-add-this-buffer
+;; cj/toggle-gptel
+;; cj/gptel-context-clear
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+(require 'ai-config)
+
+;; Top-level defvars so let-bindings reach the dynamic binding under
+;; lexical scope.
+(defvar gptel-backend nil)
+(defvar gptel-model nil)
+(defvar gptel-claude-backend nil)
+(defvar gptel-chatgpt-backend nil)
+(defvar gptel-context--alist nil)
+
+;;; cj/gptel--available-backends
+
+(ert-deftest test-ai-available-backends-returns-claude-and-chatgpt ()
+ "Normal: both backends present become alist entries."
+ (let ((gptel-claude-backend 'claude-obj)
+ (gptel-chatgpt-backend 'chatgpt-obj))
+ (cl-letf (((symbol-function 'require) (lambda (&rest _) t))
+ ((symbol-function 'cj/ensure-gptel-backends) #'ignore))
+ (let ((result (cj/gptel--available-backends)))
+ (should (equal (assoc "Anthropic - Claude" result)
+ '("Anthropic - Claude" . claude-obj)))
+ (should (equal (assoc "OpenAI - ChatGPT" result)
+ '("OpenAI - ChatGPT" . chatgpt-obj)))))))
+
+(ert-deftest test-ai-available-backends-skips-nil-entries ()
+ "Boundary: only configured backends appear in the alist."
+ (let ((gptel-claude-backend nil)
+ (gptel-chatgpt-backend 'chatgpt-only))
+ (cl-letf (((symbol-function 'require) (lambda (&rest _) t))
+ ((symbol-function 'cj/ensure-gptel-backends) #'ignore))
+ (let ((result (cj/gptel--available-backends)))
+ (should-not (assoc "Anthropic - Claude" result))
+ (should (assoc "OpenAI - ChatGPT" result))))))
+
+;;; cj/gptel-change-model
+
+(ert-deftest test-ai-change-model-global-sets-globals-and-messages ()
+ "Normal: choosing 'global' sets `gptel-backend' and `gptel-model'
+globally and reports via `message'."
+ (let ((gptel-backend 'old-backend)
+ (gptel-model 'old-model)
+ (gptel-claude-backend 'claude-obj)
+ (gptel-chatgpt-backend nil)
+ msg)
+ (cl-letf (((symbol-function 'require) (lambda (&rest _) t))
+ ((symbol-function 'cj/ensure-gptel-backends) #'ignore)
+ ((symbol-function 'gptel-backend-models)
+ (lambda (_) '("claude-opus-4-7")))
+ ((symbol-function 'completing-read)
+ (lambda (prompt &rest _)
+ (if (string-prefix-p "Set model for" prompt)
+ "global"
+ "Anthropic - Claude: claude-opus-4-7")))
+ ((symbol-function 'message)
+ (lambda (fmt &rest args) (setq msg (apply #'format fmt args)))))
+ (cj/gptel-change-model))
+ (should (eq gptel-backend 'claude-obj))
+ (should (eq gptel-model 'claude-opus-4-7))
+ (should (string-match-p "global" msg))))
+
+;;; cj/gptel-add-file
+
+(ert-deftest test-ai-add-file-outside-projectile-uses-read-file-name ()
+ "Normal: without projectile, add-file routes through read-file-name."
+ (let* ((target (make-temp-file "cj-ai-add-file-" nil ".org"))
+ added)
+ (unwind-protect
+ (cl-letf (((symbol-function 'featurep)
+ (lambda (sym) (not (eq sym 'projectile))))
+ ((symbol-function 'read-file-name)
+ (lambda (&rest _) target))
+ ((symbol-function 'gptel-add-file)
+ (lambda (f) (setq added f)))
+ ((symbol-function 'message) #'ignore))
+ (cj/gptel-add-file))
+ (delete-file target))
+ (should (equal added target))))
+
+;;; cj/gptel-add-this-buffer
+
+(ert-deftest test-ai-add-this-buffer-calls-gptel-add-with-prefix ()
+ "Normal: add-this-buffer calls `gptel-add' with the prefix-arg form."
+ (let (gptel-add-args msg)
+ (cl-letf (((symbol-function 'require) (lambda (&rest _) t))
+ ((symbol-function 'gptel-add)
+ (lambda (&rest args) (setq gptel-add-args args)))
+ ((symbol-function 'message)
+ (lambda (fmt &rest args) (setq msg (apply #'format fmt args)))))
+ (cj/gptel-add-this-buffer))
+ (should (equal gptel-add-args '((4))))
+ (should (string-match-p "to GPTel context" msg))))
+
+;;; cj/toggle-gptel
+
+(ert-deftest test-ai-toggle-gptel-hides-when-visible ()
+ "Normal: when the AI buffer is showing in a window, toggle hides it."
+ (let ((buffer (get-buffer-create "*AI-Assistant*"))
+ deleted-window)
+ (unwind-protect
+ (cl-letf (((symbol-function 'get-buffer-window)
+ (lambda (&rest _) 'fake-window))
+ ((symbol-function 'delete-window)
+ (lambda (w) (setq deleted-window w))))
+ (cj/toggle-gptel))
+ (kill-buffer buffer))
+ (should (eq deleted-window 'fake-window))))
+
+;;; cj/gptel-context-clear
+
+(ert-deftest test-ai-context-clear-uses-remove-all-when-available ()
+ "Normal: with `gptel-context-remove-all' present, it is called."
+ (let (called msg)
+ (cl-letf (((symbol-function 'gptel-context-remove-all)
+ (lambda () (setq called t)))
+ ((symbol-function 'call-interactively)
+ (lambda (fn) (funcall fn)))
+ ((symbol-function 'message)
+ (lambda (fmt &rest args) (setq msg (apply #'format fmt args)))))
+ (cj/gptel-context-clear))
+ (should called)
+ (should (string-match-p "cleared" msg))))
+
+(ert-deftest test-ai-context-clear-resets-alist-as-fallback ()
+ "Boundary: when no clear function exists but the alist does, it gets
+nilled directly."
+ (let ((gptel-context--alist '("item1" "item2"))
+ msg)
+ ;; Make sure the fboundp branches are skipped.
+ (cl-letf (((symbol-function 'fboundp)
+ (lambda (sym)
+ (not (memq sym '(gptel-context-remove-all
+ gptel-context-clear)))))
+ ((symbol-function 'message)
+ (lambda (fmt &rest args) (setq msg (apply #'format fmt args)))))
+ (cj/gptel-context-clear))
+ (should-not gptel-context--alist)
+ (should (string-match-p "cleared" msg))))
+
+(provide 'test-ai-config-commands)
+;;; test-ai-config-commands.el ends here