diff options
| -rw-r--r-- | chime-org-contacts.el | 55 | ||||
| -rw-r--r-- | tests/test-chime-org-contacts.el | 157 |
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 () |
