diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-31 08:35:16 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-31 08:35:16 -0500 |
| commit | 26cc4472dea261a1ad13fbee8fb6a91b019f77bb (patch) | |
| tree | a7063337b05c3ea278a5b910d0f1420de033dfe8 /tests/test-org-drill-statistics-primitives.el | |
| parent | 532ce532465834ce06238648ba1490c48bed29ca (diff) | |
| download | org-drill-26cc4472dea261a1ad13fbee8fb6a91b019f77bb.tar.gz org-drill-26cc4472dea261a1ad13fbee8fb6a91b019f77bb.zip | |
feat: add the org-drill statistics dashboard renderer
Step 1 shipped the session-log data layer. This is the renderer on top of it.
org-drill-statistics opens a read-only dashboard with five sections: an overview (card counts plus a last-session recap), trends (reviews-per-day and pass-rate-per-day quadrant-block sparklines over the trend window, plus a 12-week table), a quality histogram, a needs-attention view (leech candidates, long-overdue, and forgotten-new cards), and a 7-day forecast counted from SCHEDULED dates. A buffer-wide filter (scope, range, algorithm) sits in the header and cycles with s/r/a. The other keys are q to bury, g to refresh, e for the CSV-export hook that lands next, and RET to follow the card link at point.
The aggregation math lives in pure helpers (day-bucketing, sparkline scaling, weekly aggregates, the histogram, the attention selectors, forecast bucketing). The render helpers are thin string formatters over them, so the logic is unit-tested independently of the UI. New defcustoms tune the views: org-drill-statistics-trend-days, -forecast-days, -attention-row-limit, and -leech-quality-threshold.
I added require 'calendar for the Monday week-start arithmetic in the weekly aggregates. CSV export and the manual and README entries are the step-3 follow-on.
Diffstat (limited to 'tests/test-org-drill-statistics-primitives.el')
| -rw-r--r-- | tests/test-org-drill-statistics-primitives.el | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/tests/test-org-drill-statistics-primitives.el b/tests/test-org-drill-statistics-primitives.el new file mode 100644 index 0000000..2022e0d --- /dev/null +++ b/tests/test-org-drill-statistics-primitives.el @@ -0,0 +1,139 @@ +;;; test-org-drill-statistics-primitives.el --- Tests for primitives statistics -*- lexical-binding: t; -*- + +;;; Commentary: +;; ERT tests for the org-drill statistics dashboard primitives block. + +;;; Code: + +(require 'ert) +(require 'org-drill) +(require 'cl-lib) +(require 'org) + +;;; Tests for statistics primitives. Require 'org-drill and 'cl-lib. +;;; Fixtures are deterministic: start-time values are fixed floats, and +;;; day numbers are derived, never hardcoded against today's date. + + +(defun test-org-drill-statistics--make-record (start-time algorithm) + "Build a minimal `org-drill-session-record' for tests. +START-TIME is a float; ALGORITHM is a symbol. Other slots get inert +placeholder values sufficient for the primitive under test." + (make-org-drill-session-record + :start-time start-time + :end-time (+ start-time 60.0) + :scope 'directory + :algorithm algorithm + :qualities (vector 5 4 3) + :pass-percent 67 + :new-count 1 + :mature-count 1 + :failed-count 1 + :cram-mode nil)) + +;;; org-drill-statistics--today-day + +(ert-deftest test-org-drill-statistics-today-day-matches-time-to-days () + "`--today-day' equals `time-to-days' of the current time." + (should (= (org-drill-statistics--today-day) + (time-to-days (current-time))))) + +(ert-deftest test-org-drill-statistics-today-day-redefinable () + "`--today-day' can be redefined to a fixed day for deterministic tests." + (cl-letf (((symbol-function 'org-drill-statistics--today-day) + (lambda () 700000))) + (should (= (org-drill-statistics--today-day) 700000)))) + +;;; org-drill-statistics--record-day + +(ert-deftest test-org-drill-statistics-record-day-derives-from-start-time () + "`--record-day' returns the day number of the record's start time." + (let* ((now (float-time)) + (record (test-org-drill-statistics--make-record now 'sm5))) + (should (= (org-drill-statistics--record-day record) + (time-to-days (seconds-to-time now)))))) + +(ert-deftest test-org-drill-statistics-record-day-earlier-is-smaller () + "A record started a day earlier has a day number one less." + (let* ((now (float-time)) + (today (test-org-drill-statistics--make-record now 'sm5)) + (yesterday (test-org-drill-statistics--make-record + (- now 86400.0) 'sm5))) + (should (= (- (org-drill-statistics--record-day today) + (org-drill-statistics--record-day yesterday)) + 1)))) + +;;; org-drill-statistics--filter-log + +(ert-deftest test-org-drill-statistics-filter-log-nil-returns-all () + "Filtering with nil algorithm returns the log unchanged." + (let ((log (list (test-org-drill-statistics--make-record 100.0 'sm5) + (test-org-drill-statistics--make-record 200.0 'simple8)))) + (should (equal (org-drill-statistics--filter-log log nil) log)))) + +(ert-deftest test-org-drill-statistics-filter-log-keeps-matching () + "Filtering keeps only records whose algorithm matches." + (let* ((a (test-org-drill-statistics--make-record 100.0 'sm5)) + (b (test-org-drill-statistics--make-record 200.0 'simple8)) + (c (test-org-drill-statistics--make-record 300.0 'sm5)) + (log (list a b c)) + (result (org-drill-statistics--filter-log log 'sm5))) + (should (equal result (list a c))))) + +(ert-deftest test-org-drill-statistics-filter-log-no-match-returns-empty () + "Filtering on an absent algorithm returns an empty list." + (let ((log (list (test-org-drill-statistics--make-record 100.0 'sm5)))) + (should (null (org-drill-statistics--filter-log log 'simple8))))) + +(ert-deftest test-org-drill-statistics-filter-log-empty-log () + "Filtering an empty log returns an empty list for any algorithm." + (should (null (org-drill-statistics--filter-log nil 'sm5))) + (should (null (org-drill-statistics--filter-log nil nil)))) + +(ert-deftest test-org-drill-statistics-filter-log-does-not-mutate () + "Filtering leaves the input list intact." + (let* ((log (list (test-org-drill-statistics--make-record 100.0 'sm5) + (test-org-drill-statistics--make-record 200.0 'simple8))) + (copy (copy-sequence log))) + (org-drill-statistics--filter-log log 'sm5) + (should (equal log copy)))) + +;;; org-drill-statistics--log-since + +(ert-deftest test-org-drill-statistics-log-since-keeps-at-or-after-cutoff () + "Records at or after the cutoff are kept; earlier ones dropped." + (let* ((before (test-org-drill-statistics--make-record 100.0 'sm5)) + (at (test-org-drill-statistics--make-record 200.0 'sm5)) + (after (test-org-drill-statistics--make-record 300.0 'sm5)) + (log (list before at after)) + (result (org-drill-statistics--log-since log 200.0))) + (should (equal result (list at after))))) + +(ert-deftest test-org-drill-statistics-log-since-all-before-cutoff () + "When every record predates the cutoff, the result is empty." + (let ((log (list (test-org-drill-statistics--make-record 100.0 'sm5) + (test-org-drill-statistics--make-record 150.0 'sm5)))) + (should (null (org-drill-statistics--log-since log 200.0))))) + +(ert-deftest test-org-drill-statistics-log-since-all-after-cutoff () + "When every record is at or after the cutoff, all are kept." + (let* ((log (list (test-org-drill-statistics--make-record 300.0 'sm5) + (test-org-drill-statistics--make-record 400.0 'sm5))) + (result (org-drill-statistics--log-since log 200.0))) + (should (equal result log)))) + +(ert-deftest test-org-drill-statistics-log-since-empty-log () + "Filtering an empty log by cutoff returns an empty list." + (should (null (org-drill-statistics--log-since nil 200.0)))) + +(ert-deftest test-org-drill-statistics-log-since-does-not-mutate () + "Cutoff filtering leaves the input list intact." + (let* ((log (list (test-org-drill-statistics--make-record 100.0 'sm5) + (test-org-drill-statistics--make-record 300.0 'sm5))) + (copy (copy-sequence log))) + (org-drill-statistics--log-since log 200.0) + (should (equal log copy)))) + +(provide 'test-org-drill-statistics-primitives) + +;;; test-org-drill-statistics-primitives.el ends here |
