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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
|
;;; test-auto-dim-config.el --- Tests for the auto-dim-other-buffers config -*- lexical-binding: t; -*-
;;; Commentary:
;; auto-dim-config configures the local auto-dim-other-buffers fork: dim only
;; non-selected windows within Emacs (not the whole frame on focus-out), drop
;; fringe from the dimmed faces to avoid flicker on this non-pgtk build, and
;; enable the global mode. Guarded with `skip-unless' because the fork lives
;; in ~/code and may be absent on a clean checkout.
;;
;; The vterm dim-integration tests were removed when the terminal engine moved
;; off vterm. EAT (the current engine) renders in real Emacs faces and uses the
;; `default' face for its background, so terminal buffers dim like any other
;; buffer with no dedicated integration.
;;; Code:
(require 'ert)
(require 'cl-lib)
(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
(defconst test-auto-dim--fork
(expand-file-name "~/code/auto-dim-other-buffers.el")
"Local fork directory the module loads via `:load-path'.")
(ert-deftest test-auto-dim-config-applies-settings ()
"Normal: loading the module enables the mode with the chosen settings."
(skip-unless (file-directory-p test-auto-dim--fork))
(require 'auto-dim-config)
(unwind-protect
(progn
(should (bound-and-true-p auto-dim-other-buffers-mode))
(should (null auto-dim-other-buffers-dim-on-focus-out))
;; Config intent only: this asserts the value the module just set, so it
;; cannot fail even if the fork inverts what the flag MEANS. The two
;; behavioral tests below are what actually pin the behavior.
(should (null auto-dim-other-buffers-dim-on-switch-to-minibuffer))
(should-not (assq 'fringe auto-dim-other-buffers-affected-faces)))
(when (fboundp 'auto-dim-other-buffers-mode)
(auto-dim-other-buffers-mode -1))))
(defmacro test-auto-dim--with-two-windows (win-a win-b &rest body)
"Bind WIN-A and WIN-B to two live windows with the mode on, then run BODY.
Restores the window configuration, the buffers, and the mode's PRIOR state.
Restoring rather than force-disabling matters: `auto-dim-other-buffers-mode' is
global, and switching it off unconditionally left a later test in this file
asserting the mode is on with it off. That only stayed hidden because ERT runs
tests alphabetically and the asserting test sorts first."
(declare (indent 2))
`(let ((config (current-window-configuration))
(was-on (bound-and-true-p auto-dim-other-buffers-mode)))
(unwind-protect
(let* ((,win-a (selected-window))
(,win-b (split-window)))
(set-window-buffer ,win-a (get-buffer-create " *adob-a*"))
(set-window-buffer ,win-b (get-buffer-create " *adob-b*"))
(select-window ,win-a)
(auto-dim-other-buffers-mode 1)
,@body)
(when (fboundp 'auto-dim-other-buffers-mode)
(auto-dim-other-buffers-mode (if was-on 1 -1)))
(set-window-configuration config)
(dolist (name '(" *adob-a*" " *adob-b*"))
(when (get-buffer name) (kill-buffer name))))))
(ert-deftest test-auto-dim-config-minibuffer-entry-leaves-previous-window-lit ()
"Normal: with the flag nil, entering the minibuffer leaves the previous window lit.
This is the half that works, and the reason the flag is set to nil.
Deliberately asserts the composite behavior rather than naming one function.
Selecting the minibuffer fires the mode's own hooks, so the observable outcome is
not attributable to the explicit `adob--update' call alone -- and the observable
outcome is what the setting promises the user.
The `win-b' assertions are positive controls. Without them this test passes
against an implementation where dimming is broken everywhere, which looks
identical to the implementation being correct."
(skip-unless (file-directory-p test-auto-dim--fork))
(require 'auto-dim-config)
(test-auto-dim--with-two-windows win-a win-b
(adob--rescan-windows)
(should (null (window-parameter win-a 'adob--dim)))
(should (window-parameter win-b 'adob--dim))
(select-window (minibuffer-window))
(adob--update)
(should (null (window-parameter win-a 'adob--dim)))
(should (window-parameter win-b 'adob--dim))))
(ert-deftest test-auto-dim-config-minibuffer-entry-dims-when-flag-is-t ()
"Boundary: with the flag t, entering the minibuffer DOES dim the previous window.
This is what gives the nil setting meaning. Without it the suite never shows the
flag changing anything, so the config assertion in
`test-auto-dim-config-applies-settings' has nothing standing behind it."
(skip-unless (file-directory-p test-auto-dim--fork))
(require 'auto-dim-config)
(test-auto-dim--with-two-windows win-a win-b
(let ((auto-dim-other-buffers-dim-on-switch-to-minibuffer t))
(adob--rescan-windows)
(should (null (window-parameter win-a 'adob--dim)))
(select-window (minibuffer-window))
(adob--update)
(should (window-parameter win-a 'adob--dim)))))
(ert-deftest test-auto-dim-config-rescan-ignores-the-minibuffer-flag ()
"Error: `adob--rescan-windows' dims everything on a minibuffer selection.
Known defect in the fork, pinned here rather than left undocumented. The
rescan is on `window-configuration-change-hook' and dims by window identity
alone -- and `(window-list nil \\='n)' excludes the minibuffer, so when the
minibuffer is selected nothing matches and every window dims. A completion
popup is the everyday case: `adob--update' honours the flag, this does not.
Expected to fail until the fork honours the flag in the rescan too. When it
starts passing, ERT reports an unexpected pass -- that is the signal to drop
this test and stop treating the gap as open.
The `win-b' positive control is load-bearing here. Three different broken
implementations -- dimming disabled everywhere, the rescan never setting the
parameter, `adob--update' made a no-op -- all produce an unexpected pass that
would otherwise read as \"the fork fixed it\". Asserting that `win-b' is still
dimmed separates a real fix from dimming having broken."
:expected-result :failed
(skip-unless (file-directory-p test-auto-dim--fork))
(require 'auto-dim-config)
(test-auto-dim--with-two-windows win-a win-b
(adob--rescan-windows)
(should (null (window-parameter win-a 'adob--dim)))
(should (window-parameter win-b 'adob--dim))
(select-window (minibuffer-window))
(adob--rescan-windows)
(should (window-parameter win-b 'adob--dim))
(should (null (window-parameter win-a 'adob--dim)))))
(defconst test-auto-dim--flat-dimmed-org-faces
(append (mapcar (lambda (n) (intern (format "org-level-%d" n)))
(number-sequence 1 8))
'(org-link org-tag
;; Document header: #+TITLE:, #+AUTHOR:, #+ARCHIVE: and their values.
org-document-title org-document-info org-document-info-keyword
org-meta-line
;; Inline markup and blocks.
org-code org-verbatim org-block-begin-line org-block-end-line
;; Drawers, properties, planning lines.
org-drawer org-special-keyword org-property-value org-date
;; Tables and the fold indicator.
org-table org-table-row org-ellipsis))
"Org faces that must flat-dim to the `auto-dim-other-buffers' face.
These carry structure, not status: nothing about them needs to stay
readable in a window the user is not looking at. Excluded on purpose are
`org-todo' and `org-priority' (keyword class -- see the -dim variant test
below) and `org-hide' (needs `auto-dim-other-buffers-hide' so folded text
stays hidden).")
(defconst test-auto-dim--flat-dimmed-link-faces
'(link link-visited)
"Built-in link faces that must flat-dim, distinct from `org-link'.
They fontify links in help, info, and customize buffers. Both carry
`:underline t', which survives the relative remap, so a dimmed link still
reads as a link.")
(defconst test-auto-dim--flat-dimmed-superstar-faces
'(org-superstar-header-bullet org-superstar-item org-superstar-first)
"org-superstar faces that must flat-dim.
org-superstar puts its own face ahead of the org face beneath, so a heading
star renders as (org-superstar-header-bullet org-level-1) and wins over the
dimmed org-level-1. Without these, bullets stay lit in an unfocused window
even though every face under them dims.
`org-superstar-leading' is excluded on purpose -- see the test below.")
(defconst test-auto-dim--hide-class-faces
'(org-hide org-superstar-leading org-indent)
"Faces whose foreground IS the background colour.
That is what makes them invisible. They take `auto-dim-other-buffers-hide',
never the flat dim, which would paint them visible grey.")
(defconst test-auto-dim--no-foreground-faces
'(bold italic underline)
"Faces that carry no foreground, even through inheritance.
They set weight, slant or underline only, so text wearing them takes its
colour from `default', which is already remapped. They need no entry and
must not gain one, or the alist grows entries that do nothing.")
(defconst test-auto-dim--keyword-dim-variants
'((org-faces-todo . org-faces-todo-dim)
(org-faces-doing . org-faces-doing-dim)
(org-faces-priority-a . org-faces-priority-a-dim))
"Sample of keyword faces that must keep dedicated -dim variants.")
(ert-deftest test-auto-dim-config-org-structure-faces-flat-dim ()
"Normal: org heading, link, and tag faces remap to the flat dim face."
(skip-unless (file-directory-p test-auto-dim--fork))
(require 'auto-dim-config)
(dolist (face test-auto-dim--flat-dimmed-org-faces)
(let ((entry (assq face auto-dim-other-buffers-affected-faces)))
(should entry)
(should (eq 'auto-dim-other-buffers (car (cdr entry))))
(should (null (cdr (cdr entry)))))))
(ert-deftest test-auto-dim-config-link-faces-flat-dim ()
"Normal: the built-in `link' and `link-visited' faces flat-dim.
Without these, links in help, info, and customize buffers stay lit while
the rest of an unfocused window fades. `org-link' is a separate face and
is covered above."
(skip-unless (file-directory-p test-auto-dim--fork))
(require 'auto-dim-config)
(dolist (face test-auto-dim--flat-dimmed-link-faces)
(let ((entry (assq face auto-dim-other-buffers-affected-faces)))
(should entry)
(should (eq 'auto-dim-other-buffers (car (cdr entry))))
(should (null (cdr (cdr entry)))))))
(ert-deftest test-auto-dim-config-link-underline-survives-the-remap ()
"Boundary: the dim face sets no `:underline', so the link cue survives.
A relative remap layers the dim face over the base face, so an underline
the dim face does not specify falls through from `link'. If the theme ever
gives `auto-dim-other-buffers' an `:underline', dimmed links stop looking
like links and this test says so."
(skip-unless (file-directory-p test-auto-dim--fork))
(require 'auto-dim-config)
(should (eq 'unspecified
(face-attribute 'auto-dim-other-buffers :underline nil nil))))
(ert-deftest test-auto-dim-config-superstar-bullets-flat-dim ()
"Normal: org-superstar's heading stars and list bullets flat-dim.
They were the last thing left lit in an unfocused org window."
(skip-unless (file-directory-p test-auto-dim--fork))
(require 'auto-dim-config)
(dolist (face test-auto-dim--flat-dimmed-superstar-faces)
(let ((entry (assq face auto-dim-other-buffers-affected-faces)))
(should entry)
(should (eq 'auto-dim-other-buffers (car (cdr entry))))
(should (null (cdr (cdr entry)))))))
(ert-deftest test-auto-dim-config-superstar-leading-uses-hide-face ()
"Error: `org-superstar-leading' takes the -hide face, never the flat dim.
Its foreground is the background colour, which is what keeps hidden leading
stars invisible. Flat-dimming it would give them the dim face's visible grey
and reveal stars the user chose to hide. Same contract as `org-hide'."
(skip-unless (file-directory-p test-auto-dim--fork))
(require 'auto-dim-config)
(should-not (memq 'org-superstar-leading
test-auto-dim--flat-dimmed-superstar-faces))
(let ((entry (assq 'org-superstar-leading auto-dim-other-buffers-affected-faces)))
(should entry)
(should (eq 'auto-dim-other-buffers-hide (car (cdr entry))))))
(ert-deftest test-auto-dim-config-hide-class-faces-use-hide-face ()
"Error: every background-coloured face takes the -hide face.
`org-hide', `org-superstar-leading' and `org-indent' all resolve to the
background colour, which is what keeps folded text, leading stars and indent
prefixes invisible. Flat-dimming any of them reveals what the user hid."
(skip-unless (file-directory-p test-auto-dim--fork))
(require 'auto-dim-config)
(dolist (face test-auto-dim--hide-class-faces)
(let ((entry (assq face auto-dim-other-buffers-affected-faces)))
(should entry)
(should (eq 'auto-dim-other-buffers-hide (car (cdr entry)))))))
(ert-deftest test-auto-dim-config-no-org-face-left-unmapped ()
"Boundary: a fontified org buffer uses no face we forgot to handle.
Four rounds of this bug all had the same shape: a face nobody enumerated,
sitting ahead of a mapped face in a face list and outranking it. This walks
a representative buffer, collects every face it actually uses (including the
`line-prefix' and `wrap-prefix' org-indent hangs its faces on), and fails on
anything that is neither mapped nor deliberately excluded.
Built-in org only. org-superstar and org-drill are elpa packages, and the
test run has no `package-initialize', so their faces are pinned by name in
the tests above instead."
(skip-unless (file-directory-p test-auto-dim--fork))
(require 'auto-dim-config)
(require 'org)
(let ((used (make-hash-table :test #'eq))
(allowed (append test-auto-dim--no-foreground-faces
;; Keyword class: deliberately unmapped so status stays
;; readable in an unfocused window. Pinned by
;; test-auto-dim-config-todo-priority-faces-not-flat-dimmed.
'(org-todo org-priority)
(mapcar #'car auto-dim-other-buffers-affected-faces))))
(with-temp-buffer
(insert "#+TITLE: T\n#+AUTHOR: A\n\n* H1 :tag:\n** TODO [#A] task\n"
"DEADLINE: <2026-07-10 Fri>\n:PROPERTIES:\n:K: v\n:END:\n"
"Body ~verbatim~ =code= [[https://x.org][link]].\n"
"| a | b |\n|---+---|\n| 1 | 2 |\n"
"#+begin_src sh\necho hi\n#+end_src\n"
"- [X] done item\n")
(org-mode)
(font-lock-ensure)
(let ((p (point-min)))
(while (< p (point-max))
(dolist (f (let ((v (get-text-property p 'face)))
(if (listp v) v (list v))))
(when (and f (symbolp f)) (puthash f t used)))
(dolist (prop '(line-prefix wrap-prefix))
(let ((s (get-text-property p prop)))
(when (stringp s)
(dolist (f (let ((v (get-text-property 0 'face s)))
(if (listp v) v (list v))))
(when (and f (symbolp f)) (puthash f t used))))))
(setq p (1+ p)))))
(let (unmapped)
(maphash (lambda (face _v)
(unless (memq face allowed) (push face unmapped)))
used)
(should (equal nil (sort unmapped #'string<))))))
(ert-deftest test-auto-dim-config-keyword-faces-keep-dim-variants ()
"Boundary: org TODO-keyword faces keep dedicated -dim variants, not flat dim.
Keyword status is scanned across unfocused windows, so it earns a variant;
heading colour does not. Guards the flat-dim change from over-reaching."
(skip-unless (file-directory-p test-auto-dim--fork))
(require 'auto-dim-config)
(dolist (pair test-auto-dim--keyword-dim-variants)
(let ((entry (assq (car pair) auto-dim-other-buffers-affected-faces)))
(should entry)
(should (eq (cdr pair) (car (cdr entry)))))))
(ert-deftest test-auto-dim-config-todo-priority-faces-not-flat-dimmed ()
"Boundary: `org-todo' and `org-priority' are never flat-dimmed.
They are keyword class. Dimming them would erase the status colour the
-dim variants exist to preserve, so they stay out of the flat-dim set."
(skip-unless (file-directory-p test-auto-dim--fork))
(require 'auto-dim-config)
(dolist (face '(org-todo org-priority))
(should-not (memq face test-auto-dim--flat-dimmed-org-faces))
(let ((entry (assq face auto-dim-other-buffers-affected-faces)))
(should-not (and entry (eq 'auto-dim-other-buffers (car (cdr entry))))))))
(ert-deftest test-auto-dim-config-org-hide-uses-hide-face ()
"Boundary: `org-hide' remaps to the -hide face, not the flat dim face.
Flat-dimming it would give folded text a visible foreground."
(skip-unless (file-directory-p test-auto-dim--fork))
(require 'auto-dim-config)
(let ((entry (assq 'org-hide auto-dim-other-buffers-affected-faces)))
(should entry)
(should (eq 'auto-dim-other-buffers-hide (car (cdr entry))))))
(ert-deftest test-auto-dim-config-never-dim-dashboard-exempts-dashboard ()
"Normal: the *dashboard* buffer is exempt from dimming."
(skip-unless (file-directory-p test-auto-dim--fork))
(require 'auto-dim-config)
(let* ((existed (get-buffer "*dashboard*"))
(buffer (or existed (get-buffer-create "*dashboard*"))))
(unwind-protect
(should (cj/auto-dim--never-dim-dashboard-p buffer))
(unless existed (kill-buffer buffer)))))
(ert-deftest test-auto-dim-config-never-dim-dashboard-near-miss-name-dims ()
"Boundary: a buffer whose name only resembles the dashboard is not exempt."
(skip-unless (file-directory-p test-auto-dim--fork))
(require 'auto-dim-config)
(let ((buffer (get-buffer-create "dashboard")))
(unwind-protect
(should-not (cj/auto-dim--never-dim-dashboard-p buffer))
(kill-buffer buffer))))
(ert-deftest test-auto-dim-config-never-dim-dashboard-other-buffer-dims ()
"Error: an ordinary buffer is not exempt from dimming."
(skip-unless (file-directory-p test-auto-dim--fork))
(require 'auto-dim-config)
(with-temp-buffer
(should-not (cj/auto-dim--never-dim-dashboard-p (current-buffer)))))
(provide 'test-auto-dim-config)
;;; test-auto-dim-config.el ends here
|