aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chime-org-contacts.el55
-rw-r--r--tests/test-chime-org-contacts.el157
2 files changed, 16 insertions, 196 deletions
diff --git a/chime-org-contacts.el b/chime-org-contacts.el
index 33d6010..5f532f6 100644
--- a/chime-org-contacts.el
+++ b/chime-org-contacts.el
@@ -86,60 +86,29 @@ New contacts will be filed under this heading in `chime-org-contacts-file'."
;;; Implementation
-(defun chime-org-contacts--parse-birthday (birthday-string)
- "Parse BIRTHDAY-STRING into (YEAR MONTH DAY) list.
-YEAR may be current year if not present in the string.
-Returns nil if parsing fails."
- (cond
- ;; Format: YYYY-MM-DD
- ((string-match "^\\([0-9]\\{4\\}\\)-\\([0-9]\\{2\\}\\)-\\([0-9]\\{2\\}\\)$" birthday-string)
- (list (string-to-number (match-string 1 birthday-string))
- (string-to-number (match-string 2 birthday-string))
- (string-to-number (match-string 3 birthday-string))))
- ;; Format: MM-DD (use current year)
- ((string-match "^\\([0-9]\\{2\\}\\)-\\([0-9]\\{2\\}\\)$" birthday-string)
- (list (nth 5 (decode-time))
- (string-to-number (match-string 1 birthday-string))
- (string-to-number (match-string 2 birthday-string))))
- (t nil)))
-
-(defun chime-org-contacts--format-timestamp (year month day)
- "Format YEAR MONTH DAY as yearly repeating org timestamp."
- (let* ((time (encode-time 0 0 0 day month year))
- (dow (format-time-string "%a" time)))
- (format "<%04d-%02d-%02d %s +1y>" year month day dow)))
-
-(defun chime-org-contacts--insert-timestamp-after-drawer (timestamp)
- "Insert TIMESTAMP after properties drawer if not already present."
- (let ((heading-end (save-excursion (outline-next-heading) (point))))
- (when (re-search-forward "^[ \t]*:END:[ \t]*$" heading-end t)
- (let ((end-pos (point)))
- ;; Only insert if no yearly timestamp already exists
- (unless (save-excursion
- (goto-char end-pos)
- (re-search-forward "<[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}[^>]*\\+1y>" heading-end t))
- (goto-char end-pos)
- (end-of-line)
- (insert "\n" timestamp))))))
+;; Birthday parsing, formatting, and insertion are provided by
+;; convert-org-contacts-birthdays.el to avoid duplication.
+(require 'convert-org-contacts-birthdays
+ (expand-file-name "convert-org-contacts-birthdays.el"
+ (file-name-directory (or load-file-name buffer-file-name))))
(defun chime-org-contacts--finalize-birthday-timestamp ()
"Add yearly repeating timestamp after properties drawer if BIRTHDAY is set.
This function is called during org-capture finalization to automatically
insert a plain timestamp for birthdays, enabling them to appear in org-agenda
-without requiring org-contacts to be loaded in the async subprocess."
+without requiring org-contacts to be loaded in the async subprocess.
+
+Delegates to `chime--insert-birthday-timestamp-after-drawer' for the
+actual parsing, formatting, and insertion."
(when (string= (plist-get org-capture-plist :key) chime-org-contacts-capture-key)
(save-excursion
(goto-char (point-min))
(let ((birthday (org-entry-get (point) "BIRTHDAY")))
(when (and birthday (not (string-blank-p birthday)))
- (let ((parsed (chime-org-contacts--parse-birthday birthday)))
- (when parsed
- (let* ((year (nth 0 parsed))
- (month (nth 1 parsed))
- (day (nth 2 parsed))
- (timestamp (chime-org-contacts--format-timestamp year month day)))
- (chime-org-contacts--insert-timestamp-after-drawer timestamp)))))))))
+ (condition-case nil
+ (chime--insert-birthday-timestamp-after-drawer birthday)
+ (user-error nil)))))))
(defun chime-org-contacts--setup-capture-template ()
"Add org-capture template for contacts with birthday timestamps.
diff --git a/tests/test-chime-org-contacts.el b/tests/test-chime-org-contacts.el
index 3fbe11f..172ddd1 100644
--- a/tests/test-chime-org-contacts.el
+++ b/tests/test-chime-org-contacts.el
@@ -23,164 +23,15 @@
(require 'org)
(require 'org-capture)
+;; Load the conversion module (shared birthday utilities)
+(load (expand-file-name "../convert-org-contacts-birthdays.el"
+ (file-name-directory (or load-file-name buffer-file-name))) nil t)
+
;; 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 ()