aboutsummaryrefslogtreecommitdiff
path: root/tests/test-org-drill-leitner-orchestrator.el
blob: e2d3f75306815f1b0327998a7f143965c24265c7 (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
;;; test-org-drill-leitner-orchestrator.el --- Tests for the leitner main entry  -*- lexical-binding: t; -*-

;;; Commentary:
;; Tests for `org-drill-leitner', the public Leitner-learning command.  The
;; orchestrator captures all leitner-tagged entries, fills box 1 if there
;; aren't enough boxed entries, then runs the per-entry loop until the
;; user quits or asks to edit.

;;; Code:

(require 'ert)
(require 'cl-lib)
(require 'org)
(require 'org-drill)

(defmacro with-leitner-orch-tempfile (content &rest body)
  "Run BODY in a tempfile-backed org buffer with CONTENT and clean leitner queues."
  (declare (indent 1))
  `(let ((tmpfile (make-temp-file "org-drill-leitner-orch-" nil ".org"))
         (org-drill-leitner-boxed-entries nil)
         (org-drill-leitner-unboxed-entries nil))
     (unwind-protect
         (with-current-buffer (find-file-noselect tmpfile)
           (let ((org-startup-folded nil))
             (insert ,content)
             (org-mode)
             (goto-char (point-min))
             ,@body))
       (when (file-exists-p tmpfile) (delete-file tmpfile)))))

(ert-deftest test-leitner-finishes-cleanly-when-loop-returns-t-each-step ()
  "When every leitner-entry returns t, the loop completes and the
'Finished Leitner Learning' summary is printed."
  (with-leitner-orch-tempfile
      "* A :leitner:\n:PROPERTIES:\n:DRILL_LEITNER_BOX: 1\n:END:\n* B :leitner:\n:PROPERTIES:\n:DRILL_LEITNER_BOX: 2\n:END:\n"
    (let ((messages nil)
          (org-drill-leitner-completed 0))
      (cl-letf (((symbol-function 'org-drill-leitner-entry)
                 (lambda (&rest _) t))
                ((symbol-function 'message)
                 (lambda (fmt &rest args)
                   (when fmt (push (apply #'format fmt args) messages))))
                ((symbol-function 'sit-for) #'ignore)
                ((symbol-function 'org-drill-progress-message) #'ignore))
        (org-drill-leitner))
      (should (cl-some (lambda (m) (string-match-p "Finished Leitner" m))
                       messages)))))

(ert-deftest test-leitner-quit-short-circuits-the-loop ()
  "When leitner-entry returns (quit ,_), the pcase matches the quit branch
and the function returns t without running further entries."
  (with-leitner-orch-tempfile
      "* A :leitner:\n:PROPERTIES:\n:DRILL_LEITNER_BOX: 1\n:END:\n* B :leitner:\n:PROPERTIES:\n:DRILL_LEITNER_BOX: 2\n:END:\n"
    (let ((calls 0))
      (cl-letf (((symbol-function 'org-drill-leitner-entry)
                 (lambda (&rest _)
                   (cl-incf calls)
                   'quit))
                ((symbol-function 'message) #'ignore)
                ((symbol-function 'sit-for) #'ignore)
                ((symbol-function 'org-drill-progress-message) #'ignore))
        (should (eq t (org-drill-leitner)))
        (should (= 1 calls))))))

(ert-deftest test-leitner-edit-jumps-to-marker ()
  "When leitner-entry returns (edit MARKER), the orchestrator jumps to MARKER."
  (with-leitner-orch-tempfile
      "* A :leitner:\n:PROPERTIES:\n:DRILL_LEITNER_BOX: 1\n:END:\nbody\n"
    (let ((jumped-to nil)
          (entry-marker nil))
      (cl-letf (((symbol-function 'org-drill-leitner-entry)
                 (lambda (&rest _) 'edit))
                ((symbol-function 'org-drill-goto-entry)
                 (lambda (m)
                   (setq jumped-to m)
                   (when (markerp m) (goto-char m))))
                ((symbol-function 'org-reveal) #'ignore)
                ((symbol-function 'org-fold-show-entry) #'ignore)
                ((symbol-function 'message) #'ignore)
                ((symbol-function 'sit-for) #'ignore)
                ((symbol-function 'org-drill-progress-message) #'ignore))
        (setq entry-marker (point-marker))
        (org-drill-leitner)
        ;; goto-entry was called twice — once to navigate to the entry,
        ;; once again on the edit branch.  We just check it was called.
        (should jumped-to)))))

(ert-deftest test-leitner-fills-box-when-boxed-queue-is-too-small ()
  "If fewer boxed entries than maximum-items-per-session, leitner-start-box
runs to fill box 1 from the unboxed queue."
  (with-leitner-orch-tempfile
      "* A :leitner:\nbody\n* B :leitner:\nbody\n"
    (let ((start-box-called nil))
      (cl-letf (((symbol-function 'org-drill-leitner-start-box)
                 (lambda (&rest _) (setq start-box-called t)))
                ((symbol-function 'org-drill-leitner-entry)
                 (lambda (&rest _) t))
                ((symbol-function 'message) #'ignore)
                ((symbol-function 'sit-for) #'ignore)
                ((symbol-function 'org-drill-progress-message) #'ignore))
        (let ((org-drill-maximum-items-per-session 5))
          (org-drill-leitner)))
      (should start-box-called))))

(provide 'test-org-drill-leitner-orchestrator)
;;; test-org-drill-leitner-orchestrator.el ends here