aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-05 04:53:10 -0500
committerCraig Jennings <c@cjennings.net>2026-05-05 04:53:10 -0500
commit8cea3e2a928d6b41929c13c44343b9ebb7fd1aac (patch)
tree55348ffa0b48c7da4410d2f037729c6a59b74604
parenta68f995fa1dfbb891611a982587dc3f07f467451 (diff)
downloadorg-drill-8cea3e2a928d6b41929c13c44343b9ebb7fd1aac.tar.gz
org-drill-8cea3e2a928d6b41929c13c44343b9ebb7fd1aac.zip
test: final-report message format and warning-branch zero-guard
6 ERT tests covering org-drill-final-report: - Reviewed-count from done-entries appears in the message - Pending-queue line lists per-queue counts (1 new, 2 young, etc.) - 100% pass rate doesn't trigger the WARNING branch - Below forgetting-index pass rate triggers the warning prompt - Per-quality counts produce correct percentages (1/4 = 25%) - Warning-branch with zero dormant+due survives (locks in the zero-divisor guard fix)
-rw-r--r--tests/test-org-drill-final-report.el162
1 files changed, 162 insertions, 0 deletions
diff --git a/tests/test-org-drill-final-report.el b/tests/test-org-drill-final-report.el
new file mode 100644
index 0000000..9ded9d4
--- /dev/null
+++ b/tests/test-org-drill-final-report.el
@@ -0,0 +1,162 @@
+;;; test-org-drill-final-report.el --- Tests for the final-report message -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for `org-drill-final-report' — the wrap-up summary shown at the
+;; end of every drill session. The function builds a multiline message
+;; with reviewed-count, duration, per-quality recall percentages, and
+;; remaining-queue counts, then waits for a key.
+;;
+;; Tests mock `input-pending-p' / `read-char-exclusive' / `message' to
+;; bypass the wait loop and capture the formatted message.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+(require 'org)
+(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))
+
+(defmacro with-mocked-final-report-loop (&rest body)
+ "Run BODY with the final-report's wait loop short-circuited after one
+iteration so `message' actually fires. Captures the last-shown prompt
+to `final-report-message'."
+ `(let ((final-report-message nil)
+ (loop-iters 0))
+ (cl-letf (((symbol-function 'input-pending-p)
+ (lambda ()
+ (cl-incf loop-iters)
+ (> loop-iters 1)))
+ ((symbol-function 'read-char-exclusive) (lambda (&optional _) ?x))
+ ((symbol-function 'sit-for) #'ignore)
+ ((symbol-function 'message)
+ (lambda (fmt &rest args)
+ (setq final-report-message (apply #'format fmt args)))))
+ ,@body
+ final-report-message)))
+
+(defun make-marker-at-1 ()
+ (let ((m (make-marker))) (set-marker m 1) m))
+
+;;;; Format / content
+
+(ert-deftest test-final-report-includes-reviewed-count ()
+ "The N-items-reviewed count from done-entries appears."
+ (with-temp-buffer
+ (let ((session (org-drill-session)))
+ (oset session done-entries
+ (list (make-marker-at-1) (make-marker-at-1) (make-marker-at-1)))
+ (oset session start-time (float-time (encode-time 0 0 12 5 5 2026)))
+ (with-fixed-now
+ (let ((msg (with-mocked-final-report-loop
+ (org-drill-final-report session))))
+ (should (string-match-p "3 items reviewed" msg)))))))
+
+(ert-deftest test-final-report-includes-pending-counts ()
+ "The remaining-queue line lists failed / overdue / new / young / old."
+ (with-temp-buffer
+ (let ((session (org-drill-session)))
+ (oset session new-entries (list (make-marker-at-1)))
+ (oset session young-mature-entries
+ (list (make-marker-at-1) (make-marker-at-1)))
+ (oset session start-time (float-time (encode-time 0 0 12 5 5 2026)))
+ (with-fixed-now
+ (let ((msg (with-mocked-final-report-loop
+ (org-drill-final-report session))))
+ (should (string-match-p "1 new" msg))
+ (should (string-match-p "2 young" msg)))))))
+
+(ert-deftest test-final-report-100-percent-pass-skips-warning ()
+ "Perfect recall doesn't trigger the second `WARNING!' message."
+ (with-temp-buffer
+ (let ((session (org-drill-session))
+ (warning-shown nil))
+ (oset session qualities '(5 5 5 5))
+ (oset session start-time (float-time (encode-time 0 0 12 5 5 2026)))
+ (with-fixed-now
+ (cl-letf (((symbol-function 'input-pending-p) (lambda () t))
+ ((symbol-function 'sit-for) #'ignore)
+ ((symbol-function 'read-char-exclusive)
+ (lambda (&optional msg)
+ (when (and msg (string-match-p "WARNING" msg))
+ (setq warning-shown t))
+ ?x))
+ ((symbol-function 'message) (lambda (&rest _))))
+ (org-drill-final-report session)
+ (should-not warning-shown))))))
+
+(ert-deftest test-final-report-low-pass-percent-shows-warning ()
+ "Pass rate below the forgetting-index complement triggers the warning prompt."
+ (with-temp-buffer
+ (let ((session (org-drill-session))
+ (warning-shown nil))
+ (oset session qualities '(0 0 0 0 0)) ; 0% pass
+ (oset session dormant-entry-count 5)
+ (oset session due-entry-count 5)
+ (oset session overdue-entry-count 2)
+ (oset session start-time (float-time (encode-time 0 0 12 5 5 2026)))
+ (with-fixed-now
+ (cl-letf (((symbol-function 'input-pending-p) (lambda () t))
+ ((symbol-function 'sit-for) #'ignore)
+ ((symbol-function 'read-char-exclusive)
+ (lambda (&optional msg)
+ (when (and msg (string-match-p "WARNING" msg))
+ (setq warning-shown t))
+ ?x))
+ ((symbol-function 'message) (lambda (&rest _))))
+ (org-drill-final-report session)
+ (should warning-shown))))))
+
+(ert-deftest test-final-report-quality-counts-percentages ()
+ "Per-quality counts add up: 1 of 4 quality-5 ratings → 25%.
+Uses all passing ratings to avoid the warning branch's divide-by-zero
+on empty dormant/due counts."
+ (with-temp-buffer
+ (let ((session (org-drill-session)))
+ (oset session qualities '(5 4 4 4)) ; 100% pass, no warning fires
+ (oset session start-time (float-time (encode-time 0 0 12 5 5 2026)))
+ (with-fixed-now
+ (let ((msg (with-mocked-final-report-loop
+ (org-drill-final-report session))))
+ ;; 1 of 4 = 25% Excellent
+ (should (string-match-p "Excellent (5):.*25%" msg))
+ ;; 3 of 4 = 75% Good
+ (should (string-match-p "Good (4):.*75%" msg)))))))
+
+(ert-deftest test-final-report-warning-branch-with-zero-dormant-and-due-survives ()
+ "The warning branch divides by (dormant + due), which is 0 in some
+edge cases (cram mode with zero items collected, or pure-failure
+sessions on empty queues).
+
+Pre-fix this hit `arith-error'. The fix wraps the divisor with
+`(max 1 ...)' so the percentage gracefully reports 0%."
+ (with-temp-buffer
+ (let ((session (org-drill-session))
+ (warning-shown nil))
+ (oset session qualities '(0 0 0)) ; 0% pass
+ (oset session dormant-entry-count 0) ; both zero — the bug shape
+ (oset session due-entry-count 0)
+ (oset session overdue-entry-count 0)
+ (oset session start-time (float-time (encode-time 0 0 12 5 5 2026)))
+ (with-fixed-now
+ (cl-letf (((symbol-function 'input-pending-p) (lambda () t))
+ ((symbol-function 'sit-for) #'ignore)
+ ((symbol-function 'read-char-exclusive)
+ (lambda (&optional msg)
+ (when (and msg (string-match-p "WARNING" msg))
+ (setq warning-shown t))
+ ?x))
+ ((symbol-function 'message) (lambda (&rest _))))
+ ;; Pre-fix this errored out before reaching the assertion.
+ (org-drill-final-report session)
+ (should warning-shown))))))
+
+(provide 'test-org-drill-final-report)
+
+;;; test-org-drill-final-report.el ends here