aboutsummaryrefslogtreecommitdiff
path: root/tests/test-prog-general-lsp.el
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test-prog-general-lsp.el')
-rw-r--r--tests/test-prog-general-lsp.el148
1 files changed, 148 insertions, 0 deletions
diff --git a/tests/test-prog-general-lsp.el b/tests/test-prog-general-lsp.el
new file mode 100644
index 00000000..e61e9a17
--- /dev/null
+++ b/tests/test-prog-general-lsp.el
@@ -0,0 +1,148 @@
+;;; test-prog-general-lsp.el --- LSP config + helper tests -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for the LSP configuration in prog-general.el, the single owner of
+;; generic LSP policy (prog-lsp.el was folded in and removed 2026-07-10).
+;;
+;; Two layers:
+;; - Load-time invariants that hold the moment the config loads, before any
+;; server starts: lsp-enable-remote stays nil (so TRAMP files don't auto-start
+;; a slow LSP), and no mode accrues a duplicate lsp-deferred entry.
+;; - The pure helpers cj/lsp--add-file-watch-ignored-extras and
+;; cj/lsp--remove-eldoc-provider-global, exercised directly with Normal /
+;; Boundary / Error cases.
+;;
+;; The quiet-UI :config defaults (snippet off, symbol highlighting off,
+;; idle-delay 0.5, ...) are deferred to lsp-mode's own load (see the make-test
+;; no-package-initialize note in CLAUDE.md), so they aren't asserted here; the
+;; daemon verifies them.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+(require 'use-package)
+
+;; Declare lsp-mode's defcustom / eldoc's hook as special so `let' binds them
+;; dynamically. Real definitions load only when lsp-mode / eldoc activate; in
+;; the test environment these provide the value cells the helpers read through.
+(defvar lsp-file-watch-ignored-directories nil)
+(defvar eldoc-documentation-functions nil)
+
+(defun lsp-eldoc-function (&rest _args)
+ "Stub lsp-mode Eldoc function for tests.")
+
+(require 'prog-general)
+
+;;; Load-time invariants
+
+(ert-deftest test-prog-general-lsp-enable-remote-nil ()
+ "Normal: lsp-enable-remote is nil so LSP never auto-starts on TRAMP files."
+ (should (boundp 'lsp-enable-remote))
+ (should (null lsp-enable-remote)))
+
+(ert-deftest test-prog-general-lsp-no-duplicate-mode-hook ()
+ "Boundary: a mode never holds more than one lsp-deferred entry.
+The per-language modules add lsp-deferred to their own mode hooks; add-hook
+dedups identical symbols, and this pins that invariant so a future non-symbol
+\(lambda) addition that breaks it gets caught."
+ (dolist (hook '(c-mode-hook python-mode-hook go-ts-mode-hook))
+ (when (boundp hook)
+ (should (>= 1 (cl-count 'lsp-deferred (symbol-value hook)))))))
+
+;;; File-watch ignore helper — Normal
+
+(ert-deftest test-prog-general-lsp-file-watch-adds-all-patterns ()
+ "Normal: every entry from `cj/lsp-file-watch-ignored-extras' lands in the list."
+ (let ((lsp-file-watch-ignored-directories nil))
+ (cj/lsp--add-file-watch-ignored-extras)
+ (should (= (length lsp-file-watch-ignored-directories)
+ (length cj/lsp-file-watch-ignored-extras)))
+ (dolist (pattern cj/lsp-file-watch-ignored-extras)
+ (should (member pattern lsp-file-watch-ignored-directories)))))
+
+(ert-deftest test-prog-general-lsp-file-watch-extends-not-replaces ()
+ "Normal: pre-existing entries (lsp-mode defaults) are preserved."
+ (let ((lsp-file-watch-ignored-directories
+ '("[/\\\\]\\.git\\'" "[/\\\\]\\.svn\\'" "[/\\\\]\\.idea\\'")))
+ (cj/lsp--add-file-watch-ignored-extras)
+ (should (member "[/\\\\]\\.git\\'" lsp-file-watch-ignored-directories))
+ (should (member "[/\\\\]\\.svn\\'" lsp-file-watch-ignored-directories))
+ (should (member "[/\\\\]\\.idea\\'" lsp-file-watch-ignored-directories))
+ (dolist (pattern cj/lsp-file-watch-ignored-extras)
+ (should (member pattern lsp-file-watch-ignored-directories)))))
+
+(ert-deftest test-prog-general-lsp-file-watch-key-patterns-present ()
+ "Normal: specific expected directory names appear in the constant."
+ (dolist (name '("node_modules" "target" "__pycache__" ".venv" "venv"
+ "dist" "coverage" "test-results" "playwright-report"
+ ".terraform" ".ruff_cache" ".pytest_cache" ".mypy_cache"))
+ (should (cl-some (lambda (p) (string-match-p (regexp-quote name) p))
+ cj/lsp-file-watch-ignored-extras))))
+
+;;; File-watch ignore helper — Boundary
+
+(ert-deftest test-prog-general-lsp-file-watch-idempotent ()
+ "Boundary: calling twice leaves each pattern present exactly once."
+ (let ((lsp-file-watch-ignored-directories nil))
+ (cj/lsp--add-file-watch-ignored-extras)
+ (cj/lsp--add-file-watch-ignored-extras)
+ (should (= (length lsp-file-watch-ignored-directories)
+ (length cj/lsp-file-watch-ignored-extras)))
+ (dolist (pattern cj/lsp-file-watch-ignored-extras)
+ (should (= 1 (cl-count pattern lsp-file-watch-ignored-directories
+ :test #'equal))))))
+
+(ert-deftest test-prog-general-lsp-file-watch-all-patterns-non-empty ()
+ "Boundary: every pattern is a non-empty string."
+ (dolist (pattern cj/lsp-file-watch-ignored-extras)
+ (should (stringp pattern))
+ (should (not (string-empty-p pattern)))))
+
+(ert-deftest test-prog-general-lsp-file-watch-all-patterns-valid-regex ()
+ "Boundary: every pattern compiles as a valid Emacs regex."
+ (dolist (pattern cj/lsp-file-watch-ignored-extras)
+ (condition-case err
+ ;; string-match-p compiles the regex; invalid syntax raises invalid-regexp.
+ (string-match-p pattern "/some/sample/path")
+ (invalid-regexp
+ (ert-fail (format "Invalid regex %S: %s"
+ pattern (error-message-string err)))))))
+
+;;; File-watch ignore helper — Error
+
+(ert-deftest test-prog-general-lsp-file-watch-non-list-target ()
+ "Error: non-list target value triggers `add-to-list' wrong-type-argument."
+ (let ((lsp-file-watch-ignored-directories "not-a-list"))
+ (should-error (cj/lsp--add-file-watch-ignored-extras)
+ :type 'wrong-type-argument)))
+
+;;; Eldoc provider helper
+
+(ert-deftest test-prog-general-lsp-eldoc-provider-removed-globally ()
+ "Normal: remove lsp-mode's Eldoc provider from the global hook value.
+The per-buffer removal this replaced raced lsp-mode's own buffer-local hook
+population; removing globally before any LSP buffer attaches makes the absence
+stick for every subsequent lsp-managed buffer."
+ (let ((eldoc-documentation-functions
+ (list #'lsp-eldoc-function 'eldoc-documentation-default)))
+ (cj/lsp--remove-eldoc-provider-global)
+ (should-not (memq #'lsp-eldoc-function eldoc-documentation-functions))
+ (should (memq 'eldoc-documentation-default eldoc-documentation-functions))))
+
+(ert-deftest test-prog-general-lsp-eldoc-provider-removal-idempotent ()
+ "Boundary: re-running the removal after the provider is gone is a no-op."
+ (let ((eldoc-documentation-functions '(eldoc-documentation-default)))
+ (cj/lsp--remove-eldoc-provider-global)
+ (cj/lsp--remove-eldoc-provider-global)
+ (should (equal eldoc-documentation-functions
+ '(eldoc-documentation-default)))))
+
+(ert-deftest test-prog-general-lsp-no-obsolete-eldoc-hook-reference ()
+ "Regression: prog-general should not reference obsolete `lsp-eldoc-hook'."
+ (with-temp-buffer
+ (insert-file-contents (expand-file-name "modules/prog-general.el" user-emacs-directory))
+ (should-not (re-search-forward "\\_<lsp-eldoc-hook\\_>" nil t))))
+
+(provide 'test-prog-general-lsp)
+;;; test-prog-general-lsp.el ends here