blob: 1a257674ba85ae2e0787ed5aa7082820fbaf9bcb (
plain)
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
|
;;; test-chime-tooltip-day-calculation.el --- Tests for tooltip day/hour calculation -*- lexical-binding: t; -*-
;;; Commentary:
;; Comprehensive tests for tooltip time-until formatting, especially day/hour calculations.
;;
;; Tests cover:
;; - Boundary cases (23h59m, 24h, 25h)
;; - Midnight boundaries
;; - Multiple days with fractional hours
;; - Exact day boundaries (48h, 72h)
;; - Edge cases that could trigger truncation bugs
;;; Code:
(require 'test-bootstrap (expand-file-name "test-bootstrap.el"))
(require 'testutil-time (expand-file-name "testutil-time.el"))
(require 'testutil-general (expand-file-name "testutil-general.el"))
(ert-deftest test-chime-tooltip-day-calculation-fractional-days ()
"Test that fractional days show both days and hours correctly.
User scenario: Viewing tooltip on Sunday 9pm, sees:
- Tuesday 9pm event: 48 hours = exactly 2 days → 'in 2 days'
- Wednesday 2pm event: 65 hours = 2.7 days → 'in 2 days 17 hours'
This test prevents regression of the integer division truncation bug."
(chime-create-test-base-dir)
(unwind-protect
(let* ((now (test-time-today-at 21 0)) ; Sunday 9pm
;; Create events at specific future times
(tuesday-9pm (time-add now (seconds-to-time (* 48 3600)))) ; +48 hours
(wednesday-2pm (time-add now (seconds-to-time (* 65 3600)))) ; +65 hours
(content (format "* Tuesday Event\n<%s>\n* Wednesday Event\n<%s>\n"
(format-time-string "<%Y-%m-%d %a %H:%M>" tuesday-9pm)
(format-time-string "<%Y-%m-%d %a %H:%M>" wednesday-2pm)))
(test-file (chime-create-temp-test-file-with-content content))
(test-buffer (find-file-noselect test-file))
(events nil))
;; Gather events
(with-current-buffer test-buffer
(org-mode)
(goto-char (point-min))
(while (re-search-forward "^\\*+ " nil t)
(beginning-of-line)
(push (chime--gather-info (point-marker)) events)
(forward-line 1)))
(kill-buffer test-buffer)
(setq events (nreverse events))
;; Set lookahead to cover events (7 days)
(setq chime-modeline-lookahead-minutes 10080)
(setq chime-tooltip-lookahead-hours 168)
(with-test-time now
;; Update modeline and get tooltip
(chime--update-modeline events)
(let ((tooltip (chime--make-tooltip chime--upcoming-events)))
;; Verify tooltip contains both events
(should (string-match-p "Tuesday Event" tooltip))
(should (string-match-p "Wednesday Event" tooltip))
;; Print tooltip for manual inspection
(message "TOOLTIP CONTENT:\n%s" tooltip)
;; AFTER FIX: Tuesday shows "in 2 days", Wednesday shows "in 2 days 17 hours"
;; Verify Tuesday shows exactly 2 days (no "hours" in countdown)
(should (string-match-p "Tuesday Event.*(in 2 days)" tooltip))
;; Make sure Tuesday doesn't have hours
(should-not (string-match-p "Tuesday Event.*hours" tooltip))
;; Verify Wednesday shows 2 days AND 17 hours
(should (string-match-p "Wednesday Event.*(in 2 days 17 hours)" tooltip))
;; Verify they show DIFFERENT countdowns
(let ((tuesday-line (progn
(string-match "Tuesday Event[^\n]*" tooltip)
(match-string 0 tooltip)))
(wednesday-line (progn
(string-match "Wednesday Event[^\n]*" tooltip)
(match-string 0 tooltip))))
(should-not (string= tuesday-line wednesday-line))))))
(chime-delete-test-base-dir)))
;;; Helper function for creating test events
(defun test-chime-tooltip-day-calculation--create-event-at-hours (base-time title hours-from-now)
"Create event with TITLE at HOURS-FROM-NOW hours from BASE-TIME.
Returns formatted org content string."
(let* ((event-time (time-add base-time (seconds-to-time (* hours-from-now 3600)))))
(format "* %s\n<%s>\n"
title
(format-time-string "%Y-%m-%d %a %H:%M" event-time))))
(defun test-chime-tooltip-day-calculation--get-formatted-line (tooltip event-name)
"Extract the formatted countdown line for EVENT-NAME from TOOLTIP."
(when (string-match (format "%s[^\n]*" event-name) tooltip)
(match-string 0 tooltip)))
;;; Boundary Cases - Critical thresholds
(ert-deftest test-chime-tooltip-day-calculation-boundary-exactly-24-hours ()
"Test event exactly 24 hours away shows 'in 1 day' not hours."
(chime-create-test-base-dir)
(unwind-protect
(let* ((now (test-time-today-at 12 0))
(content (test-chime-tooltip-day-calculation--create-event-at-hours now "Tomorrow Same Time" 24))
(test-file (chime-create-temp-test-file-with-content content))
(events (with-current-buffer (find-file-noselect test-file)
(org-mode)
(goto-char (point-min))
(let ((evs nil))
(while (re-search-forward "^\\*+ " nil t)
(push (chime--gather-info (point-marker)) evs))
(nreverse evs)))))
(kill-buffer (get-file-buffer test-file))
(setq chime-modeline-lookahead-minutes 10080)
(setq chime-tooltip-lookahead-hours 168)
(with-test-time now
(chime--update-modeline events)
(let ((tooltip (chime--make-tooltip chime--upcoming-events)))
;; Should show "in 1 day" not hours
(should (string-match-p "(in 1 day)" tooltip))
(should-not (string-match-p "hours" tooltip)))))
(chime-delete-test-base-dir)))
(ert-deftest test-chime-tooltip-day-calculation-boundary-23-hours-59-minutes ()
"Test event 23h59m away shows hours, not days (just under 24h threshold)."
(chime-create-test-base-dir)
(unwind-protect
(let* ((now (test-time-today-at 12 0))
;; 23 hours 59 minutes = 1439 minutes = just under 1440
(event-time (time-add now (seconds-to-time (* 1439 60))))
(content (format "* Almost Tomorrow\n<%s>\n"
(format-time-string "%Y-%m-%d %a %H:%M" event-time)))
(test-file (chime-create-temp-test-file-with-content content))
(events (with-current-buffer (find-file-noselect test-file)
(org-mode)
(goto-char (point-min))
(let ((evs nil))
(while (re-search-forward "^\\*+ " nil t)
(push (chime--gather-info (point-marker)) evs))
(nreverse evs)))))
(kill-buffer (get-file-buffer test-file))
(setq chime-modeline-lookahead-minutes 10080)
(setq chime-tooltip-lookahead-hours 168)
(with-test-time now
(chime--update-modeline events)
(let ((tooltip (chime--make-tooltip chime--upcoming-events)))
;; Should show hours format (< 24 hours)
(should (string-match-p "hours" tooltip))
(should-not (string-match-p "days?" tooltip)))))
(chime-delete-test-base-dir)))
(ert-deftest test-chime-tooltip-day-calculation-boundary-25-hours ()
"Test event 25 hours away shows 'in 1 day 1 hour'."
(chime-create-test-base-dir)
(unwind-protect
(let* ((now (test-time-today-at 12 0))
(content (test-chime-tooltip-day-calculation--create-event-at-hours now "Day Plus One" 25))
(test-file (chime-create-temp-test-file-with-content content))
(events (with-current-buffer (find-file-noselect test-file)
(org-mode)
(goto-char (point-min))
(let ((evs nil))
(while (re-search-forward "^\\*+ " nil t)
(push (chime--gather-info (point-marker)) evs))
(nreverse evs)))))
(kill-buffer (get-file-buffer test-file))
(setq chime-modeline-lookahead-minutes 10080)
(setq chime-tooltip-lookahead-hours 168)
(with-test-time now
(chime--update-modeline events)
(let ((tooltip (chime--make-tooltip chime--upcoming-events)))
;; Should show "in 1 day 1 hour"
(should (string-match-p "(in 1 day 1 hour)" tooltip)))))
(chime-delete-test-base-dir)))
(ert-deftest test-chime-tooltip-day-calculation-boundary-exactly-48-hours ()
"Test event exactly 48 hours away shows 'in 2 days' without hours."
(chime-create-test-base-dir)
(unwind-protect
(let* ((now (test-time-today-at 12 0))
(content (test-chime-tooltip-day-calculation--create-event-at-hours now "Two Days Exact" 48))
(test-file (chime-create-temp-test-file-with-content content))
(events (with-current-buffer (find-file-noselect test-file)
(org-mode)
(goto-char (point-min))
(let ((evs nil))
(while (re-search-forward "^\\*+ " nil t)
(push (chime--gather-info (point-marker)) evs))
(nreverse evs)))))
(kill-buffer (get-file-buffer test-file))
(setq chime-modeline-lookahead-minutes 10080)
(setq chime-tooltip-lookahead-hours 168)
(with-test-time now
(chime--update-modeline events)
(let ((tooltip (chime--make-tooltip chime--upcoming-events))
(line (test-chime-tooltip-day-calculation--get-formatted-line
(chime--make-tooltip chime--upcoming-events) "Two Days Exact")))
;; Should show exactly "in 2 days" with NO hours
(should (string-match-p "(in 2 days)" tooltip))
;; Verify the line doesn't contain "hour" (would be "2 days 0 hours")
(should-not (string-match-p "hour" line)))))
(chime-delete-test-base-dir)))
;;; Midnight Boundaries
(ert-deftest test-chime-tooltip-day-calculation-midnight-crossing-shows-correct-days ()
"Test event crossing midnight boundary calculates days correctly.
Scenario: 11pm now, event at 2am (3 hours later, next calendar day)
Should show hours, not '1 day' since it's only 3 hours away."
(chime-create-test-base-dir)
(unwind-protect
(let* ((now (test-time-today-at 23 0)) ; 11pm
;; 3 hours later = 2am next day
(content (test-chime-tooltip-day-calculation--create-event-at-hours now "Early Morning" 3))
(test-file (chime-create-temp-test-file-with-content content))
(events (with-current-buffer (find-file-noselect test-file)
(org-mode)
(goto-char (point-min))
(let ((evs nil))
(while (re-search-forward "^\\*+ " nil t)
(push (chime--gather-info (point-marker)) evs))
(nreverse evs)))))
(kill-buffer (get-file-buffer test-file))
(setq chime-modeline-lookahead-minutes 10080)
(setq chime-tooltip-lookahead-hours 168)
(with-test-time now
(chime--update-modeline events)
(let ((tooltip (chime--make-tooltip chime--upcoming-events)))
;; Should show "in 3 hours" not "in 1 day"
(should (string-match-p "3 hours" tooltip))
(should-not (string-match-p "days?" tooltip)))))
(chime-delete-test-base-dir)))
(ert-deftest test-chime-tooltip-day-calculation-midnight-plus-one-day ()
"Test event at midnight tomorrow (24h exactly) shows '1 day'."
(chime-create-test-base-dir)
(unwind-protect
(let* ((now (test-time-today-at 0 0)) ; Midnight today
(content (test-chime-tooltip-day-calculation--create-event-at-hours now "Midnight Tomorrow" 24))
(test-file (chime-create-temp-test-file-with-content content))
(events (with-current-buffer (find-file-noselect test-file)
(org-mode)
(goto-char (point-min))
(let ((evs nil))
(while (re-search-forward "^\\*+ " nil t)
(push (chime--gather-info (point-marker)) evs))
(nreverse evs)))))
(kill-buffer (get-file-buffer test-file))
(setq chime-modeline-lookahead-minutes 10080)
(setq chime-tooltip-lookahead-hours 168)
(with-test-time now
(chime--update-modeline events)
(let ((tooltip (chime--make-tooltip chime--upcoming-events)))
(should (string-match-p "(in 1 day)" tooltip))
(should-not (string-match-p "hour" tooltip)))))
(chime-delete-test-base-dir)))
;;; Multiple Events - Verify distinct formatting
(ert-deftest test-chime-tooltip-day-calculation-multiple-events-distinct ()
"Test multiple events at different fractional-day offsets show distinct times."
(chime-create-test-base-dir)
(unwind-protect
(let* ((now (test-time-today-at 12 0))
(content (concat
(test-chime-tooltip-day-calculation--create-event-at-hours now "Event 1 Day" 24)
(test-chime-tooltip-day-calculation--create-event-at-hours now "Event 1.5 Days" 36)
(test-chime-tooltip-day-calculation--create-event-at-hours now "Event 2 Days" 48)
(test-chime-tooltip-day-calculation--create-event-at-hours now "Event 2.75 Days" 66)))
(test-file (chime-create-temp-test-file-with-content content))
(events (with-current-buffer (find-file-noselect test-file)
(org-mode)
(goto-char (point-min))
(let ((evs nil))
(while (re-search-forward "^\\*+ " nil t)
(push (chime--gather-info (point-marker)) evs))
(nreverse evs)))))
(kill-buffer (get-file-buffer test-file))
(setq chime-modeline-lookahead-minutes 10080)
(setq chime-tooltip-lookahead-hours 168)
(with-test-time now
(chime--update-modeline events)
(let ((tooltip (chime--make-tooltip chime--upcoming-events)))
;; Verify each event shows correctly
(should (string-match-p "Event 1 Day.*(in 1 day)" tooltip))
(should (string-match-p "Event 1.5 Days.*(in 1 day 12 hours)" tooltip))
(should (string-match-p "Event 2 Days.*(in 2 days)" tooltip))
(should (string-match-p "Event 2.75 Days.*(in 2 days 18 hours)" tooltip))
;; Verify they're all different
(let ((lines (split-string tooltip "\n")))
(let ((countdowns (cl-remove-if-not
(lambda (line) (string-match-p "Event.*day" line))
lines)))
;; Should have 4 distinct countdown lines
(should (= 4 (length countdowns)))
;; All should be unique
(should (= 4 (length (delete-dups (copy-sequence countdowns))))))))))
(chime-delete-test-base-dir)))
(provide 'test-chime-tooltip-day-calculation)
;;; test-chime-tooltip-day-calculation.el ends here
|