aboutsummaryrefslogtreecommitdiff
path: root/tests/test-org-drill-statistics-sparkline.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-31 08:35:16 -0500
committerCraig Jennings <c@cjennings.net>2026-05-31 08:35:16 -0500
commit26cc4472dea261a1ad13fbee8fb6a91b019f77bb (patch)
treea7063337b05c3ea278a5b910d0f1420de033dfe8 /tests/test-org-drill-statistics-sparkline.el
parent532ce532465834ce06238648ba1490c48bed29ca (diff)
downloadorg-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-sparkline.el')
-rw-r--r--tests/test-org-drill-statistics-sparkline.el84
1 files changed, 84 insertions, 0 deletions
diff --git a/tests/test-org-drill-statistics-sparkline.el b/tests/test-org-drill-statistics-sparkline.el
new file mode 100644
index 0000000..ae1f5c0
--- /dev/null
+++ b/tests/test-org-drill-statistics-sparkline.el
@@ -0,0 +1,84 @@
+;;; test-org-drill-statistics-sparkline.el --- Tests for sparkline statistics -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; ERT tests for the org-drill statistics dashboard sparkline block.
+
+;;; Code:
+
+(require 'ert)
+(require 'org-drill)
+(require 'cl-lib)
+(require 'org)
+
+(ert-deftest test-org-drill-statistics-sparkline-empty ()
+ "An empty sequence renders as the empty string."
+ (should (equal (org-drill-statistics--sparkline '()) ""))
+ (should (equal (org-drill-statistics--sparkline []) "")))
+
+(ert-deftest test-org-drill-statistics-sparkline-single-value ()
+ "A single positive value renders as the full block.
+With a one-element sequence and no explicit MAX, the value equals the
+derived max and maps to the tallest glyph."
+ (should (equal (org-drill-statistics--sparkline '(5)) "█")))
+
+(ert-deftest test-org-drill-statistics-sparkline-single-zero ()
+ "A single zero value renders as the lowest block, not an error.
+The derived max is zero, so the all-zero branch applies."
+ (should (equal (org-drill-statistics--sparkline '(0)) "▁")))
+
+(ert-deftest test-org-drill-statistics-sparkline-all-equal ()
+ "All-equal positive values all render as the full block.
+Each value equals the derived max, so every glyph is the tallest."
+ (should (equal (org-drill-statistics--sparkline '(3 3 3 3)) "████")))
+
+(ert-deftest test-org-drill-statistics-sparkline-all-zero ()
+ "All-zero values render as the lowest block, one per entry.
+Max is zero, so the division-by-zero guard returns the lowest glyph for
+every value instead of erroring."
+ (should (equal (org-drill-statistics--sparkline '(0 0 0)) "▁▁▁")))
+
+(ert-deftest test-org-drill-statistics-sparkline-known-ramp ()
+ "A 0..7 ramp against MAX 7 maps to each glyph in order.
+With value I and MAX 7, the index is (round (* (/ I 7.0) 7)) = I, so the
+ramp walks the charset from lowest to highest exactly once."
+ (should (equal (org-drill-statistics--sparkline '(0 1 2 3 4 5 6 7) 7)
+ "▁▂▃▄▅▆▇█")))
+
+(ert-deftest test-org-drill-statistics-sparkline-nil-entries ()
+ "Nil entries render as spaces and do not affect the derived max.
+The values 1 and 2 against derived max 2 scale to indices 4 (1/2*7
+rounds to 4) and 7."
+ (should (equal (org-drill-statistics--sparkline '(nil 1 nil 2 nil))
+ " ▅ █ ")))
+
+(ert-deftest test-org-drill-statistics-sparkline-all-nil ()
+ "An all-nil sequence renders as one space per entry."
+ (should (equal (org-drill-statistics--sparkline '(nil nil nil)) " ")))
+
+(ert-deftest test-org-drill-statistics-sparkline-explicit-max ()
+ "An explicit MAX scales values against it, not the sequence max.
+With MAX 10, value 10 is the full block, 0 is the lowest, and 5 scales
+to 5/10*7 = 3.5 which rounds to index 4."
+ (should (equal (org-drill-statistics--sparkline '(0 5 10) 10) "▁▅█")))
+
+(ert-deftest test-org-drill-statistics-sparkline-explicit-zero-max ()
+ "An explicit MAX of zero renders every value as the lowest block.
+The guard treats a zero ceiling like the all-zero case rather than
+dividing by zero."
+ (should (equal (org-drill-statistics--sparkline '(0 1 2) 0) "▁▁▁")))
+
+(ert-deftest test-org-drill-statistics-sparkline-value-above-max ()
+ "A value exceeding MAX clamps to the full block instead of overflowing.
+Without the clamp the computed index would exceed the charset length.
+The 5 scales to 5/10*7 = 3.5 which rounds to index 4."
+ (should (equal (org-drill-statistics--sparkline '(20) 10) "█"))
+ (should (equal (org-drill-statistics--sparkline '(5 20) 10) "▅█")))
+
+(ert-deftest test-org-drill-statistics-sparkline-vector-input ()
+ "A vector argument is accepted and rendered like a list.
+The helper coerces any sequence, so callers may pass either."
+ (should (equal (org-drill-statistics--sparkline [0 7] 7) "▁█")))
+
+(provide 'test-org-drill-statistics-sparkline)
+
+;;; test-org-drill-statistics-sparkline.el ends here