aboutsummaryrefslogtreecommitdiff
path: root/tests/test-org-drill-multicloze-dispatch.el
blob: 99861c7a91d7c5a0a8b4fe707c641321aa31f4a6 (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
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
;;; test-org-drill-multicloze-dispatch.el --- Tests for multicloze hide-firstmore / show1-lastmore / show1-firstless dispatch  -*- lexical-binding: t; -*-

;;; Commentary:
;; The multicloze "weighted" presenters — hide1-firstmore,
;; show1-lastmore, show1-firstless — wrap a cond that picks between
;; "common" and "uncommon" modes based on `org-drill-cloze-text-weight'
;; and the entry's repeat counter.
;;
;; The presenters they delegate to are interactive (they call
;; `org-drill-presentation-prompt'), but the branch-selection logic
;; above the delegation is a pure cond that's testable in isolation by
;; mocking the underlying presenter functions to record which one was
;; chosen.
;;
;; The user-facing contract: with weight = N, every Nth repetition
;; uses the "uncommon" path, the rest use the "common" path.  When
;; weight is nil, the entire weighting is bypassed.

;;; 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:\n")
       (org-mode)
       (goto-char (point-min))
       ,@body)))

(defmacro with-mocked-presenters (&rest body)
  "Replace the multicloze presenters with no-op stubs that record which
function was called by pushing onto `multicloze-calls'."
  `(let ((multicloze-calls nil))
     (cl-letf (((symbol-function 'org-drill-present-multicloze-hide1)
                (lambda (_session) (push 'hide1 multicloze-calls) t))
               ((symbol-function 'org-drill-present-multicloze-hide-first)
                (lambda (_session) (push 'hide-first multicloze-calls) t))
               ((symbol-function 'org-drill-present-multicloze-hide-n)
                (lambda (&rest args) (push (cons 'hide-n args) multicloze-calls) t))
               ((symbol-function 'org-drill-present-multicloze-show1)
                (lambda (_session) (push 'show1 multicloze-calls) t)))
       ,@body)))

;;;; hide1-firstmore

(ert-deftest test-multicloze-hide1-firstmore-nil-weight-falls-back-to-hide1 ()
  "When `org-drill-cloze-text-weight' is nil, behave like plain hide1cloze."
  (with-fresh-drill-entry
    (let ((org-drill-cloze-text-weight nil))
      (with-mocked-presenters
        (org-drill-present-multicloze-hide1-firstmore (org-drill-session))
        (should (memq 'hide1 multicloze-calls))))))

(ert-deftest test-multicloze-hide1-firstmore-invalid-weight-errors ()
  "A non-positive-integer weight is rejected with a user-visible error."
  (with-fresh-drill-entry
    (let ((org-drill-cloze-text-weight -1))
      (should-error (org-drill-present-multicloze-hide1-firstmore (org-drill-session))))
    (let ((org-drill-cloze-text-weight 'not-a-number))
      (should-error (org-drill-present-multicloze-hide1-firstmore (org-drill-session))))))

(ert-deftest test-multicloze-hide1-firstmore-non-trigger-rep-uses-hide-first ()
  "When (1+ total-repeats) is NOT divisible by weight, take the common path
(hide-first).  weight=3, total-repeats=0 → 1 mod 3 = 1 → common."
  (with-fresh-drill-entry
    (org-set-property "DRILL_TOTAL_REPEATS" "0")
    (let ((org-drill-cloze-text-weight 3))
      (with-mocked-presenters
        (org-drill-present-multicloze-hide1-firstmore (org-drill-session))
        (should (memq 'hide-first multicloze-calls))))))

(ert-deftest test-multicloze-hide1-firstmore-trigger-rep-uses-hide-n ()
  "When (1+ total-repeats) IS divisible by weight, take the uncommon
path (hide-n with force-show-first).  weight=3, total-repeats=2 → 3 mod 3 = 0."
  (with-fresh-drill-entry
    (org-set-property "DRILL_TOTAL_REPEATS" "2")
    (let ((org-drill-cloze-text-weight 3))
      (with-mocked-presenters
        (org-drill-present-multicloze-hide1-firstmore (org-drill-session))
        (let ((call (cl-find-if (lambda (c) (and (consp c) (eq 'hide-n (car c))))
                                multicloze-calls)))
          (should call)
          ;; hide-n was called with force-show-first = t (4th arg, after session, n=1).
          (let* ((args (cdr call))
                 ;; args = (session 1 force-show-first)
                 (force-show-first (nth 2 args)))
            (should (eq t force-show-first))))))))

;;;; show1-lastmore

(ert-deftest test-multicloze-show1-lastmore-nil-weight-falls-back-to-show1 ()
  (with-fresh-drill-entry
    (let ((org-drill-cloze-text-weight nil))
      (with-mocked-presenters
        (org-drill-present-multicloze-show1-lastmore (org-drill-session))
        (should (memq 'show1 multicloze-calls))))))

(ert-deftest test-multicloze-show1-lastmore-non-trigger-shows-last ()
  "Common path: hide-n with -1 (show one) and force-show-last = t."
  (with-fresh-drill-entry
    (org-set-property "DRILL_TOTAL_REPEATS" "0")  ; (1+0) mod 3 = 1 → common
    (let ((org-drill-cloze-text-weight 3))
      (with-mocked-presenters
        (org-drill-present-multicloze-show1-lastmore (org-drill-session))
        (let* ((call (cl-find-if (lambda (c) (and (consp c) (eq 'hide-n (car c))))
                                 multicloze-calls))
               ;; args after `hide-n: (session -1 force-show-first force-show-last)
               (args (cdr call)))
          (should (eq -1 (nth 1 args)))
          (should (eq t (nth 3 args))))))))   ; force-show-last

;;;; show1-firstless

(ert-deftest test-multicloze-show1-firstless-nil-weight-falls-back-to-show1 ()
  (with-fresh-drill-entry
    (let ((org-drill-cloze-text-weight nil))
      (with-mocked-presenters
        (org-drill-present-multicloze-show1-firstless (org-drill-session))
        (should (memq 'show1 multicloze-calls))))))

(ert-deftest test-multicloze-show1-firstless-non-trigger-skips-first ()
  "Common path: hide-n with -1 and force-show-first omitted (the show
piece is guaranteed not to be the first)."
  (with-fresh-drill-entry
    (org-set-property "DRILL_TOTAL_REPEATS" "0")
    (let ((org-drill-cloze-text-weight 3))
      (with-mocked-presenters
        (org-drill-present-multicloze-show1-firstless (org-drill-session))
        (let* ((call (cl-find-if (lambda (c) (and (consp c) (eq 'hide-n (car c))))
                                 multicloze-calls))
               (args (cdr call)))
          (should (eq -1 (nth 1 args))))))))

;;;; Basic variants — delegation contract
;;
;; hide1/hide2/hide-first/hide-last are thin wrappers.  The hiding
;; mechanics they delegate to (hide-n / hide-nth) are exercised directly
;; in test-org-drill-multicloze-hiding.el, so re-driving present-and-reveal
;; here would just re-test those.  What's untested is the wiring: which
;; delegate each variant calls and with what argument.

(ert-deftest test-multicloze-hide1-delegates-to-hide-n-1 ()
  "hide1 hides one piece: delegates to hide-n with number-to-hide = 1."
  (with-fresh-drill-entry
    (let (recorded)
      (cl-letf (((symbol-function 'org-drill-present-multicloze-hide-n)
                 (lambda (_session n &rest _) (setq recorded n) t)))
        (org-drill-present-multicloze-hide1 (org-drill-session))
        (should (eql 1 recorded))))))

(ert-deftest test-multicloze-hide2-delegates-to-hide-n-2 ()
  "hide2 hides two pieces: delegates to hide-n with number-to-hide = 2."
  (with-fresh-drill-entry
    (let (recorded)
      (cl-letf (((symbol-function 'org-drill-present-multicloze-hide-n)
                 (lambda (_session n &rest _) (setq recorded n) t)))
        (org-drill-present-multicloze-hide2 (org-drill-session))
        (should (eql 2 recorded))))))

(ert-deftest test-multicloze-hide-first-delegates-to-hide-nth-1 ()
  "hide-first hides the first piece: delegates to hide-nth with 1."
  (with-fresh-drill-entry
    (let (recorded)
      (cl-letf (((symbol-function 'org-drill-present-multicloze-hide-nth)
                 (lambda (_session nth &rest _) (setq recorded nth) t)))
        (org-drill-present-multicloze-hide-first (org-drill-session))
        (should (eql 1 recorded))))))

(ert-deftest test-multicloze-hide-last-delegates-to-hide-nth-last ()
  "hide-last hides the last piece: delegates to hide-nth with -1."
  (with-fresh-drill-entry
    (let (recorded)
      (cl-letf (((symbol-function 'org-drill-present-multicloze-hide-nth)
                 (lambda (_session nth &rest _) (setq recorded nth) t)))
        (org-drill-present-multicloze-hide-last (org-drill-session))
        (should (eql -1 recorded))))))

(ert-deftest test-multicloze-show1-delegates-to-hide-n-minus-1 ()
  "show1 reveals one piece (hides the rest): delegates to hide-n with -1."
  (with-fresh-drill-entry
    (let (recorded)
      (cl-letf (((symbol-function 'org-drill-present-multicloze-hide-n)
                 (lambda (_session n &rest _) (setq recorded n) t)))
        (org-drill-present-multicloze-show1 (org-drill-session))
        (should (eql -1 recorded))))))

(ert-deftest test-multicloze-show2-delegates-to-hide-n-minus-2 ()
  "show2 reveals two pieces: delegates to hide-n with -2."
  (with-fresh-drill-entry
    (let (recorded)
      (cl-letf (((symbol-function 'org-drill-present-multicloze-hide-n)
                 (lambda (_session n &rest _) (setq recorded n) t)))
        (org-drill-present-multicloze-show2 (org-drill-session))
        (should (eql -2 recorded))))))

(provide 'test-org-drill-multicloze-dispatch)

;;; test-org-drill-multicloze-dispatch.el ends here