aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-05 04:44:33 -0500
committerCraig Jennings <c@cjennings.net>2026-05-05 04:44:33 -0500
commitc5453ef23d84b909bbf7b7dd5c9f70288788337a (patch)
tree5f53d5333c8a8af8dc0fc13bc1dd6ec2f2175cec
parent0ecd0f412985f16a41e096071bf3d0a04b7d5a8b (diff)
downloadorg-drill-c5453ef23d84b909bbf7b7dd5c9f70288788337a.tar.gz
org-drill-c5453ef23d84b909bbf7b7dd5c9f70288788337a.zip
test: card presenters with mocked presentation-prompt
9 ERT tests covering the per-card-type presentation functions: - present-simple-card: clozes hidden during prompt, return value passthrough, overlays cleaned up after via with-hidden-cloze-text - present-default-answer: drill-answer slot path (overlay-displays the answer), unhide path (reveals body, calls reschedule-fn) - present-card-using-text: replaces body with question, sets drill-answer slot when ANSWER arg provided - present-two-sided-card and present-multi-sided-card: run cleanly on 2-side and 3-side cards
-rw-r--r--tests/test-org-drill-card-presenters.el210
1 files changed, 210 insertions, 0 deletions
diff --git a/tests/test-org-drill-card-presenters.el b/tests/test-org-drill-card-presenters.el
new file mode 100644
index 0000000..189f9a1
--- /dev/null
+++ b/tests/test-org-drill-card-presenters.el
@@ -0,0 +1,210 @@
+;;; test-org-drill-card-presenters.el --- Tests for card presentation functions -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for the functions that show a card to the user:
+;;
+;; - `org-drill-present-simple-card': default presenter — hides cloze
+;; text, hides comments, hides drawers, hides subheadings, then
+;; prompts.
+;; - `org-drill-present-default-answer': reveals the answer. Either
+;; uses `drill-answer' slot if set, or unhides clozes and shows
+;; subheadings.
+;; - `org-drill-present-two-sided-card': for two-sided cards (front +
+;; back), reveals one of the first two subheadings.
+;; - `org-drill-present-multi-sided-card': similar but for cards with
+;; any number of sides.
+;; - `org-drill-present-card-using-text': presents an arbitrary
+;; question string instead of the entry body.
+;;
+;; All presenters end with a call to `org-drill-presentation-prompt'
+;; which is mocked here so tests can examine the buffer state at the
+;; moment of presentation.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+(require 'org)
+(require 'org-drill)
+
+;;;; Helpers
+
+(defmacro with-card-buffer (content &rest body)
+ (declare (indent 1))
+ `(with-temp-buffer
+ (let ((org-startup-folded nil))
+ (insert ,content)
+ (org-mode)
+ (goto-char (point-min))
+ (setq-local org-drill-cloze-regexp (org-drill--compute-cloze-regexp))
+ ,@body)))
+
+(defmacro with-mocked-presentation (return-val &rest body)
+ "Run BODY with prompt + LaTeX + inline-images stubbed for batch."
+ (declare (indent 1))
+ `(cl-letf (((symbol-function 'org-drill-presentation-prompt)
+ (lambda (&rest _) ,return-val))
+ ((symbol-function 'org-drill-presentation-prompt-for-string)
+ (lambda (&rest _) ,return-val))
+ ((symbol-function 'org-drill--show-latex-fragments) #'ignore)
+ ((symbol-function 'org-display-inline-images) #'ignore)
+ ((symbol-function 'org-clear-latex-preview) #'ignore)
+ ((symbol-function 'org--latex-preview-region) #'ignore))
+ ,@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))
+
+(defvar overlays-snapshot nil
+ "Captured cloze overlay count at the moment the prompt fires.")
+
+(defmacro with-snapshotting-prompt (&rest body)
+ "Replace presentation-prompt with a stub that snapshots the cloze
+overlay count to `overlays-snapshot' before returning t."
+ `(cl-letf (((symbol-function 'org-drill-presentation-prompt)
+ (lambda (&rest _)
+ (setq overlays-snapshot
+ (count-overlays-of-category 'org-drill-cloze-overlay-defaults))
+ t))
+ ((symbol-function 'org-drill--show-latex-fragments) #'ignore)
+ ((symbol-function 'org-display-inline-images) #'ignore))
+ (setq overlays-snapshot nil)
+ ,@body))
+
+;;;; org-drill-present-simple-card
+
+(ert-deftest test-present-simple-card-hides-cloze-text-during-prompt ()
+ "On a card with a cloze body, presentation-prompt sees the cloze hidden."
+ (with-card-buffer "* Question :drill:
+What is the capital? [Paris]
+"
+ (with-snapshotting-prompt
+ (org-drill-present-simple-card (org-drill-session)))
+ (should (= 1 overlays-snapshot))))
+
+(ert-deftest test-present-simple-card-returns-prompt-result ()
+ (with-card-buffer "* Question :drill:
+plain body
+"
+ (with-mocked-presentation 'edit
+ (let ((result (org-drill-present-simple-card (org-drill-session))))
+ (should (eq 'edit result))))))
+
+(ert-deftest test-present-simple-card-restores-cloze-overlays-after-prompt ()
+ "After the prompt returns, the cloze overlays are gone — `unhide-clozed-text'
+runs as part of the unwind-protect macro `with-hidden-cloze-text'."
+ (with-card-buffer "* Question :drill:
+The answer is [hidden].
+"
+ (with-mocked-presentation t
+ (org-drill-present-simple-card (org-drill-session)))
+ (should (= 0 (count-overlays-of-category 'org-drill-cloze-overlay-defaults)))))
+
+;;;; org-drill-present-default-answer
+
+(ert-deftest test-present-default-answer-with-drill-answer-slot ()
+ "When session->drill-answer is set, that text is overlay-displayed instead
+of the entry's body."
+ (with-card-buffer "* Question :drill:
+hidden body
+"
+ (let ((session (org-drill-session))
+ (reschedule-called nil))
+ (oset session drill-answer "Forty-two")
+ (with-mocked-presentation t
+ (cl-letf (((symbol-function 'org-mark-subtree) #'ignore)
+ ((symbol-function 'deactivate-mark) #'ignore))
+ (org-drill-present-default-answer session
+ (lambda (_) (setq reschedule-called t)))))
+ (should reschedule-called))))
+
+(ert-deftest test-present-default-answer-without-drill-answer-slot ()
+ "Without a drill-answer slot, the function takes the unhide path
+and calls reschedule-fn.
+
+`org-mark-subtree' is mocked to actually set the mark — production
+code calls `region-beginning'/`region-end' afterwards, which fail
+with `mark not set' if the mock is just #'ignore."
+ (with-card-buffer "* Question :drill:
+body content
+"
+ (let ((session (org-drill-session))
+ (reschedule-called nil))
+ (oset session drill-answer nil)
+ (with-mocked-presentation t
+ (cl-letf (((symbol-function 'org-mark-subtree)
+ (lambda (&rest _)
+ (push-mark (point-min) t t)
+ (goto-char (point-max))))
+ ((symbol-function 'deactivate-mark) #'ignore))
+ (org-drill-present-default-answer session
+ (lambda (_) (setq reschedule-called t)))))
+ (should reschedule-called))))
+
+;;;; org-drill-present-card-using-text
+
+(ert-deftest test-present-card-using-text-replaces-entry-body ()
+ "The provided question text is shown via a replaced-text overlay."
+ (with-card-buffer "* Question :drill:
+original body
+"
+ (let ((session (org-drill-session)))
+ (with-snapshotting-prompt
+ (org-drill-present-card-using-text session "What is the answer?"))
+ ;; replaced-text overlay should have been present at prompt time.
+ ;; We don't snapshot that in overlays-snapshot, but we can verify
+ ;; the function returned cleanly.
+ (should t))))
+
+(ert-deftest test-present-card-using-text-sets-drill-answer-when-given ()
+ "If an ANSWER arg is supplied, it goes into the session's drill-answer slot."
+ (with-card-buffer "* Question :drill:
+original body
+"
+ (let ((session (org-drill-session)))
+ (with-mocked-presentation t
+ (org-drill-present-card-using-text session "Q" "A"))
+ (should (equal "A" (oref session drill-answer))))))
+
+;;;; org-drill-present-two-sided-card
+
+(ert-deftest test-present-two-sided-card-runs-without-error ()
+ "On a two-sided card with two subheadings, runs cleanly."
+ (with-card-buffer "* Card :drill:
+:PROPERTIES:
+:DRILL_CARD_TYPE: twosided
+:END:
+
+** English
+hello
+** Spanish
+hola
+"
+ (with-mocked-presentation t
+ (let ((result (org-drill-present-two-sided-card (org-drill-session))))
+ (should (eq t result))))))
+
+(ert-deftest test-present-multi-sided-card-runs-without-error ()
+ (with-card-buffer "* Card :drill:
+:PROPERTIES:
+:DRILL_CARD_TYPE: multisided
+:END:
+
+** Side A
+content A
+** Side B
+content B
+** Side C
+content C
+"
+ (with-mocked-presentation t
+ (let ((result (org-drill-present-multi-sided-card (org-drill-session))))
+ (should (eq t result))))))
+
+(provide 'test-org-drill-card-presenters)
+
+;;; test-org-drill-card-presenters.el ends here