etaf-playground/tests/research-shelf-cell-tests.el

279 lines
15 KiB
EmacsLisp

;;; research-shelf-cell-tests.el --- SQLite editing cell acceptance -*- lexical-binding: t; -*-
;;; Commentary:
;; Mount the real 256-record application through its public root. UI events
;; drive edits; independent SQLite reads prove persistence. Runtime internals
;; are inspected only to obtain the example-owned model for invariant checks.
;;; Code:
(require 'cl-lib)
(require 'ert)
(require 'etaf-research-shelf
(expand-file-name "../examples/research-shelf.el"
(file-name-directory
(or load-file-name
(bound-and-true-p byte-compile-current-file)
buffer-file-name))))
(defun research-cell-test--mount (buffer database-file)
"Mount a fresh Research App in BUFFER backed by DATABASE-FILE."
(let ((etaf-research-shelf-database-file database-file)
(etaf-research-shelf-fixture-size 256)
(etaf-research-shelf-page-size 12))
(etaf-mount
buffer
(etaf-research-shelf-root
(etaf-playground-read-static "research-shelf")
(etaf-playground-read-ecss "research-shelf"))
'(:viewport-width 1400 :viewport-height 80))
(etaf-runtime-for-buffer buffer)))
(defun research-cell-test--model (runtime)
"Return the sole reading list's application model in RUNTIME."
(let ((instances
(cl-remove-if-not
(lambda (instance)
(eq 'etaf-research-shelf-reading-list
(etaf--component-spec-name
(etaf--component-instance-spec instance))))
(hash-table-values (etaf-runtime-instances runtime)))))
(should (= (length instances) 1))
(plist-get (etaf--component-instance-state (car instances)) :model)))
(defun research-cell-test--control (runtime identity label)
"Find the control with LABEL in RUNTIME's row with IDENTITY."
(let* ((row (intern (format "research-shelf-row-%d" identity)))
(row-id (car (gethash row (etaf-runtime-host-ancestries
runtime (list row)))))
(entries
(cl-remove-if-not
(lambda (entry) (equal label (plist-get (cdr entry) :aria-label)))
(etaf-runtime-host-props-entries runtime)))
(ancestries (etaf-runtime-host-ancestries runtime (mapcar #'car entries))))
(should row-id)
(setq entries
(cl-remove-if-not
(lambda (entry) (memq row-id (gethash (car entry) ancestries)))
entries))
(should (= (length entries) 1))
(caar entries)))
(defun research-cell-test--press (runtime identity label)
"Press the control with LABEL in RUNTIME's row with IDENTITY."
(etaf-dispatch-event
runtime (research-cell-test--control runtime identity label) 'press))
(defun research-cell-test--row (database-file identity)
"Read IDENTITY from DATABASE-FILE through an independent SQLite source."
(let* ((etaf-research-shelf-database-file database-file)
(source (etaf-sqlite-source (etaf-research-shelf--database))))
(car (plist-get (etaf-data-source-load-page source (list :id identity) 1 1)
:items))))
(defun research-cell-test--text (runtime &optional ref)
"Return RUNTIME's displayed text, restricted to REF when supplied."
(with-current-buffer (etaf-runtime-buffer runtime)
(let ((bounds (and ref (etaf-host-ref-bounds runtime ref))))
(when ref (should bounds))
(buffer-substring-no-properties
(if bounds (car bounds) (point-min))
(if bounds (cdr bounds) (point-max))))))
(cl-defmacro research-cell-test--with-app ((runtime database-file) &rest body)
"Mount RUNTIME using a temporary DATABASE-FILE, then run BODY and clean up."
(declare (indent 1))
`(let ((,database-file (make-temp-file "research-cell-" nil ".sqlite")))
(unwind-protect
(with-temp-buffer
(let ((,runtime (research-cell-test--mount (current-buffer)
,database-file)))
(unwind-protect (progn ,@body)
(etaf-unmount ,runtime))))
(delete-file ,database-file))))
(ert-deftest research-shelf-cell-edits-persist-without-selecting-the-row ()
"Controlled cells share SQLite updates with detail actions, not selection."
(research-cell-test--with-app (runtime database-file)
(let* ((model (research-cell-test--model runtime))
(data (etaf-research-shelf--controller model)))
(should (= (etaf-value (etaf-data-total data)) 256))
(should (= (etaf-value (etaf-data-page-size data)) 12))
(should (= (length (etaf-value (etaf-data-items data))) 12))
(etaf-dispatch-event runtime 'research-shelf-row-1 'press)
(let ((star (research-cell-test--control runtime 2 "Star reading")))
(should (string-match-p "" (research-cell-test--text runtime star)))
(research-cell-test--press runtime 2 "Star reading")
(should (= (plist-get (research-cell-test--row database-file 2) :starred) 1))
(should (equal star (research-cell-test--control runtime 2 "Star reading")))
(should (string-match-p "" (research-cell-test--text runtime star)))
(research-cell-test--press runtime 2 "Star reading")
(should (= (plist-get (research-cell-test--row database-file 2) :starred) 0)))
(research-cell-test--press runtime 2 "Advance reading")
(should (= (plist-get (research-cell-test--row database-file 2) :progress) 10))
(should (equal (plist-get (research-cell-test--row database-file 2) :status)
"reading"))
(should (equal (etaf-value (etaf-data-selection data)) '(1)))
(should (equal (plist-get (etaf-data-selected-item data) :title)
"The Shape of Tools"))
;; A detail edit must refresh the same controlled cell's current props.
(etaf-dispatch-event runtime 'research-shelf-row-2 'press)
(etaf-dispatch-event runtime 'research-shelf-star 'press)
(should (string-match-p
"" (research-cell-test--text
runtime (research-cell-test--control runtime 2 "Star reading"))))
(etaf-dispatch-event runtime 'research-shelf-finish 'press)
(let ((advance (research-cell-test--control runtime 2 "Advance reading")))
(should (plist-get (etaf-runtime-host-props-for runtime advance) :disabled))
(should-error (etaf-dispatch-event runtime advance 'press)
:type 'etaf-event-error)
(should (= (plist-get (research-cell-test--row database-file 2) :progress) 100)))
(etaf-dispatch-event runtime 'research-shelf-row-1 'press)
(etaf-dispatch-event runtime 'research-shelf-archive 'press)
(should (plist-get
(etaf-runtime-host-props-for
runtime (research-cell-test--control runtime 1 "Advance reading"))
:disabled)))))
(ert-deftest research-shelf-edit-controls-have-distinct-column-owners ()
"Star and advance controls belong to separate stable Table columns."
(research-cell-test--with-app (runtime database-file)
(ignore database-file)
(let* ((entries (etaf-runtime-host-props-entries runtime))
(ancestries
(etaf-runtime-host-ancestries runtime (mapcar #'car entries))))
(dolist (expected '(("Star reading" . :starred)
("Advance reading" . :advance)))
(let* ((control (research-cell-test--control runtime 2 (car expected)))
(chain (gethash control ancestries))
(cell
(cl-find-if
(lambda (entry)
(and (member "etaf-table-cell"
(split-string (or (plist-get (cdr entry) :class) "")))
(memq (car (gethash (car entry) ancestries)) chain)))
entries)))
(should cell)
(should (eq (cdr expected) (plist-get (cdr cell) :key))))))))
(ert-deftest research-shelf-cells-follow-filter-page-and-theme ()
"Filtered removal and paging recreate cells with current SQLite values."
(research-cell-test--with-app (runtime database-file)
(let* ((data (etaf-research-shelf--controller
(research-cell-test--model runtime))))
(etaf-dispatch-event runtime 'research-shelf-filter-starred 'press)
(should (= (etaf-value (etaf-data-total data)) 54))
(let ((old-star (research-cell-test--control runtime 1 "Star reading")))
(research-cell-test--press runtime 1 "Star reading")
(should (= (etaf-value (etaf-data-total data)) 53))
(should-not (etaf-runtime-host-props-for runtime 'research-shelf-row-1))
(should-not (etaf-runtime-host-props-for runtime old-star)))
(etaf-dispatch-event runtime 'research-shelf-theme-toggle 'press)
(dolist (item (etaf-value (etaf-data-items data)))
(should (string-match-p
"" (research-cell-test--text
runtime (research-cell-test--control
runtime (plist-get item :id) "Star reading")))))
(etaf-dispatch-event runtime 'research-shelf-filter-all 'press)
(should (= (etaf-value (etaf-data-total data)) 256))
(should (string-match-p
"" (research-cell-test--text
runtime (research-cell-test--control runtime 1 "Star reading"))))
(etaf-dispatch-event runtime 'research-shelf-page-next 'press)
(should (equal (mapcar #'etaf-research-shelf--item-id
(etaf-value (etaf-data-items data)))
(number-sequence 13 24)))
(research-cell-test--press runtime 13 "Star reading")
(research-cell-test--press runtime 13 "Advance reading")
(should (= (plist-get (research-cell-test--row database-file 13) :progress) 52))
(etaf-dispatch-event runtime 'research-shelf-page-previous 'press)
(should (string-match-p
"" (research-cell-test--text
runtime (research-cell-test--control runtime 1 "Star reading"))))
(etaf-dispatch-event runtime 'research-shelf-page-next 'press)
(etaf-dispatch-event runtime 'research-shelf-theme-toggle 'press)
(should (string-match-p
"" (research-cell-test--text
runtime (research-cell-test--control runtime 13 "Star reading"))))
(research-cell-test--press runtime 13 "Advance reading")
(should (= (plist-get (research-cell-test--row database-file 13) :progress) 62)))))
(ert-deftest research-shelf-cell-sqlite-abort-reloads-the-controlled-value ()
"A real SQLite abort preserves the record; Reload restores editable cells."
(research-cell-test--with-app (runtime database-file)
(let ((before (research-cell-test--row database-file 2))
(connection (sqlite-open database-file)))
(unwind-protect
(progn
(sqlite-execute
connection
"CREATE TRIGGER reject_cell_edit BEFORE UPDATE ON reading_items
WHEN NEW.id = 2 BEGIN SELECT RAISE(ABORT, 'cell write rejected'); END")
(research-cell-test--press runtime 2 "Star reading")
(should (equal before (research-cell-test--row database-file 2)))
(should (string-match-p "Could not save" (research-cell-test--text runtime)))
(should (string-match-p "Use Reload" (research-cell-test--text runtime)))
(etaf-dispatch-event runtime 'research-shelf-reload 'press)
(should (string-match-p
"" (research-cell-test--text
runtime (research-cell-test--control runtime 2 "Star reading"))))
(research-cell-test--press runtime 2 "Advance reading")
(should (equal before (research-cell-test--row database-file 2)))
(should (string-match-p "Could not save" (research-cell-test--text runtime)))
(sqlite-execute connection "DROP TRIGGER reject_cell_edit")
(etaf-dispatch-event runtime 'research-shelf-reload 'press)
(research-cell-test--press runtime 2 "Star reading")
(research-cell-test--press runtime 2 "Advance reading")
(should (= (plist-get (research-cell-test--row database-file 2) :starred) 1))
(should (= (plist-get (research-cell-test--row database-file 2) :progress) 10))
(should (string-match-p
"" (research-cell-test--text
runtime (research-cell-test--control runtime 2 "Star reading")))))
(sqlite-close connection)))))
(ert-deftest research-shelf-cell-component-reuses-across-independent-apps ()
"One cell definition resolves each App's own Context, including remounts."
(research-cell-test--with-app (left left-file)
(research-cell-test--with-app (right right-file)
(let ((right-text (research-cell-test--text right))
(left-data (etaf-research-shelf--controller
(research-cell-test--model left)))
(right-data (etaf-research-shelf--controller
(research-cell-test--model right))))
(should-not (eq left-data right-data))
(dolist (data (list left-data right-data))
(should (= (etaf-value (etaf-data-total data)) 256))
(should (= (length (etaf-value (etaf-data-items data))) 12)))
(etaf-dispatch-event left 'research-shelf-row-1 'press)
(research-cell-test--press left 2 "Star reading")
(research-cell-test--press left 2 "Advance reading")
(should (equal right-text (research-cell-test--text right)))
(should (= (plist-get (research-cell-test--row right-file 2) :starred) 0))
(should (= (plist-get (research-cell-test--row right-file 2) :progress) 0))
(should (equal (etaf-value (etaf-data-selection left-data)) '(1)))
(should-not (etaf-value (etaf-data-selection right-data)))
(etaf-dispatch-event right 'research-shelf-theme-toggle 'press)
(research-cell-test--press right 1 "Star reading")
(should (= (plist-get (research-cell-test--row left-file 1) :starred) 1))
(should (= (plist-get (research-cell-test--row right-file 1) :starred) 0))
(let ((old-left left)
(buffer (etaf-runtime-buffer left)))
(etaf-unmount left)
(should-not (etaf-runtime-mounted-p old-left))
(should-not (etaf-runtime-for-buffer buffer))
(research-cell-test--press right 2 "Advance reading")
(should (= (plist-get (research-cell-test--row right-file 2) :progress) 10))
(setq left (research-cell-test--mount buffer left-file))
(should-not (eq left old-left))
(should (= (plist-get (research-cell-test--row left-file 2) :progress) 10))
(should (string-match-p
"" (research-cell-test--text
left (research-cell-test--control left 2 "Star reading"))))
(research-cell-test--press left 2 "Advance reading")
(should (= (plist-get (research-cell-test--row left-file 2) :progress) 20))
(should (= (plist-get (research-cell-test--row right-file 2) :progress) 10)))))))
(provide 'research-shelf-cell-tests)
;;; research-shelf-cell-tests.el ends here