529 lines
28 KiB
EmacsLisp
529 lines
28 KiB
EmacsLisp
;;; task-workbench-tests.el --- Workbench acceptance preflight -*- lexical-binding: t; -*-
|
||
|
||
;;; Commentary:
|
||
|
||
;; Exercise the complete example through mounted public event entry points.
|
||
;; Minibuffer readers are stubbed only in this batch preflight. Real input,
|
||
;; redisplay, layout, and screenshots belong to task-workbench-gui-scenarios.
|
||
|
||
;;; Code:
|
||
|
||
(require 'cl-lib)
|
||
(require 'ert)
|
||
(require 'task-workbench
|
||
(expand-file-name "../examples/task-workbench.el"
|
||
(file-name-directory
|
||
(or load-file-name
|
||
(bound-and-true-p byte-compile-current-file)
|
||
buffer-file-name))))
|
||
|
||
(defun wb-test--instance (runtime name)
|
||
"Return RUNTIME's sole component instance named NAME."
|
||
(let ((instances
|
||
(cl-remove-if-not
|
||
(lambda (instance)
|
||
(eq name (etaf--component-spec-name
|
||
(etaf--component-instance-spec instance))))
|
||
(hash-table-values (etaf-runtime-instances runtime)))))
|
||
(should (= (length instances) 1))
|
||
(car instances)))
|
||
|
||
(defun wb-test--data (runtime)
|
||
"Read RUNTIME's example-owned controller for result assertions."
|
||
(plist-get (etaf--component-instance-state
|
||
(wb-test--instance runtime 'wb-app)) :data))
|
||
|
||
(defun wb-test--text (runtime)
|
||
"Return RUNTIME's rendered plain text."
|
||
(with-current-buffer (etaf-runtime-buffer runtime)
|
||
(buffer-substring-no-properties (point-min) (point-max))))
|
||
|
||
(defun wb-test--row (runtime identity)
|
||
"Return the semantic row reference for IDENTITY in RUNTIME."
|
||
(let ((entry
|
||
(cl-find-if
|
||
(lambda (entry)
|
||
(and (equal (plist-get (cdr entry) :key) identity)
|
||
(member "etaf-table-row"
|
||
(split-string (or (plist-get (cdr entry) :class) "")))))
|
||
(etaf-runtime-host-props-entries runtime))))
|
||
(should entry)
|
||
(car entry)))
|
||
|
||
(defun wb-test--control (runtime label &optional parent)
|
||
"Return RUNTIME control with aria LABEL, optionally inside PARENT."
|
||
(let* ((entries
|
||
(cl-remove-if-not
|
||
(lambda (entry) (equal (plist-get (cdr entry) :aria-label) label))
|
||
(etaf-runtime-host-props-entries runtime)))
|
||
(ancestries (and parent
|
||
(etaf-runtime-host-ancestries runtime
|
||
(mapcar #'car entries))))
|
||
(parent-id (and parent
|
||
(car (gethash parent
|
||
(etaf-runtime-host-ancestries
|
||
runtime (list parent)))))))
|
||
(when parent
|
||
(setq entries
|
||
(cl-remove-if-not
|
||
(lambda (entry) (memq parent-id (gethash (car entry) ancestries)))
|
||
entries)))
|
||
(should (= (length entries) 1))
|
||
(caar entries)))
|
||
|
||
(defun wb-test--press (runtime label &optional parent)
|
||
"Press RUNTIME control with LABEL, optionally inside PARENT."
|
||
(etaf-dispatch-event runtime (wb-test--control runtime label parent) 'press))
|
||
|
||
(defun wb-test--ids (data)
|
||
"Return the currently loaded identities from DATA."
|
||
(mapcar #'wb-task-id (etaf-value (etaf-data-items data))))
|
||
|
||
(defun wb-test--layout-bounds (runtime)
|
||
"Snapshot public bounds across RUNTIME's toolbar, panels, and ten rows."
|
||
(mapcar
|
||
(lambda (entry)
|
||
(let ((bounds (etaf-host-ref-bounds runtime (cdr entry))))
|
||
(should bounds)
|
||
(cons (car entry) (copy-tree bounds))))
|
||
(append
|
||
(mapcar (lambda (label) (cons label (wb-test--control runtime label)))
|
||
'("新增任务" "深色" "定位新增按钮" "全部" "未完成" "已完成"
|
||
"搜索" "每页条数 10 ✎" "显示说明" "刷新 / 重试" "模拟失败"))
|
||
(mapcar (lambda (identity) (cons identity (wb-test--row runtime identity)))
|
||
(number-sequence 1 10)))))
|
||
|
||
(defun wb-test--line-widths (runtime)
|
||
"Return measured widths of RUNTIME's rendered lines, including display props."
|
||
(with-current-buffer (etaf-runtime-buffer runtime)
|
||
(mapcar #'ebox-string-pixel-width (split-string (buffer-string) "\n" nil))))
|
||
|
||
(defun wb-test--desktop-header-inline-p (runtime)
|
||
"Return non-nil when RUNTIME's desktop title and toolbar share a line."
|
||
(with-current-buffer (etaf-runtime-buffer runtime)
|
||
(save-excursion
|
||
(goto-char (point-min))
|
||
(unless (search-forward "任务工作台" nil t) (error "Workbench title is absent"))
|
||
(= (line-number-at-pos)
|
||
(line-number-at-pos
|
||
(etaf-host-ref-position runtime (wb-test--control runtime "新增任务")))))))
|
||
|
||
(defun wb-test--card-layout (runtime control-label)
|
||
"Return public bounds and line geometry for CONTROL-LABEL's card in RUNTIME."
|
||
(let* ((control (wb-test--control runtime control-label))
|
||
(panels
|
||
(cl-remove-if-not
|
||
(lambda (entry)
|
||
(let ((classes (plist-get (cdr entry) :class)))
|
||
(member "etaf-panel"
|
||
(if (stringp classes) (split-string classes) classes))))
|
||
(etaf-runtime-host-props-entries runtime)))
|
||
(ancestries (etaf-runtime-host-ancestries
|
||
runtime (cons control (mapcar #'car panels))))
|
||
(matches
|
||
(cl-remove-if-not
|
||
(lambda (entry)
|
||
(memq (car (gethash (car entry) ancestries))
|
||
(gethash control ancestries)))
|
||
panels)))
|
||
(should (= (length matches) 1))
|
||
(let* ((host (caar matches))
|
||
(bounds (etaf-host-ref-bounds runtime host)))
|
||
(should bounds)
|
||
(should (< (car bounds) (cdr bounds)))
|
||
(with-current-buffer (etaf-runtime-buffer runtime)
|
||
(let ((top (line-number-at-pos (car bounds)))
|
||
(bottom (line-number-at-pos (1- (cdr bounds)))))
|
||
(list :host host :bounds (copy-tree bounds)
|
||
:top top :bottom bottom :height (1+ (- bottom top))
|
||
:left
|
||
(save-excursion
|
||
(goto-char (car bounds))
|
||
(ebox-string-pixel-width
|
||
(buffer-substring (line-beginning-position) (point))))))))))
|
||
|
||
(cl-defmacro wb-test--with-app ((runtime data &optional viewport-width) &rest body)
|
||
"Mount RUNTIME and DATA at optional VIEWPORT-WIDTH, then execute BODY."
|
||
(declare (indent 1))
|
||
`(with-temp-buffer
|
||
(let (,runtime ,data)
|
||
(unwind-protect
|
||
(progn
|
||
(etaf-mount (current-buffer) (etaf-view (wb-app))
|
||
(list :viewport-width (or ,viewport-width 1600)
|
||
:viewport-height 80))
|
||
(setq ,runtime (etaf-runtime-for-buffer (current-buffer))
|
||
,data (wb-test--data ,runtime))
|
||
,@body)
|
||
(when-let* ((mounted (etaf-runtime-for-buffer (current-buffer))))
|
||
(etaf-unmount mounted))))))
|
||
|
||
(ert-deftest wb-workbench-mounts-composed-app ()
|
||
"Slots, custom cells, resource, and summary all survive a full mount."
|
||
(wb-test--with-app (runtime data)
|
||
(should (etaf-runtime-mounted-p runtime))
|
||
(should (equal (wb-test--ids data) (number-sequence 1 10)))
|
||
(should (= (etaf-value (etaf-data-total data)) 100))
|
||
(should (= (etaf-value (etaf-data-page-size data)) 10))
|
||
(dolist (label '("任务工作台" "筛选" "设计组件接口" "检查状态隔离"
|
||
"点击任务行查看详情" "服务正常" "共 100 条 · 当前页 10 条"))
|
||
(should (string-match-p (regexp-quote label) (wb-test--text runtime))))
|
||
(dolist (identity (number-sequence 1 10))
|
||
(let ((row (wb-test--row runtime identity)))
|
||
(should (wb-test--control runtime "完成" row))
|
||
(should (wb-test--control runtime "删除" row))))
|
||
(should (plist-get (etaf-runtime-host-props-for
|
||
runtime (wb-test--control runtime "Previous page"))
|
||
:disabled))))
|
||
|
||
(ert-deftest wb-workbench-filter-search-and-pagination ()
|
||
"Filter and search reset paging; both pager directions update rows."
|
||
(wb-test--with-app (runtime data)
|
||
(wb-test--press runtime "Next page")
|
||
(should (= (etaf-value (etaf-data-page data)) 2))
|
||
(should (equal (wb-test--ids data) (number-sequence 11 20)))
|
||
(wb-test--press runtime "Previous page")
|
||
(should (equal (wb-test--ids data) (number-sequence 1 10)))
|
||
(wb-test--press runtime "未完成")
|
||
(should (equal (wb-test--ids data) '(1 2 4 6 7 9 10 11 13 14)))
|
||
(should (= (etaf-value (etaf-data-total data)) 74))
|
||
(wb-test--press runtime "已完成")
|
||
(should (equal (wb-test--ids data) '(3 5 8 12 16 20 24 28 32 36)))
|
||
(should (= (etaf-value (etaf-data-total data)) 26))
|
||
(cl-letf (((symbol-function 'read-string) (lambda (&rest _) "键盘")))
|
||
(wb-test--press runtime "搜索"))
|
||
(should (equal (wb-test--ids data) '(4)))
|
||
(should (= (etaf-value (etaf-data-page data)) 1))
|
||
(cl-letf (((symbol-function 'read-string) (lambda (&rest _) "不存在的任务")))
|
||
(wb-test--press runtime "搜索"))
|
||
(should-not (wb-test--ids data))
|
||
(should (string-match-p "没有匹配的任务" (wb-test--text runtime)))
|
||
(wb-test--press runtime "全部")
|
||
(should (equal (wb-test--ids data) (number-sequence 1 10)))
|
||
(should (= (etaf-value (etaf-data-total data)) 100))))
|
||
|
||
(ert-deftest wb-workbench-page-size-validates-and-reloads ()
|
||
"The NumberInput's public press callback applies valid input only."
|
||
(wb-test--with-app (runtime data)
|
||
(wb-test--press runtime "Next page")
|
||
(cl-letf (((symbol-function 'read-number) (lambda (&rest _) 2)))
|
||
(wb-test--press runtime "每页条数 10 ✎"))
|
||
(should (= (etaf-value (etaf-data-page-size data)) 2))
|
||
(should (= (etaf-value (etaf-data-page data)) 1))
|
||
(should (equal (wb-test--ids data) '(1 2)))
|
||
(should (string-match-p "Page 1 / 50" (wb-test--text runtime)))
|
||
(cl-letf (((symbol-function 'read-number) (lambda (&rest _) 0)))
|
||
(should-error (wb-test--press runtime "每页条数 2 ✎") :type 'user-error))
|
||
(should (= (etaf-value (etaf-data-page-size data)) 2))
|
||
(should (equal (wb-test--ids data) '(1 2)))))
|
||
|
||
(ert-deftest wb-workbench-cell-toggle-delete-and-detail ()
|
||
"Cell actions retain the row and dynamic detail, and respect disabled."
|
||
(let ((observed 0))
|
||
(cl-letf (((symbol-function 'wb-trace-press) (lambda () (cl-incf observed))))
|
||
(wb-test--with-app (runtime data)
|
||
(let* ((row (wb-test--row runtime 1))
|
||
(remove-ref (wb-test--control runtime "删除" row)))
|
||
(should-error (etaf-dispatch-event runtime remove-ref 'press)
|
||
:type 'etaf-event-error)
|
||
(should-error (etaf-focus runtime remove-ref) :type 'etaf-event-error)
|
||
(should-not (etaf-data-selected-item data))
|
||
(etaf-dispatch-event runtime row 'press)
|
||
(should (= (wb-task-id (etaf-data-selected-item data)) 1))
|
||
(should (string-match-p "进行中" (wb-test--text runtime)))
|
||
(let ((detail (wb-test--instance runtime 'wb-task-info)))
|
||
(wb-test--press runtime "完成" row)
|
||
(should (= observed 1))
|
||
(should (plist-get (car (etaf-value (etaf-data-items data))) :done))
|
||
(should (equal (plist-get (car (etaf-value (etaf-data-items data))) :title)
|
||
"设计组件接口"))
|
||
(should (eq detail (wb-test--instance runtime 'wb-task-info)))
|
||
(should (equal row (wb-test--row runtime 1)))
|
||
(should (equal remove-ref (wb-test--control runtime "删除" row)))
|
||
(should-not (plist-get (etaf-runtime-host-props-for runtime remove-ref)
|
||
:disabled))
|
||
(etaf-dispatch-event runtime remove-ref 'press)
|
||
(should (= observed 2))
|
||
(should (= (etaf-value (etaf-data-total data)) 99))
|
||
(should (equal (wb-test--ids data) (number-sequence 2 11)))
|
||
(should-not (etaf-data-selected-item data))
|
||
(should-not (etaf-effect-scope-active-p
|
||
(etaf--component-instance-scope detail)))
|
||
(should (string-match-p "点击任务行查看详情" (wb-test--text runtime)))))))))
|
||
|
||
(ert-deftest wb-workbench-add-trims-and-ignores-empty-input ()
|
||
"Add creates a complete row once and leaves whitespace-only input alone."
|
||
(wb-test--with-app (runtime data)
|
||
(cl-letf (((symbol-function 'read-string) (lambda (&rest _) " 新增验收任务 ")))
|
||
(wb-test--press runtime "新增任务"))
|
||
(should (= (etaf-value (etaf-data-total data)) 101))
|
||
(cl-letf (((symbol-function 'read-string) (lambda (&rest _) " ")))
|
||
(wb-test--press runtime "新增任务"))
|
||
(should (= (etaf-value (etaf-data-total data)) 101))
|
||
(dotimes (_ 10) (wb-test--press runtime "Next page"))
|
||
(should (= (etaf-value (etaf-data-page data)) 11))
|
||
(should (equal (wb-test--ids data) '(101)))
|
||
(should (equal (car (last (etaf-value (etaf-data-items data))))
|
||
'(:id 101 :title "新增验收任务" :done nil)))
|
||
(should (plist-get (etaf-runtime-host-props-for
|
||
runtime (wb-test--control runtime "Next page")) :disabled))))
|
||
|
||
(ert-deftest wb-workbench-last-page-and-high-id-deletion ()
|
||
"Task 100 is selectable and deletable, with a correct shorter last page."
|
||
(wb-test--with-app (runtime data)
|
||
(dotimes (_ 9) (wb-test--press runtime "Next page"))
|
||
(should (= (etaf-value (etaf-data-page data)) 10))
|
||
(should (equal (wb-test--ids data) (number-sequence 91 100)))
|
||
(should (plist-get (etaf-runtime-host-props-for
|
||
runtime (wb-test--control runtime "Next page")) :disabled))
|
||
(let ((row (wb-test--row runtime 100)))
|
||
(etaf-dispatch-event runtime row 'press)
|
||
(should (= (wb-task-id (etaf-data-selected-item data)) 100))
|
||
(should (plist-get (etaf-data-selected-item data) :done))
|
||
(wb-test--press runtime "删除" row)
|
||
(should (= (etaf-value (etaf-data-total data)) 99))
|
||
(should (= (etaf-value (etaf-data-page data)) 1))
|
||
(should (equal (wb-test--ids data) (number-sequence 1 10)))
|
||
(should-not (etaf-data-selected-item data))
|
||
(should-error (etaf-dispatch-event runtime row 'press) :type 'etaf-event-error))
|
||
(dotimes (_ 9) (wb-test--press runtime "Next page"))
|
||
(should (equal (wb-test--ids data) (number-sequence 91 99)))
|
||
(should (string-match-p "91–99 of 99" (wb-test--text runtime)))))
|
||
|
||
(ert-deftest wb-workbench-theme-focus-and-conditional-slot-content ()
|
||
"Theme changes paint without moving layout; local focus and details work."
|
||
(wb-test--with-app (runtime data)
|
||
(ignore data)
|
||
(let ((before (with-current-buffer (etaf-runtime-buffer runtime)
|
||
(buffer-string)))
|
||
(layout (wb-test--layout-bounds runtime))
|
||
(widths (wb-test--line-widths runtime))
|
||
(add (wb-test--control runtime "新增任务")))
|
||
(should (wb-test--desktop-header-inline-p runtime))
|
||
(wb-test--press runtime "深色")
|
||
(should (etaf-value (plist-get (etaf--component-instance-state
|
||
(wb-test--instance runtime 'wb-app)) :dark)))
|
||
(should-not (equal-including-properties
|
||
before (with-current-buffer (etaf-runtime-buffer runtime)
|
||
(buffer-string))))
|
||
(should (equal layout (wb-test--layout-bounds runtime)))
|
||
(should (equal widths (wb-test--line-widths runtime)))
|
||
(should (wb-test--desktop-header-inline-p runtime))
|
||
(should (equal add (wb-test--control runtime "新增任务")))
|
||
(wb-test--press runtime "定位新增按钮")
|
||
(should (eq add (etaf-focused-host-ref runtime)))
|
||
(wb-test--press runtime "显示说明")
|
||
(should-not (string-match-p "点击行:选择任务" (wb-test--text runtime)))
|
||
(wb-test--press runtime "显示说明")
|
||
(should (string-match-p "点击行:选择任务" (wb-test--text runtime)))
|
||
(wb-test--press runtime "深色")
|
||
(should-not (etaf-value (plist-get (etaf--component-instance-state
|
||
(wb-test--instance runtime 'wb-app)) :dark)))
|
||
(should (equal layout (wb-test--layout-bounds runtime)))
|
||
(should (equal widths (wb-test--line-widths runtime)))
|
||
(should (wb-test--desktop-header-inline-p runtime)))))
|
||
|
||
(ert-deftest wb-workbench-explanation-toggle-isolates-card-heights ()
|
||
"Explanation changes only detail height across three rendered layouts."
|
||
(dolist (viewport '((250 . stacked-sidebar) (500 . side-by-side-sidebar)
|
||
(70 . single-column)))
|
||
(ert-info ((format "Workbench explanation layout: %S" viewport))
|
||
(wb-test--with-app (runtime data (car viewport))
|
||
(etaf-dispatch-event runtime (wb-test--row runtime 1) 'press)
|
||
(cl-labels
|
||
((cards ()
|
||
(mapcar (lambda (entry)
|
||
(cons (car entry) (wb-test--card-layout runtime (cdr entry))))
|
||
'((filter . "全部") (tasks . "每页条数 10 ✎")
|
||
(detail . "显示说明") (service . "刷新 / 重试")))))
|
||
(let* ((before (cards))
|
||
(text (wb-test--text runtime))
|
||
(items (etaf-value (etaf-data-items data)))
|
||
(selected (etaf-data-selected-item data))
|
||
(detail (wb-test--instance runtime 'wb-detail))
|
||
(service (wb-test--instance runtime 'wb-service))
|
||
(expanded (plist-get (etaf--component-instance-state detail) :expanded))
|
||
(resource (plist-get (etaf--component-instance-state service) :resource))
|
||
(rows (mapcar (lambda (id) (wb-test--row runtime id))
|
||
(number-sequence 1 10)))
|
||
(control (wb-test--control runtime "显示说明"))
|
||
(filter (alist-get 'filter before))
|
||
(tasks (alist-get 'tasks before))
|
||
(detail-layout (alist-get 'detail before))
|
||
(service-layout (alist-get 'service before)))
|
||
;; Prove the viewport actually exercises the intended wrapping
|
||
;; shape from published host bounds, rather than style declarations.
|
||
(pcase (cdr viewport)
|
||
('stacked-sidebar
|
||
(should (= (plist-get filter :top) (plist-get tasks :top)
|
||
(plist-get detail-layout :top)))
|
||
(should (< (plist-get tasks :left) (plist-get detail-layout :left)))
|
||
(should (< (plist-get detail-layout :bottom)
|
||
(plist-get service-layout :top))))
|
||
('side-by-side-sidebar
|
||
(should (= (plist-get filter :top) (plist-get tasks :top)
|
||
(plist-get detail-layout :top) (plist-get service-layout :top)))
|
||
(should (< (plist-get detail-layout :left)
|
||
(plist-get service-layout :left))))
|
||
('single-column
|
||
(should (< (plist-get filter :bottom) (plist-get tasks :top)))
|
||
(should (< (plist-get tasks :bottom) (plist-get detail-layout :top)))
|
||
(should (< (plist-get detail-layout :bottom)
|
||
(plist-get service-layout :top)))))
|
||
(should (etaf-value expanded))
|
||
(should (= (wb-task-id selected) 1))
|
||
(should (string-match-p "点击行:选择任务" text))
|
||
(dolist (visible '(nil t))
|
||
(wb-test--press runtime "显示说明")
|
||
(let ((current (cards)))
|
||
(message "Workbench explanation width=%d visible=%S heights=%S -> %S"
|
||
(car viewport) visible
|
||
(mapcar (lambda (entry) (plist-get (cdr entry) :height)) before)
|
||
(mapcar (lambda (entry) (plist-get (cdr entry) :height)) current))
|
||
(dolist (card '(filter tasks service))
|
||
(should (= (plist-get (alist-get card before) :height)
|
||
(plist-get (alist-get card current) :height))))
|
||
(should (funcall (if visible #'= #'<)
|
||
(plist-get (alist-get 'detail current) :height)
|
||
(plist-get detail-layout :height)))
|
||
(dolist (card '(filter tasks detail service))
|
||
(should (equal (plist-get (alist-get card before) :host)
|
||
(plist-get (alist-get card current) :host))))
|
||
(should (eq visible (etaf-value expanded)))
|
||
(should (eq detail (wb-test--instance runtime 'wb-detail)))
|
||
(should (eq service (wb-test--instance runtime 'wb-service)))
|
||
(should (eq items (etaf-value (etaf-data-items data))))
|
||
(should (eq selected (etaf-data-selected-item data)))
|
||
(should (= (etaf-value (etaf-data-total data)) 100))
|
||
(should (= (etaf-value (etaf-data-page data)) 1))
|
||
(should (= (etaf-value (etaf-data-page-size data)) 10))
|
||
(should (eq (etaf-resource-status resource) 'success))
|
||
(should (equal rows (mapcar (lambda (id) (wb-test--row runtime id))
|
||
(number-sequence 1 10))))
|
||
(should (equal control (wb-test--control runtime "显示说明")))
|
||
(should (string-match-p "设计组件接口" (wb-test--text runtime)))
|
||
(should (string-match-p "服务正常" (wb-test--text runtime)))
|
||
(should (eq visible
|
||
(not (null (string-match-p "点击行:选择任务"
|
||
(wb-test--text runtime))))))))
|
||
(should (equal before (cards)))
|
||
(should (equal text (wb-test--text runtime)))))))))
|
||
|
||
(ert-deftest wb-workbench-theme-render-failure-retains-published-handlers ()
|
||
"A failed theme render keeps the published UI and usable old callbacks."
|
||
(wb-test--with-app (runtime data)
|
||
(let* ((theme (symbol-function 'wb-theme))
|
||
(dark (plist-get (etaf--component-instance-state
|
||
(wb-test--instance runtime 'wb-app)) :dark))
|
||
(generation (etaf-runtime-current-generation runtime))
|
||
(before (with-current-buffer (etaf-runtime-buffer runtime)
|
||
(buffer-string))))
|
||
(cl-letf (((symbol-function 'wb-theme)
|
||
(lambda (value)
|
||
(if value (error "Rejected theme candidate")
|
||
(funcall theme value)))))
|
||
(should-error (wb-test--press runtime "深色")))
|
||
(should (eq generation (etaf-runtime-current-generation runtime)))
|
||
(should (equal-including-properties
|
||
before (with-current-buffer (etaf-runtime-buffer runtime)
|
||
(buffer-string))))
|
||
;; The App passes a checked value, so the published Checkbox keeps that
|
||
;; snapshot. Its next press still requests true; UI rollback does not
|
||
;; restore the separately owned business ref.
|
||
(should (etaf-value dark))
|
||
(wb-test--press runtime "深色")
|
||
(should (etaf-value dark))
|
||
(should (eq generation (etaf-runtime-current-generation runtime)))
|
||
;; Restore the business value explicitly, then retry through the same
|
||
;; public control after removing the injected render failure.
|
||
(setf (etaf-value dark) nil)
|
||
(should (equal (wb-test--ids data) (number-sequence 1 10)))
|
||
(let ((retry-generation (etaf-runtime-current-generation runtime)))
|
||
(wb-test--press runtime "深色")
|
||
(should (etaf-value dark))
|
||
(should-not (eq retry-generation
|
||
(etaf-runtime-current-generation runtime)))
|
||
(let ((bounds (etaf-host-ref-bounds
|
||
runtime (wb-test--control runtime "深色"))))
|
||
(should bounds)
|
||
(should (string-match-p
|
||
"☑" (with-current-buffer (etaf-runtime-buffer runtime)
|
||
(buffer-substring-no-properties
|
||
(car bounds) (cdr bounds))))))))))
|
||
|
||
(ert-deftest wb-workbench-resource-failure-retry-and-cleanup ()
|
||
"The resource reports failure, retries, and disposes its last value once."
|
||
(let ((cleanups 0))
|
||
(cl-letf (((symbol-function 'wb-service-cleanup) (lambda () (cl-incf cleanups))))
|
||
(wb-test--with-app (runtime data)
|
||
(ignore data)
|
||
(let* ((instance (wb-test--instance runtime 'wb-service))
|
||
(resource (plist-get (etaf--component-instance-state instance)
|
||
:resource)))
|
||
(should (eq (etaf-resource-status resource) 'success))
|
||
(wb-test--press runtime "模拟失败")
|
||
(should (eq (etaf-resource-status resource) 'error))
|
||
(should (string-match-p "模拟加载失败" (wb-test--text runtime)))
|
||
(wb-test--press runtime "刷新 / 重试")
|
||
(should (eq (etaf-resource-status resource) 'success))
|
||
(should (string-match-p "服务正常" (wb-test--text runtime)))
|
||
(should-not (string-match-p "模拟加载失败" (wb-test--text runtime)))
|
||
(should (= cleanups 1))
|
||
(wb-test--press runtime "刷新 / 重试")
|
||
(should (= cleanups 2))
|
||
(etaf-unmount runtime)
|
||
(should (= cleanups 3))
|
||
(should-not (etaf-resource-active-p resource))
|
||
(should-not (etaf-effect-scope-active-p
|
||
(etaf--component-instance-scope instance))))))))
|
||
|
||
(ert-deftest wb-workbench-apps-isolate-state-and-remount-cleans-scopes ()
|
||
"Two mounts isolate state; teardown releases all component-owned resources."
|
||
(let ((installed 0) (cleaned 0)
|
||
(one (generate-new-buffer " *wb-one*"))
|
||
(two (generate-new-buffer " *wb-two*")))
|
||
(cl-letf (((symbol-function 'wb-trace-install)
|
||
(lambda () (cl-incf installed) (lambda () (cl-incf cleaned)))))
|
||
(unwind-protect
|
||
(progn
|
||
(dolist (buffer (list one two))
|
||
(etaf-mount buffer (etaf-view (wb-app))
|
||
'(:viewport-width 1600 :viewport-height 80)))
|
||
(let* ((r1 (etaf-runtime-for-buffer one))
|
||
(r2 (etaf-runtime-for-buffer two))
|
||
(d1 (wb-test--data r1))
|
||
(d2 (wb-test--data r2))
|
||
(scopes (mapcar #'etaf--component-instance-scope
|
||
(hash-table-values (etaf-runtime-instances r1)))))
|
||
(should-not (eq d1 d2))
|
||
(should-not (eq (wb-test--control r1 "新增任务")
|
||
(wb-test--control r2 "新增任务")))
|
||
(wb-test--press r1 "完成" (wb-test--row r1 1))
|
||
(should (plist-get (car (etaf-value (etaf-data-items d1))) :done))
|
||
(should-not (plist-get (car (etaf-value (etaf-data-items d2))) :done))
|
||
(wb-test--press r1 "深色")
|
||
(should-not (etaf-value (plist-get (etaf--component-instance-state
|
||
(wb-test--instance r2 'wb-app))
|
||
:dark)))
|
||
(etaf-unmount r1)
|
||
(should-not (etaf-runtime-for-buffer one))
|
||
(should (cl-every (lambda (scope)
|
||
(not (etaf-effect-scope-active-p scope))) scopes))
|
||
(should (etaf-runtime-mounted-p r2))
|
||
(etaf-mount one (etaf-view (wb-app))
|
||
'(:viewport-width 1600 :viewport-height 80))
|
||
(let ((fresh (wb-test--data (etaf-runtime-for-buffer one))))
|
||
(should-not (eq d1 fresh))
|
||
(should (equal (wb-test--ids fresh) (number-sequence 1 10)))
|
||
(should (= (etaf-value (etaf-data-total fresh)) 100))
|
||
(should-not (plist-get (car (etaf-value (etaf-data-items fresh))) :done)))))
|
||
(dolist (buffer (list one two))
|
||
(when-let* ((runtime (etaf-runtime-for-buffer buffer)))
|
||
(etaf-unmount runtime))
|
||
(when (buffer-live-p buffer) (kill-buffer buffer))))
|
||
(should (> installed 0))
|
||
(should (= installed cleaned)))))
|
||
|
||
(provide 'task-workbench-tests)
|
||
;;; task-workbench-tests.el ends here
|