refactor: redesign book detail page to match journal mockup styling
- Replace Pico CSS with index-style custom CSS variables - Redesign layout: back breadcrumb, info table, shelf/status selects - Update spine-book-model: add shelf_name, format_icon/label, tags_display, date display fields, rating display, status/shelf options - Remove unused fields: meta, progress_label, formats array - Update spine-book-model-test.el: 23 tests cover all new fields - Add --text-danger CSS variable for remove button
This commit is contained in:
+117
-42
@@ -15,19 +15,29 @@
|
||||
(cl-find "8c1e-uow" (spine-books)
|
||||
:key (lambda (b) (plist-get b :id))
|
||||
:test #'equal)
|
||||
"Use of Weapons fixture.")
|
||||
"Use of Weapons fixture (READING, audiobook, tags=scifi+literary, rec_by=Priya).")
|
||||
|
||||
(defconst spine-book-model-test--aj
|
||||
(cl-find "3a1b-aj" (spine-books)
|
||||
:key (lambda (b) (plist-get b :id))
|
||||
:test #'equal)
|
||||
"Ancillary Justice fixture.")
|
||||
"Ancillary Justice fixture (READ, ebook, rating=5, tags=scifi).")
|
||||
|
||||
(defconst spine-book-model-test--pir
|
||||
(cl-find "5e8d-pir" (spine-books)
|
||||
:key (lambda (b) (plist-get b :id))
|
||||
:test #'equal)
|
||||
"Piranesi fixture.")
|
||||
"Piranesi fixture (WANT, no format, no tags, no rec).")
|
||||
|
||||
(defconst spine-book-model-test--tcm
|
||||
(let* ((books (spine-books))
|
||||
(b (cl-find "b2c1-tcm" books
|
||||
:key (lambda (x) (plist-get x :id))
|
||||
:test #'equal)))
|
||||
b)
|
||||
"The Checklist Manifesto fixture (no TODO, no format, no rec).")
|
||||
|
||||
;; --- Title & status ----------------------------------------------------
|
||||
|
||||
(ert-deftest spine-book-model-title-strips-todo ()
|
||||
"Title has TODO prefix stripped."
|
||||
@@ -35,47 +45,123 @@
|
||||
(should (equal (ht-get model "title") "Use of Weapons"))))
|
||||
|
||||
(ert-deftest spine-book-model-status-reading ()
|
||||
"READING book has status_class reading."
|
||||
"READING status_class and label."
|
||||
(let ((model (spine-book-model spine-book-model-test--uow)))
|
||||
(should (equal (ht-get model "status_class") "reading"))
|
||||
(should (equal (ht-get model "status_label") "Reading"))))
|
||||
|
||||
(ert-deftest spine-book-model-status-read ()
|
||||
"READ book has status_class read."
|
||||
"READ status_class and label."
|
||||
(let ((model (spine-book-model spine-book-model-test--aj)))
|
||||
(should (equal (ht-get model "status_class") "read"))
|
||||
(should (equal (ht-get model "status_label") "Read"))))
|
||||
|
||||
(ert-deftest spine-book-model-status-want ()
|
||||
"WANT book has status_class want."
|
||||
"WANT status_class and label."
|
||||
(let ((model (spine-book-model spine-book-model-test--pir)))
|
||||
(should (equal (ht-get model "status_class") "want"))
|
||||
(should (equal (ht-get model "status_label") "Want"))))
|
||||
|
||||
(ert-deftest spine-book-model-no-todo-prefix ()
|
||||
"No TODO prefix shows title as-is and empty status."
|
||||
(let ((model (spine-book-model spine-book-model-test--tcm)))
|
||||
(should (equal (ht-get model "title") "The Checklist Manifesto"))
|
||||
(should (equal (ht-get model "status_class") ""))
|
||||
(should (equal (ht-get model "status_label") ""))))
|
||||
|
||||
;; --- Metadata fields ----------------------------------------------------
|
||||
|
||||
(ert-deftest spine-book-model-author ()
|
||||
"Author is passed through."
|
||||
"Author passed through."
|
||||
(let ((model (spine-book-model spine-book-model-test--uow)))
|
||||
(should (equal (ht-get model "author") "Iain M. Banks"))))
|
||||
|
||||
(ert-deftest spine-book-model-meta-with-format-and-date ()
|
||||
"Meta includes format and date when both present."
|
||||
(ert-deftest spine-book-model-shelf-name ()
|
||||
"Shelf_name passed through."
|
||||
(let ((model (spine-book-model spine-book-model-test--uow)))
|
||||
(should (string-match "started.*audiobook" (ht-get model "meta")))))
|
||||
(should (equal (ht-get model "shelf_name") "Fiction"))))
|
||||
|
||||
(ert-deftest spine-book-model-format-active ()
|
||||
"Format list has correct active flag."
|
||||
(ert-deftest spine-book-model-format-fields ()
|
||||
"Format icon and label match the book's format."
|
||||
(let ((model (spine-book-model spine-book-model-test--uow)))
|
||||
(should (equal (ht-get model "format_icon") "ti-headphones"))
|
||||
(should (equal (ht-get model "format_label") "Audiobook"))))
|
||||
|
||||
(ert-deftest spine-book-model-format-none ()
|
||||
"No format yields empty label and default icon."
|
||||
(let ((model (spine-book-model spine-book-model-test--pir)))
|
||||
(should (equal (ht-get model "format_icon") "ti-book"))
|
||||
(should (equal (ht-get model "format_label") ""))))
|
||||
|
||||
(ert-deftest spine-book-model-tags-display ()
|
||||
"Tags rendered as HTML badge spans."
|
||||
(let ((model (spine-book-model spine-book-model-test--uow)))
|
||||
(let ((tags (ht-get model "tags_display")))
|
||||
(should (string-match "scifi" tags))
|
||||
(should (string-match "literary" tags)))))
|
||||
|
||||
(ert-deftest spine-book-model-no-tags ()
|
||||
"No tags yields empty string."
|
||||
(let ((model (spine-book-model spine-book-model-test--tcm)))
|
||||
(should (equal (ht-get model "tags_display") ""))))
|
||||
|
||||
(ert-deftest spine-book-model-started-display ()
|
||||
"started_display from ADDED property."
|
||||
(let ((model (spine-book-model spine-book-model-test--uow)))
|
||||
(should (equal (ht-get model "started_display") "20 Feb 2026"))))
|
||||
|
||||
(ert-deftest spine-book-model-added-display ()
|
||||
"added_display from ADDED property."
|
||||
(let ((model (spine-book-model spine-book-model-test--uow)))
|
||||
(should (equal (ht-get model "added_display") "20 Feb 2026"))))
|
||||
|
||||
(ert-deftest spine-book-model-finished-default ()
|
||||
"finished_display defaults to em-dash."
|
||||
(let ((model (spine-book-model spine-book-model-test--uow)))
|
||||
(should (equal (ht-get model "finished_display") "—"))))
|
||||
|
||||
(ert-deftest spine-book-model-rating-display ()
|
||||
"Rating produces star characters."
|
||||
(let ((model (spine-book-model spine-book-model-test--aj)))
|
||||
(should (equal (ht-get model "rating_display") "★★★★★"))
|
||||
(should (ht-get model "has_rating"))))
|
||||
|
||||
(ert-deftest spine-book-model-rating-none ()
|
||||
"No rating shows placeholder text."
|
||||
(let ((model (spine-book-model spine-book-model-test--uow)))
|
||||
(should (equal (ht-get model "rating_display") "rate when finished"))
|
||||
(should-not (ht-get model "has_rating"))))
|
||||
|
||||
;; --- Format/status/shelf options ---------------------------------------
|
||||
|
||||
(ert-deftest spine-book-model-status-options ()
|
||||
"status_options includes all statuses with one selected."
|
||||
(let* ((model (spine-book-model spine-book-model-test--uow))
|
||||
(formats (ht-get model "formats")))
|
||||
(should (= (length formats) 3))
|
||||
(should (equal (ht-get (nth 0 formats) "label") "Hardcover"))
|
||||
(should-not (ht-get (nth 0 formats) "active"))
|
||||
(should (equal (ht-get (nth 1 formats) "label") "eBook"))
|
||||
(should-not (ht-get (nth 1 formats) "active"))
|
||||
(should (equal (ht-get (nth 2 formats) "label") "Audiobook"))
|
||||
(should (ht-get (nth 2 formats) "active"))))
|
||||
(opts (ht-get model "status_options")))
|
||||
(should (= (length opts) 3))
|
||||
(should (equal (ht-get (nth 0 opts) "name") "Want"))
|
||||
(should-not (ht-get (nth 0 opts) "selected"))
|
||||
(should (equal (ht-get (nth 1 opts) "name") "Reading"))
|
||||
(should (ht-get (nth 1 opts) "selected"))
|
||||
(should (equal (ht-get (nth 2 opts) "name") "Read"))
|
||||
(should-not (ht-get (nth 2 opts) "selected"))))
|
||||
|
||||
(ert-deftest spine-book-model-shelf-options ()
|
||||
"shelf_options lists all shelves with current one selected."
|
||||
(let* ((model (spine-book-model spine-book-model-test--uow))
|
||||
(opts (ht-get model "shelf_options")))
|
||||
(should (>= (length opts) 3))
|
||||
(let ((fiction (cl-find "Fiction" opts :key (lambda (o) (ht-get o "name")) :test #'equal)))
|
||||
(should fiction)
|
||||
(should (ht-get fiction "selected")))
|
||||
(let ((scifi (cl-find "Science Fiction" opts :key (lambda (o) (ht-get o "name")) :test #'equal)))
|
||||
(should scifi)
|
||||
(should-not (ht-get scifi "selected")))))
|
||||
|
||||
;; --- Notes -------------------------------------------------------------
|
||||
|
||||
(ert-deftest spine-book-model-notes ()
|
||||
"Notes are mapped correctly."
|
||||
"Notes mapped with date and text."
|
||||
(let* ((model (spine-book-model spine-book-model-test--uow))
|
||||
(notes (ht-get model "notes")))
|
||||
(should (= (length notes) 3))
|
||||
@@ -83,6 +169,15 @@
|
||||
(should (equal (ht-get (nth 0 notes) "text")
|
||||
"The two-track structure is doing something I can't name yet."))))
|
||||
|
||||
(ert-deftest spine-book-model-notes-empty ()
|
||||
"No notes yields empty array."
|
||||
(let* ((model (spine-book-model spine-book-model-test--tcm))
|
||||
(notes (ht-get model "notes")))
|
||||
(should (listp notes))
|
||||
(should (= (length notes) 0))))
|
||||
|
||||
;; --- Recommendation ----------------------------------------------------
|
||||
|
||||
(ert-deftest spine-book-model-recommendation ()
|
||||
"Recommendation includes initials, by, and note."
|
||||
(let* ((model (spine-book-model spine-book-model-test--uow))
|
||||
@@ -97,27 +192,7 @@
|
||||
(let ((model (spine-book-model spine-book-model-test--aj)))
|
||||
(should-not (ht-get model "recommendation"))))
|
||||
|
||||
(ert-deftest spine-book-model-no-todo-prefix ()
|
||||
"Book with no TODO prefix shows title as-is and empty status."
|
||||
(let* ((books (spine-books))
|
||||
(tcm (cl-find "b2c1-tcm" books
|
||||
:key (lambda (b) (plist-get b :id))
|
||||
:test #'equal)))
|
||||
(should tcm)
|
||||
(let ((model (spine-book-model tcm)))
|
||||
(should (equal (ht-get model "title") "The Checklist Manifesto"))
|
||||
(should (equal (ht-get model "status_class") ""))
|
||||
(should (equal (ht-get model "status_label") "")))))
|
||||
|
||||
(ert-deftest spine-book-model-has-date-no-format ()
|
||||
"Has date but no format → meta shows started date only."
|
||||
(let* ((books (spine-books))
|
||||
(tcm (cl-find "b2c1-tcm" books
|
||||
:key (lambda (b) (plist-get b :id))
|
||||
:test #'equal)))
|
||||
(should tcm)
|
||||
(let ((model (spine-book-model tcm)))
|
||||
(should (equal (ht-get model "meta") "started 15 Jun")))))
|
||||
;; --- Navigation --------------------------------------------------------
|
||||
|
||||
(ert-deftest spine-book-model-back-url ()
|
||||
"back_url links to index with this book's id."
|
||||
|
||||
Reference in New Issue
Block a user