From cc74ca5cfa881fef318b87d9eaada2c7cca2e6d3 Mon Sep 17 00:00:00 2001 From: James Brechtel Date: Sat, 20 Jun 2026 16:58:45 -0400 Subject: [PATCH] test: add ERT tests for spine-books, fix cl-return-from issue --- spine.el | 67 ++++++++++++++++---------------- test/spine-books-test.el | 83 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 33 deletions(-) create mode 100644 test/spine-books-test.el diff --git a/spine.el b/spine.el index db62390..4a42125 100644 --- a/spine.el +++ b/spine.el @@ -85,39 +85,40 @@ Returns nil for empty or missing properties." (defun spine-books () "Return a list of plists, one per top-level headline in `spine-org-file'. Returns nil if the file is missing or cannot be parsed." - (unless (file-exists-p spine-org-file) - (message "spine-books: file not found: %s" spine-org-file) - (cl-return-from spine-books nil)) - (condition-case err - (with-temp-buffer - (insert-file-contents spine-org-file) - (org-mode) - (let ((books nil)) - (org-element-map (org-element-parse-buffer 'headline) 'headline - (lambda (hl) - (when (= (org-element-property :level hl) 1) - (let ((pos (org-element-property :begin hl))) - (push (list :id (spine--prop pos "ID") - :title (org-element-property :title hl) - :status (org-element-property :todo-keyword hl) - :author (spine--prop pos "AUTHOR") - :isbn (spine--prop pos "ISBN") - :cover (spine--prop pos "COVER") - :format (spine--prop pos "FORMAT") - :rec_by (spine--prop pos "REC_BY") - :rec_note (spine--prop pos "REC_NOTE") - :rating (spine--prop pos "RATING") - :added (spine--prop pos "ADDED") - :tags (org-element-property :tags hl) - :notes (spine--extract-notes - (org-element-property :begin hl) - (org-element-property :end hl))) - books))))) - (nreverse books))) - (error - (message "spine-books: failed to parse %s: %s" - spine-org-file (error-message-string err)) - nil))) + (if (not (file-exists-p spine-org-file)) + (progn + (message "spine-books: file not found: %s" spine-org-file) + nil) + (condition-case err + (with-temp-buffer + (insert-file-contents spine-org-file) + (org-mode) + (let ((books nil)) + (org-element-map (org-element-parse-buffer 'headline) 'headline + (lambda (hl) + (when (= (org-element-property :level hl) 1) + (let ((pos (org-element-property :begin hl))) + (push (list :id (spine--prop pos "ID") + :title (org-element-property :title hl) + :status (org-element-property :todo-keyword hl) + :author (spine--prop pos "AUTHOR") + :isbn (spine--prop pos "ISBN") + :cover (spine--prop pos "COVER") + :format (spine--prop pos "FORMAT") + :rec_by (spine--prop pos "REC_BY") + :rec_note (spine--prop pos "REC_NOTE") + :rating (spine--prop pos "RATING") + :added (spine--prop pos "ADDED") + :tags (org-element-property :tags hl) + :notes (spine--extract-notes + (org-element-property :begin hl) + (org-element-property :end hl))) + books))))) + (nreverse books))) + (error + (message "spine-books: failed to parse %s: %s" + spine-org-file (error-message-string err)) + nil)))) (defvar spine-skip-server-start nil "When non-nil, do not start the HTTP server. diff --git a/test/spine-books-test.el b/test/spine-books-test.el new file mode 100644 index 0000000..e44f6bd --- /dev/null +++ b/test/spine-books-test.el @@ -0,0 +1,83 @@ +;;; spine-books-test.el — ERT tests for spine-books + +(require 'ert) +(require 'cl-lib) + +;; Load spine.el without starting the server +(setq spine-skip-server-start t) +(setq spine-org-file (expand-file-name "sample-books.org" + (file-name-directory + (directory-file-name + (file-name-directory load-file-name))))) +(load-file (expand-file-name "../spine.el" + (file-name-directory load-file-name))) + +(ert-deftest spine-books-parses-sample () + "spine-books returns correct plists for sample-books.org." + (let ((books (spine-books))) + (should books) + (should (= (length books) 5)))) + +(ert-deftest spine-books-first-book-has-all-fields () + "Use of Weapons entry has all expected fields." + (let* ((books (spine-books)) + (uow (cl-find "8c1e-uow" books + :key (lambda (b) (plist-get b :id)) + :test #'equal))) + (should uow) + (should (equal (plist-get uow :title) "Use of Weapons")) + (should (equal (plist-get uow :status) "READING")) + (should (equal (plist-get uow :author) "Iain M. Banks")) + (should (equal (plist-get uow :isbn) "978-0316029193")) + (should (equal (plist-get uow :cover) "covers/use-of-weapons.jpg")) + (should (equal (plist-get uow :format) "audiobook")) + (should (equal (plist-get uow :rec_by) "Priya")) + (should (equal (plist-get uow :rec_note) "If you liked Player of Games, this one will wreck you")) + (should (equal (plist-get uow :tags) '("scifi" "literary"))) + (should (equal (plist-get uow :notes) + '(("2026-03-02" "The two-track structure is doing something I can't name yet.") + ("2026-03-07" "Zakalwe's competence reads as a wound.") + ("2026-03-09" "The chapter numbering. Oh.")))))) + +(ert-deftest spine-books-missing-properties-are-nil () + "Piranesi entry has nil for non-present properties." + (let* ((books (spine-books)) + (pir (cl-find "5e8d-pir" books + :key (lambda (b) (plist-get b :id)) + :test #'equal))) + (should pir) + (should (equal (plist-get pir :title) "Piranesi")) + (should (equal (plist-get pir :status) "WANT")) + (should (equal (plist-get pir :author) "Susanna Clarke")) + (should-not (plist-get pir :isbn)) + (should-not (plist-get pir :cover)) + (should-not (plist-get pir :format)) + (should-not (plist-get pir :rec_by)) + (should-not (plist-get pir :rec_note)) + (should-not (plist-get pir :notes)))) + +(ert-deftest spine-books-read-book-has-rating () + "Ancillary Justice has rating 5." + (let* ((books (spine-books)) + (aj (cl-find "3a1b-aj" books + :key (lambda (b) (plist-get b :id)) + :test #'equal))) + (should aj) + (should (equal (plist-get aj :status) "READ")) + (should (equal (plist-get aj :rating) "5")) + (should (= (length (plist-get aj :notes)) 3)))) + +(ert-deftest spine-books-empty-file-returns-empty-list () + "Empty Org file returns empty list, not nil." + (let* ((tmp-file (make-temp-file "spine-test-" nil ".org")) + (spine-org-file tmp-file)) + (unwind-protect + (let ((books (spine-books))) + (should (listp books)) + (should (= (length books) 0))) + (delete-file tmp-file)))) + +(ert-deftest spine-books-missing-file-returns-nil () + "Non-existent file returns nil." + (let ((spine-org-file "/tmp/spine-nonexistent-12345.org")) + (should-not (spine-books))))