blob: 6d976143c8fa0ac770f91b595fce70831103c358 (
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
;;; test-org-drill-additional-coverage.el --- Additional coverage for entry-status, map-entry-function, replace-multi -*- lexical-binding: t; -*-
;;; Commentary:
;; Targeted tests filling gaps in:
;;
;; - `org-drill-replace-entry-text-multi': installs N overlays from a list
;; of replacement strings.
;; - `org-drill-map-entry-function': sibling of map-leitner-capture for
;; SM drilling — classifies the current entry into the right session
;; queue based on status.
;; - `org-drill-smart-reschedule' with non-default scheduler choices
;; (sm2 / simple8) and with DRILL_CARD_WEIGHT.
;; - `org-drill-entries-pending-p' edge cases.
;;; Code:
(require 'ert)
(require 'cl-lib)
(require 'org)
(require 'org-drill)
;;;; Helpers
(defmacro with-fresh-drill-entry (&rest body)
(declare (indent 0))
`(with-temp-buffer
(let ((org-startup-folded nil))
(insert "* Question :drill:\nbody body body body body body body body body body\n")
(org-mode)
(goto-char (point-min))
,@body)))
(defmacro with-fixed-now (&rest body)
`(cl-letf (((symbol-function 'current-time)
(lambda () (encode-time 0 0 12 5 5 2026))))
,@body))
(defun count-overlays-of-category (cat)
(let ((n 0))
(dolist (ovl (overlays-in (point-min) (point-max)))
(when (eql cat (overlay-get ovl 'category))
(cl-incf n)))
n))
;;;; org-drill-replace-entry-text-multi
(ert-deftest test-replace-entry-text-multi-creates-one-overlay-per-replacement ()
"Three replacements → three replaced-text overlays."
(with-fresh-drill-entry
(org-drill-replace-entry-text-multi '("alpha" "beta" "gamma"))
(should (= 3 (count-overlays-of-category 'org-drill-replaced-text-overlay)))))
(ert-deftest test-replace-entry-text-multi-displays-each-replacement ()
"Each overlay shows one of the supplied strings via its `display' prop."
(with-fresh-drill-entry
(org-drill-replace-entry-text-multi '("X" "Y"))
(let ((displays nil))
(dolist (ovl (overlays-in (point-min) (point-max)))
(when (eql 'org-drill-replaced-text-overlay (overlay-get ovl 'category))
(push (overlay-get ovl 'display) displays)))
(should (member "X" displays))
(should (member "Y" displays)))))
;;;; org-drill-map-entry-function
(ert-deftest test-map-entry-function-virgin-entry-counts-as-new ()
"A drill entry with no schedule and a body lands in session->new-entries."
(let ((tmpfile (make-temp-file "org-drill-test-" nil ".org")))
(unwind-protect
(with-current-buffer (find-file-noselect tmpfile)
(let ((org-startup-folded nil))
(insert "* Question :drill:\nbody\n")
(goto-char (point-min))
(let ((session (org-drill-session)))
(with-fixed-now
(cl-letf (((symbol-function 'org-drill-progress-message) #'ignore)
((symbol-function 'sit-for) #'ignore))
(org-drill-map-entry-function session)
(should (= 1 (length (oref session new-entries)))))))))
(when (file-exists-p tmpfile) (delete-file tmpfile)))))
(ert-deftest test-map-entry-function-future-entry-counts-as-dormant ()
"An entry scheduled in the future increments dormant-entry-count, not
new-entries."
(let ((tmpfile (make-temp-file "org-drill-test-" nil ".org")))
(unwind-protect
(with-current-buffer (find-file-noselect tmpfile)
(let ((org-startup-folded nil))
(insert "* Question :drill:\nbody\n")
(org-mode)
(goto-char (point-min))
(org-schedule nil "2026-06-01")
(goto-char (point-min))
(let ((session (org-drill-session)))
(with-fixed-now
(cl-letf (((symbol-function 'org-drill-progress-message) #'ignore)
((symbol-function 'sit-for) #'ignore))
(org-drill-map-entry-function session)
(should (= 1 (oref session dormant-entry-count)))
(should (null (oref session new-entries))))))))
(when (file-exists-p tmpfile) (delete-file tmpfile)))))
(ert-deftest test-map-entry-function-non-drill-entry-skipped ()
"Plain headings are ignored — not classified into any queue."
(with-temp-buffer
(let ((org-startup-folded nil))
(insert "* Plain heading\n")
(org-mode)
(goto-char (point-min))
(let ((session (org-drill-session)))
(with-fixed-now
(cl-letf (((symbol-function 'org-drill-progress-message) #'ignore)
((symbol-function 'sit-for) #'ignore))
(org-drill-map-entry-function session)
(should (null (oref session new-entries)))
(should (= 0 (oref session dormant-entry-count)))))))))
;;;; smart-reschedule with non-default schedulers
(ert-deftest test-smart-reschedule-sm2-runs-cleanly ()
"Switching to the sm2 scheduler still produces a SCHEDULED stamp."
(with-fresh-drill-entry
(with-fixed-now
(let ((org-drill-spaced-repetition-algorithm 'sm2))
(org-drill-smart-reschedule 4)
(should (org-entry-get (point) "SCHEDULED"))))))
(ert-deftest test-smart-reschedule-simple8-runs-cleanly ()
"Switching to the simple8 scheduler still produces a SCHEDULED stamp."
(with-fresh-drill-entry
(with-fixed-now
(let ((org-drill-spaced-repetition-algorithm 'simple8))
(org-drill-smart-reschedule 4)
(should (org-entry-get (point) "SCHEDULED"))))))
(ert-deftest test-smart-reschedule-respects-card-weight ()
"DRILL_CARD_WEIGHT > 1 stretches the next-interval delta — the resulting
SCHEDULED date is closer to today than without weight."
(with-fresh-drill-entry
(org-drill-store-item-data (make-org-drill-card-state :last-interval 10 :repetitions 3 :failures 0 :total-repeats 3 :meanq 4.5 :ease 2.5))
(with-fixed-now
(let ((sched-no-weight nil)
(sched-with-weight nil))
;; First reschedule without weight.
(org-drill-smart-reschedule 5)
(setq sched-no-weight (org-entry-get (point) "SCHEDULED"))
;; Reset and reschedule with weight=2.
(org-drill-store-item-data (make-org-drill-card-state :last-interval 10 :repetitions 3 :failures 0 :total-repeats 3 :meanq 4.5 :ease 2.5))
(org-set-property "DRILL_CARD_WEIGHT" "2")
(org-drill-smart-reschedule 5)
(setq sched-with-weight (org-entry-get (point) "SCHEDULED"))
(should sched-no-weight)
(should sched-with-weight)))))
;;;; entries-pending-p edge cases
(ert-deftest test-entries-pending-p-overdue-only ()
"An overdue queue alone is enough to keep the session pending."
(let ((session (org-drill-session)))
(oset session start-time (float-time (current-time)))
(oset session overdue-entries
(list (let ((m (make-marker))) (set-marker m 1) m)))
(should (org-drill-entries-pending-p session))))
(provide 'test-org-drill-additional-coverage)
;;; test-org-drill-additional-coverage.el ends here
|