;;; spine-edit-test.el — ERT tests for spine-edit functions (require 'ert) (require 'cl-lib) (setq spine-skip-server-start t) (setq spine-org-file (expand-file-name "test-edit-temp.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))) (defun spine-edit-test--setup () "Create a test Org file with one shelf and one book." (with-temp-file spine-org-file (insert "* Fiction\n** WANT Edit Test Book\n:PROPERTIES:\n:AUTHOR: Test Author\n:ID: test-0001\n:END:\n")) t) (defun spine-edit-test--cleanup () "Remove the test Org file." (ignore-errors (delete-file spine-org-file))) (ert-deftest spine-add-note-appends () "spine-add-note appends a note with date and text." (spine-edit-test--cleanup) (unwind-protect (progn (spine-edit-test--setup) (let* ((books (spine-books)) (book (car books)) (id (plist-get book :id))) (spine-add-note id "Great book!" "2026-06-21") (let ((updated (cl-find id (spine-books) :key (lambda (b) (plist-get b :id)) :test #'equal))) (should updated) (let ((notes (plist-get updated :notes))) (should notes) (should (equal (caar notes) "2026-06-21")) (should (equal (cadar notes) "Great book!")))))) (spine-edit-test--cleanup))) (ert-deftest spine-add-note-default-date () "spine-add-note uses today's date when none provided." (spine-edit-test--cleanup) (unwind-protect (progn (spine-edit-test--setup) (let* ((books (spine-books)) (book (car books)) (id (plist-get book :id)) (today (format-time-string "%Y-%m-%d"))) (spine-add-note id "Note without date") (let ((updated (cl-find id (spine-books) :key (lambda (b) (plist-get b :id)) :test #'equal))) (should updated) (let ((notes (plist-get updated :notes))) (should notes) (should (equal (caar notes) today)) (should (equal (cadar notes) "Note without date")))))) (spine-edit-test--cleanup))) (ert-deftest spine-set-rating-sets-rating () "spine-set-rating sets the RATING property." (spine-edit-test--cleanup) (unwind-protect (progn (spine-edit-test--setup) (let* ((books (spine-books)) (book (car books)) (id (plist-get book :id))) (spine-set-rating id "4") (let ((updated (cl-find id (spine-books) :key (lambda (b) (plist-get b :id)) :test #'equal))) (should updated) (should (equal (plist-get updated :rating) "4"))))) (spine-edit-test--cleanup))) (ert-deftest spine-edit-model-actions-include-note-and-rating () "Selected book shows Add note and Set rating actions." (spine-edit-test--cleanup) (unwind-protect (progn (spine-edit-test--setup) (let* ((books (spine-books)) (book (car books)) (id (plist-get book :id)) (model (spine-index-model books nil id)) (minibuffer (ht-get model "minibuffer"))) (should minibuffer) (let ((actions (ht-get minibuffer "actions"))) (should (= (length actions) 2)) (should (equal (ht-get (nth 0 actions) "label") "Add note")) (should (equal (ht-get (nth 1 actions) "label") "Set rating"))))) (spine-edit-test--cleanup))) (ert-deftest spine-edit-model-no-actions-when-no-selection () "No minibuffer actions when no book is selected." (spine-edit-test--cleanup) (unwind-protect (progn (spine-edit-test--setup) (let* ((books (spine-books)) (model (spine-index-model books nil nil))) (should-not (ht-get model "minibuffer")))) (spine-edit-test--cleanup))) (defun spine-edit-test--two-shelf-setup () "Create a test Org file with two shelves and a book on the first." (with-temp-file spine-org-file (insert "* Fiction\n** WANT Edit Test Book\n:PROPERTIES:\n:AUTHOR: Test Author\n:ID: test-0001\n:END:\n* Science Fiction\n")) t) (ert-deftest spine-move-book-moves-to-target-shelf () "spine-move-book moves the book to the target shelf." (spine-edit-test--cleanup) (unwind-protect (progn (spine-edit-test--two-shelf-setup) (spine-move-book "test-0001" "Science Fiction") (let* ((books (spine-books)) (book (cl-find "test-0001" books :key (lambda (b) (plist-get b :id)) :test #'equal))) (should book) (should (equal (plist-get book :shelf) "Science Fiction")))) (spine-edit-test--cleanup))) (ert-deftest spine-move-book-keeps-properties () "spine-move-book preserves book properties after move." (spine-edit-test--cleanup) (unwind-protect (progn (spine-edit-test--two-shelf-setup) (spine-move-book "test-0001" "Science Fiction") (let* ((books (spine-books)) (book (cl-find "test-0001" books :key (lambda (b) (plist-get b :id)) :test #'equal))) (should book) (should (equal (plist-get book :title) "WANT Edit Test Book")) (should (equal (plist-get book :author) "Test Author")))) (spine-edit-test--cleanup))) (ert-deftest spine-move-book-errors-on-missing-book () "spine-move-book errors when book not found." (spine-edit-test--cleanup) (unwind-protect (progn (spine-edit-test--two-shelf-setup) (should-error (spine-move-book "nonexistent" "Science Fiction"))) (spine-edit-test--cleanup))) (ert-deftest spine-move-book-errors-on-missing-shelf () "spine-move-book errors when target shelf not found." (spine-edit-test--cleanup) (unwind-protect (progn (spine-edit-test--two-shelf-setup) (should-error (spine-move-book "test-0001" "Nonexistent Shelf"))) (spine-edit-test--cleanup))) (ert-deftest spine-set-status-changes-todo () "spine-set-status changes the TODO keyword." (spine-edit-test--cleanup) (unwind-protect (progn (spine-edit-test--setup) (spine-set-status "test-0001" "reading") (let* ((books (spine-books)) (book (cl-find "test-0001" books :key (lambda (b) (plist-get b :id)) :test #'equal))) (should book) (should (equal (plist-get book :title) "READING Edit Test Book")))) (spine-edit-test--cleanup))) (ert-deftest spine-set-status-clears-todo () "spine-set-status with empty string removes TODO keyword." (spine-edit-test--cleanup) (unwind-protect (progn (spine-edit-test--setup) (spine-set-status "test-0001" "") (let* ((books (spine-books)) (book (cl-find "test-0001" books :key (lambda (b) (plist-get b :id)) :test #'equal))) (should book) (should (equal (plist-get book :title) "Edit Test Book")))) (spine-edit-test--cleanup))) (ert-deftest spine-set-status-errors-on-missing-book () "spine-set-status errors when book not found." (spine-edit-test--cleanup) (unwind-protect (progn (spine-edit-test--setup) (should-error (spine-set-status "nonexistent" "reading"))) (spine-edit-test--cleanup)))