blob: a9a01a1e29f05498e4faec32f9b554571984d32a (
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
|
;;; test-chime-org-contacts.el --- Tests for chime-org-contacts.el -*- lexical-binding: t; -*-
;; Copyright (C) 2025 Craig Jennings
;; Author: Craig Jennings <c@cjennings.net>
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;; Commentary:
;; Unit and integration tests for chime-org-contacts.el
;;; Code:
;; Initialize package system for batch mode
(when noninteractive
(package-initialize))
(require 'ert)
(require 'org)
(require 'org-capture)
;; Load the module being tested
(let ((module-file (expand-file-name "../chime-org-contacts.el"
(file-name-directory (or load-file-name buffer-file-name)))))
(load module-file nil t))
;;; Unit Tests - chime-org-contacts--parse-birthday
(ert-deftest test-chime-org-contacts-parse-birthday-full-format ()
"Test parsing YYYY-MM-DD format."
(let ((result (chime-org-contacts--parse-birthday "2000-01-01")))
(should (equal result '(2000 1 1))))
(let ((result (chime-org-contacts--parse-birthday "1985-03-15")))
(should (equal result '(1985 3 15))))
(let ((result (chime-org-contacts--parse-birthday "2024-12-31")))
(should (equal result '(2024 12 31)))))
(ert-deftest test-chime-org-contacts-parse-birthday-partial-format ()
"Test parsing MM-DD format uses current year."
(let ((current-year (nth 5 (decode-time))))
(let ((result (chime-org-contacts--parse-birthday "03-15")))
(should (equal result (list current-year 3 15))))
(let ((result (chime-org-contacts--parse-birthday "12-31")))
(should (equal result (list current-year 12 31))))))
(ert-deftest test-chime-org-contacts-parse-birthday-leap-year ()
"Test parsing leap year date."
(let ((result (chime-org-contacts--parse-birthday "2024-02-29")))
(should (equal result '(2024 2 29)))))
(ert-deftest test-chime-org-contacts-parse-birthday-invalid-format ()
"Test that invalid formats return nil."
(should (null (chime-org-contacts--parse-birthday "2000/01/01")))
(should (null (chime-org-contacts--parse-birthday "1-1-2000")))
(should (null (chime-org-contacts--parse-birthday "Jan 1, 2000")))
(should (null (chime-org-contacts--parse-birthday "not a date"))))
(ert-deftest test-chime-org-contacts-parse-birthday-empty-input ()
"Test that empty input returns nil."
(should (null (chime-org-contacts--parse-birthday ""))))
(ert-deftest test-chime-org-contacts-parse-birthday-boundary-dates ()
"Test boundary dates (start/end of year, end of months)."
(should (equal (chime-org-contacts--parse-birthday "2025-01-01") '(2025 1 1)))
(should (equal (chime-org-contacts--parse-birthday "2025-12-31") '(2025 12 31)))
(should (equal (chime-org-contacts--parse-birthday "2025-11-30") '(2025 11 30))))
;;; Unit Tests - chime-org-contacts--format-timestamp
(ert-deftest test-chime-org-contacts-format-timestamp-basic ()
"Test basic timestamp formatting."
(let ((timestamp (chime-org-contacts--format-timestamp 2025 1 1)))
(should (string-match-p "^<2025-01-01 [A-Za-z]\\{3\\} \\+1y>$" timestamp))))
(ert-deftest test-chime-org-contacts-format-timestamp-day-of-week ()
"Test that day of week matches the date."
;; 2025-01-01 is a Wednesday
(let ((timestamp (chime-org-contacts--format-timestamp 2025 1 1)))
(should (string-match-p "Wed" timestamp)))
;; 2024-02-29 is a Thursday (leap year)
(let ((timestamp (chime-org-contacts--format-timestamp 2024 2 29)))
(should (string-match-p "Thu" timestamp))))
(ert-deftest test-chime-org-contacts-format-timestamp-all-months ()
"Test formatting for all months."
(dolist (month '(1 2 3 4 5 6 7 8 9 10 11 12))
(let ((timestamp (chime-org-contacts--format-timestamp 2025 month 1)))
(should (string-match-p (format "^<2025-%02d-01 [A-Za-z]\\{3\\} \\+1y>$" month) timestamp)))))
(ert-deftest test-chime-org-contacts-format-timestamp-repeater ()
"Test that +1y repeater is always included."
(let ((timestamp (chime-org-contacts--format-timestamp 2025 3 15)))
(should (string-match-p "\\+1y>" timestamp))))
;;; Unit Tests - chime-org-contacts--insert-timestamp-after-drawer
(ert-deftest test-chime-org-contacts-insert-timestamp-when-none-exists ()
"Test inserting timestamp when none exists."
(with-temp-buffer
(org-mode)
(insert "* Contact\n")
(insert ":PROPERTIES:\n")
(insert ":BIRTHDAY: 2000-01-01\n")
(insert ":END:\n")
(goto-char (point-min))
(chime-org-contacts--insert-timestamp-after-drawer "<2000-01-01 Wed +1y>")
(let ((content (buffer-string)))
(should (string-match-p "<2000-01-01 Wed \\+1y>" content))
(should (string-match-p ":END:\n<2000-01-01" content)))))
(ert-deftest test-chime-org-contacts-insert-timestamp-skips-when-exists ()
"Test that insertion is skipped when timestamp already exists."
(with-temp-buffer
(org-mode)
(insert "* Contact\n")
(insert ":PROPERTIES:\n")
(insert ":BIRTHDAY: 2000-01-01\n")
(insert ":END:\n")
(insert "<2000-01-01 Wed +1y>\n")
(goto-char (point-min))
(chime-org-contacts--insert-timestamp-after-drawer "<2000-01-01 Wed +1y>")
;; Should have exactly one timestamp
(should (= 1 (how-many "<2000-01-01 [A-Za-z]\\{3\\} \\+1y>" (point-min) (point-max))))))
(ert-deftest test-chime-org-contacts-insert-timestamp-handles-whitespace ()
"Test handling of whitespace around :END:."
(with-temp-buffer
(org-mode)
(insert "* Contact\n")
(insert ":PROPERTIES:\n")
(insert ":BIRTHDAY: 2000-01-01\n")
(insert " :END: \n") ; Whitespace before and after
(goto-char (point-min))
(chime-org-contacts--insert-timestamp-after-drawer "<2000-01-01 Wed +1y>")
(should (string-match-p "<2000-01-01 Wed \\+1y>" (buffer-string)))))
(ert-deftest test-chime-org-contacts-insert-timestamp-preserves-content ()
"Test that insertion doesn't modify other content."
(with-temp-buffer
(org-mode)
(insert "* Contact\n")
(insert ":PROPERTIES:\n")
(insert ":EMAIL: test@example.com\n")
(insert ":BIRTHDAY: 2000-01-01\n")
(insert ":END:\n")
(insert "Some notes about the contact.\n")
(goto-char (point-min))
(let ((original-content (buffer-substring (point-min) (point-max))))
(chime-org-contacts--insert-timestamp-after-drawer "<2000-01-01 Wed +1y>")
(should (string-search ":EMAIL: test@example.com" (buffer-string)))
(should (string-search "Some notes about the contact" (buffer-string))))))
(ert-deftest test-chime-org-contacts-insert-timestamp-missing-end ()
"Test handling when :END: is missing (malformed drawer)."
(with-temp-buffer
(org-mode)
(insert "* Contact\n")
(insert ":PROPERTIES:\n")
(insert ":BIRTHDAY: 2000-01-01\n")
;; No :END:
(goto-char (point-min))
(chime-org-contacts--insert-timestamp-after-drawer "<2000-01-01 Wed +1y>")
;; Should not insert when :END: is missing
(should-not (string-match-p "<2000-01-01" (buffer-string)))))
;;; Integration Tests - chime-org-contacts--finalize-birthday-timestamp
(ert-deftest test-chime-org-contacts-finalize-adds-timestamp-full-date ()
"Test finalize adds timestamp for YYYY-MM-DD birthday."
(with-temp-buffer
(org-mode)
(insert "* Alice Anderson\n")
(insert ":PROPERTIES:\n")
(insert ":EMAIL: alice@example.com\n")
(insert ":BIRTHDAY: 1985-03-15\n")
(insert ":END:\n")
(goto-char (point-min))
(let ((org-capture-plist '(:key "C")))
(chime-org-contacts--finalize-birthday-timestamp)
(let ((content (buffer-string)))
(should (string-match-p "<1985-03-15 [A-Za-z]\\{3\\} \\+1y>" content))))))
(ert-deftest test-chime-org-contacts-finalize-adds-timestamp-partial-date ()
"Test finalize adds timestamp for MM-DD birthday."
(let ((current-year (nth 5 (decode-time))))
(with-temp-buffer
(org-mode)
(insert "* Bob Baker\n")
(insert ":PROPERTIES:\n")
(insert ":BIRTHDAY: 07-04\n")
(insert ":END:\n")
(goto-char (point-min))
(let ((org-capture-plist '(:key "C")))
(chime-org-contacts--finalize-birthday-timestamp)
(let ((content (buffer-string)))
(should (string-match-p (format "<%d-07-04 [A-Za-z]\\{3\\} \\+1y>" current-year) content)))))))
(ert-deftest test-chime-org-contacts-finalize-skips-when-no-birthday ()
"Test finalize does nothing when :BIRTHDAY: property missing."
(with-temp-buffer
(org-mode)
(insert "* Carol Chen\n")
(insert ":PROPERTIES:\n")
(insert ":EMAIL: carol@example.com\n")
(insert ":END:\n")
(goto-char (point-min))
(let ((original-content (buffer-string))
(org-capture-plist '(:key "C")))
(chime-org-contacts--finalize-birthday-timestamp)
;; Content should be unchanged
(should (string= (buffer-string) original-content)))))
(ert-deftest test-chime-org-contacts-finalize-skips-empty-birthday ()
"Test finalize skips empty birthday values."
(with-temp-buffer
(org-mode)
(insert "* David Davis\n")
(insert ":PROPERTIES:\n")
(insert ":BIRTHDAY: \n")
(insert ":END:\n")
(goto-char (point-min))
(let ((original-content (buffer-string))
(org-capture-plist '(:key "C")))
(chime-org-contacts--finalize-birthday-timestamp)
(should (string= (buffer-string) original-content)))))
(ert-deftest test-chime-org-contacts-finalize-only-runs-for-correct-key ()
"Test finalize only runs for configured capture key."
(with-temp-buffer
(org-mode)
(insert "* Task\n")
(insert ":PROPERTIES:\n")
(insert ":BIRTHDAY: 2000-01-01\n")
(insert ":END:\n")
(goto-char (point-min))
(let ((original-content (buffer-string))
(org-capture-plist '(:key "t"))) ; Different key
(chime-org-contacts--finalize-birthday-timestamp)
;; Should not insert timestamp
(should (string= (buffer-string) original-content)))))
;;; Integration Tests - chime-org-contacts--setup-capture-template
(ert-deftest test-chime-org-contacts-setup-adds-template-when-file-set ()
"Test that template is added when file is set."
(let ((chime-org-contacts-file "/tmp/test-contacts.org")
(org-capture-templates nil))
(chime-org-contacts--setup-capture-template)
(should org-capture-templates)
(should (assoc "C" org-capture-templates))))
(ert-deftest test-chime-org-contacts-setup-skips-when-file-nil ()
"Test that template is not added when file is nil."
(let ((chime-org-contacts-file nil)
(org-capture-templates nil))
(chime-org-contacts--setup-capture-template)
(should-not org-capture-templates)))
(ert-deftest test-chime-org-contacts-setup-template-structure ()
"Test that added template has correct structure."
(let ((chime-org-contacts-file "/tmp/test-contacts.org")
(chime-org-contacts-capture-key "C")
(chime-org-contacts-heading "Contacts")
(org-capture-templates nil))
(chime-org-contacts--setup-capture-template)
(let ((template (assoc "C" org-capture-templates)))
(should (string= (nth 1 template) "Contact (chime)"))
(should (eq (nth 2 template) 'entry))
(should (equal (nth 3 template) '(file+headline chime-org-contacts-file "Contacts"))))))
(ert-deftest test-chime-org-contacts-setup-uses-custom-key ()
"Test that template uses custom capture key."
(let ((chime-org-contacts-file "/tmp/test-contacts.org")
(chime-org-contacts-capture-key "K")
(org-capture-templates nil))
(chime-org-contacts--setup-capture-template)
(should (assoc "K" org-capture-templates))
(should-not (assoc "C" org-capture-templates))))
(provide 'test-chime-org-contacts)
;;; test-chime-org-contacts.el ends here
|