aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-05 04:26:36 -0500
committerCraig Jennings <c@cjennings.net>2026-05-05 04:26:36 -0500
commit0cc022912ad65a67ee89bca11ac035b1018d5ec9 (patch)
tree6467b382e751d504ffd18ec3cc24c5b564d9a3cb
parent0ce03604b5267f301c119ed75e0eba9b5104c6b9 (diff)
downloadorg-drill-0cc022912ad65a67ee89bca11ac035b1018d5ec9.tar.gz
org-drill-0cc022912ad65a67ee89bca11ac035b1018d5ec9.zip
test: queue popping, fontification, ID creation, strip-all-data
12 ERT tests covering: - org-drill-pop-next-pending-entry: empty session → nil, failed prioritized over new/old, again-entries fallback, max-item limit gates primary queues but again-entries bypasses - org-drill-card-tag-caller: dispatches per-tag hook fn from alist, unknown tag is silent no-op (falls through to ignore) - org-drill-id-get-create-with-warning: creates ID and flips warned-about-id-creation flag, doesn't re-warn (uses tempfile- backed buffer because org-id-get requires file-visiting) - org-drill-add-cloze-fontification: sets buffer-local cloze-regexp and cloze-keywords from current delimiters - org-drill-strip-all-data: yes-or-no-p gate (no-confirm = no-op, confirm = wipes scheduling props)
-rw-r--r--tests/test-org-drill-queue-and-misc.el219
1 files changed, 219 insertions, 0 deletions
diff --git a/tests/test-org-drill-queue-and-misc.el b/tests/test-org-drill-queue-and-misc.el
new file mode 100644
index 0000000..51923e9
--- /dev/null
+++ b/tests/test-org-drill-queue-and-misc.el
@@ -0,0 +1,219 @@
+;;; test-org-drill-queue-and-misc.el --- Tests for queue popping, fontification, and miscellaneous helpers -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for several disparate small functions:
+;;
+;; - `org-drill-pop-next-pending-entry': priority-aware queue popper
+;; (failed → overdue → young → new/old → again). Drives which card
+;; is shown next during a session.
+;; - `org-drill-card-tag-caller': dispatches per-card-tag hook functions
+;; from `org-drill-card-tags-alist'.
+;; - `org-drill-id-get-create-with-warning': creates an :ID: on demand
+;; and warns the user once per session.
+;; - `org-drill-add-cloze-fontification': installs buffer-local cloze
+;; regex/font-lock spec.
+;; - `org-drill-strip-all-data': bulk version of strip-entry-data.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+(require 'org)
+(require 'org-id)
+(require 'org-drill)
+
+;;;; Helpers
+
+(defmacro with-fixed-now (&rest body)
+ `(cl-letf (((symbol-function 'current-time)
+ (lambda () (encode-time 0 0 12 5 5 2026))))
+ ,@body))
+
+;;;; org-drill-pop-next-pending-entry
+
+(ert-deftest test-org-drill-pop-next-pending-entry-empty-session-returns-nil ()
+ (with-fixed-now
+ (let ((session (org-drill-session)))
+ (oset session start-time (float-time (current-time)))
+ (should (null (org-drill-pop-next-pending-entry session))))))
+
+(ert-deftest test-org-drill-pop-next-pending-entry-prioritizes-failed-first ()
+ "When failed-entries are present, they're popped before overdue/young/new."
+ (with-fixed-now
+ (with-temp-buffer
+ (insert "* Failed :drill:\n\n* New :drill:\n\n* Old :drill:\n")
+ (org-mode)
+ (let* ((session (org-drill-session))
+ (m-failed (save-excursion (goto-char (point-min))
+ (point-marker)))
+ (m-new (save-excursion (goto-char (point-min))
+ (re-search-forward "^\\* New" nil t)
+ (line-beginning-position)
+ (point-marker)))
+ (m-old (save-excursion (goto-char (point-min))
+ (re-search-forward "^\\* Old" nil t)
+ (line-beginning-position)
+ (point-marker))))
+ (oset session start-time (float-time (current-time)))
+ (oset session failed-entries (list m-failed))
+ (oset session new-entries (list m-new))
+ (oset session old-mature-entries (list m-old))
+ (let ((popped (org-drill-pop-next-pending-entry session)))
+ (should (eq m-failed popped))
+ ;; failed-entries is now empty.
+ (should (null (oref session failed-entries))))))))
+
+(ert-deftest test-org-drill-pop-next-pending-entry-falls-through-to-again ()
+ "When all primary queues are empty but again-entries has items, pops from again."
+ (with-fixed-now
+ (with-temp-buffer
+ (insert "* Again :drill:\n")
+ (org-mode)
+ (let* ((session (org-drill-session))
+ (m (save-excursion (goto-char (point-min)) (point-marker))))
+ (oset session start-time (float-time (current-time)))
+ (oset session again-entries (list m))
+ (let ((popped (org-drill-pop-next-pending-entry session)))
+ (should (eq m popped))
+ (should (null (oref session again-entries))))))))
+
+(ert-deftest test-org-drill-pop-next-pending-entry-respects-max-item-limit ()
+ "Once max-items is reached, primary queues are skipped — only again-entries is reachable."
+ (with-fixed-now
+ (with-temp-buffer
+ (insert "* Done :drill:\n* Done :drill:\n* New :drill:\n* Again :drill:\n")
+ (org-mode)
+ (let* ((session (org-drill-session))
+ (org-drill-maximum-items-per-session 2)
+ (m-done1 (save-excursion (goto-char (point-min)) (point-marker)))
+ (m-done2 (save-excursion (goto-char (point-min))
+ (re-search-forward "^\\* Done" nil t 2)
+ (line-beginning-position)
+ (point-marker)))
+ (m-new (save-excursion (goto-char (point-min))
+ (re-search-forward "^\\* New" nil t)
+ (line-beginning-position)
+ (point-marker)))
+ (m-again (save-excursion (goto-char (point-min))
+ (re-search-forward "^\\* Again" nil t)
+ (line-beginning-position)
+ (point-marker))))
+ (oset session start-time (float-time (current-time)))
+ (oset session done-entries (list m-done1 m-done2)) ; at limit
+ (oset session new-entries (list m-new))
+ (oset session again-entries (list m-again))
+ (let ((popped (org-drill-pop-next-pending-entry session)))
+ ;; new-entries is gated by limit; again-entries isn't → pops m-again.
+ (should (eq m-again popped))
+ ;; new-entries unchanged.
+ (should (member m-new (oref session new-entries))))))))
+
+;;;; org-drill-card-tag-caller
+
+(ert-deftest test-org-drill-card-tag-caller-runs-mapped-function ()
+ "When TAG is in `org-drill-card-tags-alist', the ITEM'th function is called."
+ (let* ((called nil)
+ (org-drill-card-tags-alist
+ `(("mytag" ,(lambda () (setq called 'first))
+ ,(lambda () (setq called 'second))))))
+ (org-drill-card-tag-caller 1 "mytag")
+ (should (eq 'first called))
+ (org-drill-card-tag-caller 2 "mytag")
+ (should (eq 'second called))))
+
+(ert-deftest test-org-drill-card-tag-caller-unknown-tag-no-op ()
+ "Calling with a tag not in the alist falls through to `ignore' silently."
+ (let ((org-drill-card-tags-alist nil))
+ ;; should not error
+ (should (null (org-drill-card-tag-caller 1 "no-such-tag")))))
+
+;;;; org-drill-id-get-create-with-warning
+
+(defmacro with-tempfile-org-buffer (content &rest body)
+ "Run BODY in a buffer visiting a temp file with CONTENT.
+`org-id-get-create' refuses to operate on non-file-visiting buffers,
+so the id-creation tests need a real (if temporary) backing file."
+ (declare (indent 1))
+ `(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 ,content)
+ (goto-char (point-min))
+ ,@body))
+ (when (file-exists-p tmpfile) (delete-file tmpfile)))))
+
+(ert-deftest test-org-drill-id-get-create-with-warning-creates-id-and-flags-session ()
+ "On the first call against an entry without an ID, the session's
+warned-about-id-creation slot flips to t and a fresh ID is returned."
+ (with-tempfile-org-buffer "* Question :drill:\n"
+ (let ((session (org-drill-session)))
+ (cl-letf (((symbol-function 'sit-for) #'ignore))
+ (let ((id (org-drill-id-get-create-with-warning session)))
+ (should (stringp id))
+ (should (oref session warned-about-id-creation)))))))
+
+(ert-deftest test-org-drill-id-get-create-with-warning-doesnt-rewarn ()
+ "Once warned, the session flag stays set and no extra warning fires."
+ (with-tempfile-org-buffer "* Question :drill:\n"
+ (let ((session (org-drill-session)))
+ (oset session warned-about-id-creation t)
+ (cl-letf (((symbol-function 'sit-for) #'ignore))
+ (org-drill-id-get-create-with-warning session)
+ (should (oref session warned-about-id-creation))))))
+
+;;;; org-drill-add-cloze-fontification
+
+(ert-deftest test-org-drill-add-cloze-fontification-sets-buffer-local-regex ()
+ "Sets buffer-local `org-drill-cloze-regexp' built from the current delimiters."
+ (with-temp-buffer
+ (let ((org-startup-folded nil))
+ (org-mode)
+ (let ((org-drill-left-cloze-delimiter "{{")
+ (org-drill-right-cloze-delimiter "}}"))
+ (org-drill-add-cloze-fontification)
+ (should (local-variable-p 'org-drill-cloze-regexp))
+ ;; The buffer-local regex matches the new delimiters.
+ (should (string-match-p org-drill-cloze-regexp "test {{x}} more"))
+ (should-not (string-match-p org-drill-cloze-regexp "test [x] more"))))))
+
+(ert-deftest test-org-drill-add-cloze-fontification-sets-buffer-local-keywords ()
+ (with-temp-buffer
+ (let ((org-startup-folded nil))
+ (org-mode)
+ (org-drill-add-cloze-fontification)
+ (should (local-variable-p 'org-drill-cloze-keywords))
+ (should (listp org-drill-cloze-keywords)))))
+
+;;;; org-drill-strip-all-data
+
+(ert-deftest test-org-drill-strip-all-data-no-confirm-no-action ()
+ "Without yes-or-no-p confirmation, nothing changes — destructive
+actions require explicit consent."
+ (with-temp-buffer
+ (let ((org-startup-folded nil))
+ (insert "* Question :drill:\n")
+ (org-mode)
+ (goto-char (point-min))
+ (org-drill-store-item-data 10 3 1 5 3.8 2.4)
+ (cl-letf (((symbol-function 'yes-or-no-p) (lambda (_) nil)))
+ (org-drill-strip-all-data)
+ ;; Properties survive — user said no.
+ (should (org-entry-get (point) "DRILL_LAST_INTERVAL"))))))
+
+(ert-deftest test-org-drill-strip-all-data-with-confirm-strips ()
+ "With yes-or-no-p approval, every drill scheduling property is removed."
+ (with-temp-buffer
+ (let ((org-startup-folded nil))
+ (insert "* Question :drill:\n")
+ (org-mode)
+ (goto-char (point-min))
+ (org-drill-store-item-data 10 3 1 5 3.8 2.4)
+ (cl-letf (((symbol-function 'yes-or-no-p) (lambda (_) t)))
+ (org-drill-strip-all-data)
+ (dolist (prop org-drill-scheduling-properties)
+ (should (null (org-entry-get (point) prop))))))))
+
+(provide 'test-org-drill-queue-and-misc)
+
+;;; test-org-drill-queue-and-misc.el ends here