blob: e61e9a1751e7c645f509393a357cc328f0ff1ce6 (
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
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
|