1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
;;; test-org-drill-statistics-render-trends.el --- Tests for render-trends statistics -*- lexical-binding: t; -*-
;;; Commentary:
;; ERT tests for the org-drill statistics dashboard render-trends block.
;;; Code:
(require 'ert)
(require 'org-drill)
(require 'cl-lib)
(require 'org)
;;; Tests for the Trends render helper (render 2/5).
(defun org-drill-statistics-test--record (start-day-offset qualities
&optional duration-min)
"Build an `org-drill-session-record' for trends tests.
START-DAY-OFFSET is an integer day offset from today (0 is today, -7 is
a week ago). QUALITIES is a list of int qualities, stored as a vector.
DURATION-MIN defaults to 10 minutes. The record's start-time and
end-time floats land on the requested day; only the day component is
load bearing for these tests."
(let* ((duration (or duration-min 10))
(today-secs (float-time (current-time)))
(start (+ today-secs (* start-day-offset 86400.0)))
(end (+ start (* duration 60.0))))
(make-org-drill-session-record
:start-time start
:end-time end
:scope 'file
:algorithm 'sm5
:qualities (vconcat qualities)
:pass-percent (org-drill--compute-pass-percent (vconcat qualities))
:new-count 0
:mature-count (length qualities)
:failed-count 0
:cram-mode nil)))
(ert-deftest test-org-drill-statistics-render-trends-has-subheading ()
"The rendered section opens with the \"* Trends\" org subheading."
(let ((out (org-drill-statistics--render-trends nil)))
(should (string-prefix-p "* Trends\n" out))))
(ert-deftest test-org-drill-statistics-render-trends-empty-log ()
"An empty log still renders both sparkline lines and a table header.
The sparklines are all-space (no data) and the table has only its header
and separator rows."
(let ((out (org-drill-statistics--render-trends nil)))
(should (string-match-p "Reviews/day (last 90):" out))
(should (string-match-p "Pass rate/day (last 90):" out))
(should (string-match-p "| Week | Reviews | Pass % | Avg min |" out))
(should (string-match-p "|------" out))))
(ert-deftest test-org-drill-statistics-render-trends-sparkline-glyph ()
"A record today puts a non-space block glyph in the reviews sparkline.
The final sparkline column (today) must be a quadrant block, not the
space rendered for empty days."
(let* ((log (list (org-drill-statistics-test--record 0 '(5 4 3))))
(out (org-drill-statistics--render-trends log))
(line (car (seq-filter
(lambda (l) (string-prefix-p "Reviews/day" l))
(split-string out "\n")))))
;; The sparkline glyph for the busiest day is the full block, since
;; today is the only day with data so it scales to the ceiling.
(should (string-match-p "█" line))))
(ert-deftest test-org-drill-statistics-render-trends-weekly-row ()
"A this-week session produces a body row with its counts.
The row carries the Monday date of this week, the review count, the
pass percentage, and a one-decimal average duration."
(let* ((today (time-to-days (current-time)))
(week-start (org-drill-statistics--week-start-day today))
(expected-date (org-drill-statistics--format-week-start week-start))
;; Three qualities, two passes (> failure-quality default 2).
(log (list (org-drill-statistics-test--record 0 '(5 4 1) 20)))
(out (org-drill-statistics--render-trends log)))
;; Pass percent: 2 of 3 -> 67. Avg duration: 20.0 minutes.
(should (string-match-p
(regexp-quote (format "| %s | 3 | 67 | 20.0 |" expected-date))
out))))
(ert-deftest test-org-drill-statistics-render-trends-twelve-week-rows ()
"The table body has exactly 12 week rows, one per week in the window."
(let* ((out (org-drill-statistics--render-trends nil))
(lines (split-string out "\n" t))
(body (seq-filter
(lambda (l)
(and (string-prefix-p "| " l)
(not (string-match-p "Week" l))))
lines)))
(should (= (length body) 12))))
(ert-deftest test-org-drill-statistics-render-trends-algorithm-filter ()
"Passing an algorithm filters records out of the aggregates.
A record under `sm5' is excluded when the section filters for `sm2',
leaving an empty (all-zero) this-week row."
(let* ((today (time-to-days (current-time)))
(week-start (org-drill-statistics--week-start-day today))
(date (org-drill-statistics--format-week-start week-start))
(log (list (org-drill-statistics-test--record 0 '(5 4 3) 15)))
(out (org-drill-statistics--render-trends log 'sm2)))
;; sm5 record is filtered out, so this week's row is zeroed.
(should (string-match-p
(regexp-quote (format "| %s | 0 | 0 | 0.0 |" date))
out))
;; And the unfiltered render keeps it.
(let ((unfiltered (org-drill-statistics--render-trends log)))
(should (string-match-p
(regexp-quote (format "| %s | 3 |" date))
unfiltered)))))
(ert-deftest test-org-drill-statistics-render-trends-pass-rate-absolute-scale ()
"The pass-rate sparkline scales against 100, not the window peak.
A day with a 50 percent pass rate must render a mid-height glyph, not
the full block it would reach if scaled to its own maximum."
(let* ((log (list (org-drill-statistics-test--record 0 '(5 1))))
(out (org-drill-statistics--render-trends log))
(line (car (seq-filter
(lambda (l) (string-prefix-p "Pass rate/day" l))
(split-string out "\n")))))
;; 1 pass of 2 -> 50 percent. Scaled to 100 over an 8-glyph charset,
;; round(50/100 * 7) = 4 -> the 5th glyph, not the full block.
(should-not (string-match-p "█" line))
(should (string-match-p "▅" line))))
(provide 'test-org-drill-statistics-render-trends)
;;; test-org-drill-statistics-render-trends.el ends here
|