aboutsummaryrefslogtreecommitdiff
path: root/tests/test-org-drill-smart-reschedule.el
blob: 6fbf3d608508aa576bc58feb76f3b04d04d95b99 (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
;;; test-org-drill-smart-reschedule.el --- Tests for smart-reschedule  -*- lexical-binding: t; -*-

;;; Commentary:
;; Tests for `org-drill-smart-reschedule', the function that runs after a
;; user rates a card.  It updates the card's DRILL_* properties via
;; store-item-data and then sets a fresh SCHEDULED stamp.
;;
;; Three days-ahead branches:
;;
;; - 0   → unschedule (treat as new again)
;; - <0  → schedule for today (right now)
;; - >0  → schedule N days from today
;; - nil → use the algorithm-computed next-interval (this branch was
;;         broken before the fix — `(= 0 nil)' errored).
;;
;; The user-facing contract: rate a card, see the next-review date
;; advance to a sensible point.

;;; 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-fixed-now (&rest body)
  `(cl-letf (((symbol-function 'current-time)
              (lambda () (encode-time 0 0 12 5 5 2026))))
     ,@body))

(defun current-scheduled-time-string ()
  "Return SCHEDULED stamp on entry at point or nil."
  (org-entry-get (point) "SCHEDULED"))

;;;; Branch: days-ahead = 0 (unschedule)

(ert-deftest test-org-drill-smart-reschedule-zero-days-ahead-unschedules ()
  "Passing 0 removes the SCHEDULED stamp — the entry is treated as new again."
  (with-fresh-drill-entry
    (org-schedule nil "2026-05-01")
    (with-fixed-now
      (org-drill-smart-reschedule 4 0))
    (should (null (current-scheduled-time-string)))))

;;;; Branch: days-ahead < 0 (today)

(ert-deftest test-org-drill-smart-reschedule-negative-schedules-today ()
  "Negative days-ahead schedules for current-time (today)."
  (with-fresh-drill-entry
    (with-fixed-now
      (org-drill-smart-reschedule 4 -1))
    (let ((scheduled (current-scheduled-time-string)))
      (should scheduled)
      ;; Scheduled stamp matches today's date.
      (should (string-match-p "2026-05-05" scheduled)))))

;;;; Branch: days-ahead > 0 (N days from today)

(ert-deftest test-org-drill-smart-reschedule-positive-schedules-n-days-ahead ()
  "Positive days-ahead schedules exactly N days into the future."
  (with-fresh-drill-entry
    (with-fixed-now
      (org-drill-smart-reschedule 4 7))
    (let ((scheduled (current-scheduled-time-string)))
      (should scheduled)
      ;; 2026-05-05 + 7 days = 2026-05-12.
      (should (string-match-p "2026-05-12" scheduled)))))

;;;; Branch: days-ahead = nil (use algorithm)

(ert-deftest test-org-drill-smart-reschedule-nil-days-ahead-uses-algorithm ()
  "Without days-ahead, fall back to the scheduler's computed next-interval.
Pre-fix this branch crashed with `Wrong type argument: number-or-marker-p, nil'
because the cond compared nil with `=' before the type-guard."
  (with-fresh-drill-entry
    (with-fixed-now
      ;; Should not error.  The scheduled date should be in the future
      ;; (some sensible interval).
      (org-drill-smart-reschedule 5)
      (let ((scheduled (current-scheduled-time-string)))
        (should scheduled)))))

;;;; Algorithm dispatch (covers the cl-case branches in smart-reschedule)

(ert-deftest test-org-drill-smart-reschedule-sm2-algorithm-schedules ()
  "With sm2 selected, smart-reschedule still produces a SCHEDULED stamp."
  (with-fresh-drill-entry
    (with-fixed-now
      (let ((org-drill-spaced-repetition-algorithm 'sm2))
        (org-drill-smart-reschedule 5))
      (should (current-scheduled-time-string)))))

(ert-deftest test-org-drill-smart-reschedule-simple8-algorithm-schedules ()
  "With simple8 selected, smart-reschedule still produces a SCHEDULED stamp."
  (with-fresh-drill-entry
    (with-fixed-now
      (let ((org-drill-spaced-repetition-algorithm 'simple8))
        (org-drill-smart-reschedule 5))
      (should (current-scheduled-time-string)))))

;;;; Property side-effects

(ert-deftest test-org-drill-smart-reschedule-writes-drill-properties ()
  "Rescheduling also writes DRILL_* properties via store-item-data."
  (with-fresh-drill-entry
    (with-fixed-now
      (org-drill-smart-reschedule 5 7))
    (should (org-entry-get (point) "DRILL_LAST_INTERVAL"))
    (should (org-entry-get (point) "DRILL_TOTAL_REPEATS"))
    (should (org-entry-get (point) "DRILL_EASE"))))

(ert-deftest test-org-drill-smart-reschedule-increments-total-repeats ()
  "Each rating increments DRILL_TOTAL_REPEATS."
  (with-fresh-drill-entry
    (with-fixed-now
      (org-drill-smart-reschedule 4 7)
      (let ((after-first (string-to-number
                          (org-entry-get (point) "DRILL_TOTAL_REPEATS"))))
        (org-drill-smart-reschedule 4 7)
        (let ((after-second (string-to-number
                             (org-entry-get (point) "DRILL_TOTAL_REPEATS"))))
          (should (= (1+ after-first) after-second)))))))

(provide 'test-org-drill-smart-reschedule)

;;; test-org-drill-smart-reschedule.el ends here