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
|
;;; test-org-drill-mode.el --- Tests for org-drill-mode minor mode -*- lexical-binding: t; -*-
;;; Commentary:
;; `org-drill-mode' scopes cloze fontification to buffers that hold drill
;; cards instead of installing it globally via `org-font-lock-set-keywords-hook'.
;;
;; Regression target: the global hook caused org priority cookies ([#A]..[#D])
;; to match the cloze `[...]' pattern in EVERY org buffer, colliding with org's
;; headline fontification and stripping the heading's `org-level-N' face. With
;; the mode, plain org buffers carry no cloze rule at all, so the collision
;; cannot happen there.
;;; Code:
(require 'ert)
(require 'org-drill)
;;;; Helpers
(defun tdm--face-prop-has (face prop)
"Return non-nil if FACE is, or is a member of, the face PROP."
(or (eq prop face)
(and (listp prop) (memq face prop))))
(defun tdm--face-at-string (s)
"Fontify the buffer and return the `face' text property at the first
character of the first occurrence of literal string S."
(font-lock-ensure)
(goto-char (point-min))
(when (search-forward s nil t)
(get-text-property (match-beginning 0) 'face)))
;;;; Minor mode definition
(ert-deftest test-org-drill-mode-is-buffer-local-minor-mode ()
"`org-drill-mode' exists and is buffer-local."
(should (fboundp 'org-drill-mode))
(should (local-variable-if-set-p 'org-drill-mode)))
(ert-deftest test-org-drill-mode-toggle-adds-and-removes-cloze-keyword ()
"Enabling the mode highlights a cloze; disabling stops highlighting it."
(let ((org-drill-use-visible-cloze-face-p t))
(with-temp-buffer
(insert "Question [answer] tail\n")
(org-mode)
(org-drill-mode 1)
(should org-drill-mode)
(should (tdm--face-prop-has 'org-drill-visible-cloze-face
(tdm--face-at-string "answer")))
(org-drill-mode -1)
(should-not org-drill-mode)
(should-not (tdm--face-prop-has 'org-drill-visible-cloze-face
(tdm--face-at-string "answer"))))))
;;;; Drill-buffer predicate
(ert-deftest test-org-drill-buffer-has-cards-p-true-for-drill-tag ()
"A heading tagged with the question tag counts as a drill buffer."
(with-temp-buffer
(insert "* A card :drill:\nbody\n")
(org-mode)
(should (org-drill-buffer-has-cards-p))))
(ert-deftest test-org-drill-buffer-has-cards-p-true-for-leitner-tag ()
"A heading tagged with the leitner tag counts as a drill buffer."
(with-temp-buffer
(insert "* A card :leitner:\nbody\n")
(org-mode)
(should (org-drill-buffer-has-cards-p))))
(ert-deftest test-org-drill-buffer-has-cards-p-false-for-plain-org ()
"An org buffer with no drill/leitner tag is not a drill buffer."
(with-temp-buffer
(insert "* Just notes :work:\n** TODO [#A] A heading\nbody [bracketed] text\n")
(org-mode)
(should-not (org-drill-buffer-has-cards-p))))
;;;; Drill-buffer predicate — file-level tags (#+FILETAGS), upstream bug
(ert-deftest test-org-drill-buffer-has-cards-p-true-for-filetags-space ()
"A deck that tags its cards via `#+FILETAGS: drill' (space syntax, no
per-heading tag) counts as a drill buffer."
(with-temp-buffer
(insert "#+FILETAGS: drill\n* A card\nQ [answer] A\n")
(org-mode)
(should (org-drill-buffer-has-cards-p))))
(ert-deftest test-org-drill-buffer-has-cards-p-true-for-filetags-colon ()
"A deck tagged via the colon syntax `#+FILETAGS: :spanish:drill:verbs:'
counts as a drill buffer even when the tag sits among others."
(with-temp-buffer
(insert "#+FILETAGS: :spanish:drill:verbs:\n* A card\nQ [answer] A\n")
(org-mode)
(should (org-drill-buffer-has-cards-p))))
(ert-deftest test-org-drill-buffer-has-cards-p-true-for-filetags-leitner ()
"A deck tagged via `#+FILETAGS: leitner' counts as a drill buffer."
(with-temp-buffer
(insert "#+FILETAGS: leitner\n* A card\nQ [answer] A\n")
(org-mode)
(should (org-drill-buffer-has-cards-p))))
(ert-deftest test-org-drill-buffer-has-cards-p-true-for-inherited-top-level-tag ()
"A deck whose cards inherit the tag from a tagged top-level heading counts
as a drill buffer. The ancestor heading line carries the literal tag, so the
per-heading scan already matches it — this locks that in."
(with-temp-buffer
(insert "* Deck :drill:\n** Card 1\nQ [answer] A\n** Card 2\nQ2 [answer2] A2\n")
(org-mode)
(should (org-drill-buffer-has-cards-p))))
(ert-deftest test-org-drill-buffer-has-cards-p-false-for-filetags-substring ()
"A `#+FILETAGS:' value that merely contains the tag as a substring
\(e.g. `drilldown') must NOT count as a drill buffer."
(with-temp-buffer
(insert "#+FILETAGS: drilldown\n* Notes\nbody\n")
(org-mode)
(should-not (org-drill-buffer-has-cards-p))))
;;;; Auto-enable on org-mode-hook
(ert-deftest test-org-drill-mode-auto-enables-in-drill-buffer ()
"With auto-enable on, opening a drill buffer turns the mode on."
(let ((org-drill-auto-enable-mode t))
(with-temp-buffer
(insert "* A card :drill:\nQ [answer] A\n")
(org-mode)
(should org-drill-mode))))
(ert-deftest test-org-drill-mode-auto-enables-in-filetags-buffer ()
"With auto-enable on, opening a deck tagged only via `#+FILETAGS: drill'
turns the mode on. This is the user-facing bug: filetag-only decks were
left without cloze highlighting."
(let ((org-drill-auto-enable-mode t))
(with-temp-buffer
(insert "#+FILETAGS: drill\n* A card\nQ [answer] A\n")
(org-mode)
(should org-drill-mode))))
(ert-deftest test-org-drill-mode-does-not-auto-enable-in-plain-buffer ()
"With auto-enable on, a plain org buffer does NOT turn the mode on."
(let ((org-drill-auto-enable-mode t))
(with-temp-buffer
(insert "* Notes\n** TODO [#A] With cookie\n")
(org-mode)
(should-not org-drill-mode))))
(ert-deftest test-org-drill-mode-auto-enable-respects-defcustom-off ()
"When `org-drill-auto-enable-mode' is nil, drill buffers do not auto-enable."
(let ((org-drill-auto-enable-mode nil))
(with-temp-buffer
(insert "* A card :drill:\nQ [answer] A\n")
(org-mode)
(should-not org-drill-mode))))
(ert-deftest test-org-drill-auto-enable-mode-defaults-on ()
"The auto-enable defcustom ships on so existing drill files keep highlighting."
(should (eq t (default-value 'org-drill-auto-enable-mode))))
;;;; Regression — the bug
(ert-deftest test-org-drill-no-global-font-lock-keywords-hook ()
"Cloze fontification is no longer installed globally on
`org-font-lock-set-keywords-hook' — that was the every-buffer pollution that
made priority cookies collide with the cloze pattern."
(should-not (memq 'org-drill-add-cloze-fontification
(if (boundp 'org-font-lock-set-keywords-hook)
org-font-lock-set-keywords-hook
nil))))
(ert-deftest test-org-drill-priority-cookie-not-clozed-in-plain-buffer ()
"In a plain org buffer (no drill cards) a priority cookie must not be
fontified as a cloze, regardless of `org-drill-use-visible-cloze-face-p'."
(let ((org-drill-use-visible-cloze-face-p t)
(org-drill-auto-enable-mode t))
(with-temp-buffer
(insert "* Notes\n** TODO [#A] With cookie\n")
(org-mode)
(should-not org-drill-mode)
(goto-char (point-min))
(search-forward "#A")
(should-not (tdm--face-prop-has
'org-drill-visible-cloze-face
(get-text-property (match-beginning 0) 'face))))))
(provide 'test-org-drill-mode)
;;; test-org-drill-mode.el ends here
|