diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-14 04:17:32 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-14 04:17:32 -0500 |
| commit | 7994ee3d0f68d1cda261b4b6ed2a82671d9095eb (patch) | |
| tree | d25b1e881ad0d12b05ddd899e904a6fedce31c36 /tests/test-ai-config-commands.el | |
| parent | b517ca7ea5f3e09203bc5a15c14194061d848fa9 (diff) | |
| download | dotemacs-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.el | 160 |
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 |
