From 092ea9d631dd850133ce36620658b9a580ae521b Mon Sep 17 00:00:00 2001 From: Kinneyzhang Date: Wed, 5 Aug 2026 09:16:36 +0800 Subject: [PATCH] feat(playground): add complete ETAF showcase Add a public interactive example covering Grid layout, layered canvas and panel backgrounds, Context-derived themes, reactive state, in-memory Data Controller updates, keyed rows, navigation, and event dispatch. Defer one viewport sync after display so fullscreen frames render the actual visible dimensions. --- Makefile | 2 +- README.md | 9 + README.zh-CN.md | 9 + etaf-playground.el | 547 +++++++++++++++++++++++++++++++++ tests/etaf-playground-tests.el | 57 ++++ 5 files changed, 623 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9a502ff..b80097a 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ EMACS ?= emacs -LOAD_PATH = -L . -L ../etaf -L ../etaf-ui -L ../emacs-box +LOAD_PATH = -L . -L ../etaf -L ../etaf-ui -L ../ebox .PHONY: all compile test check checkdoc load clean diff --git a/README.md b/README.md index 11dcd6d..e3523d4 100644 --- a/README.md +++ b/README.md @@ -7,4 +7,13 @@ (etaf-playground-open) ``` +For the complete interactive example, open the Showcase: + +```elisp +(require 'etaf-playground) +(etaf-playground-open-showcase) +``` + +The Showcase is intentionally built only from public ETAF APIs. It demonstrates a Grid shell, a root canvas background with local panel surfaces, Context-based theme values, reactive state, a memory Data Controller, keyed rows, and public event dispatch. The `etaf-playground-open-ui` entry remains the smaller optional example for the official `etaf-ui` catalog. + Run `make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs` from this directory. diff --git a/README.zh-CN.md b/README.zh-CN.md index 57bede2..1a8fec4 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -7,4 +7,13 @@ (etaf-playground-open) ``` +要打开完整的交互示例,使用 Showcase: + +```elisp +(require 'etaf-playground) +(etaf-playground-open-showcase) +``` + +Showcase 只使用 ETAF 公共 API,演示 Grid 外壳、根画布背景与局部面板背景的分层、基于 Context 的主题值、响应式状态、内存 Data Controller、带 key 的行以及公共事件分发。`etaf-playground-open-ui` 仍然是展示官方 `etaf-ui` 目录的较小可选示例。 + 在该目录运行 `make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs`。 diff --git a/etaf-playground.el b/etaf-playground.el index e335df6..2f16110 100644 --- a/etaf-playground.el +++ b/etaf-playground.el @@ -20,6 +20,9 @@ (defconst etaf-playground-buffer-name "*ETAF Playground*" "Default buffer name used by `etaf-playground-open'.") +(defconst etaf-playground-showcase-buffer-name "*ETAF Showcase*" + "Default buffer name used by `etaf-playground-open-showcase'.") + ;;;###autoload (etaf-define-component etaf-playground-counter (&key title) "Render a retained counter titled TITLE." @@ -39,6 +42,525 @@ :on-press (lambda () (cl-incf (etaf-value count))) " + "))))))) +(defun etaf-playground--showcase-palette (dark-p) + "Return the Showcase palette for DARK-P." + (if dark-p + '(:color "#E8EEF7" :bgcolor "#151A23" :surface "#202938" + :surface-alt "#29364A" :muted "#AAB7CC" :accent "#79B8FF" + :accent-strong "#A7D0FF" :success "#70D6A0" :warning "#F5CA78" + :danger "#FF8A8A" :border "#52637E") + '(:color "#263244" :bgcolor "#F4F6FB" :surface "#FFFFFF" + :surface-alt "#E8EDF5" :muted "#687386" :accent "#245EA8" + :accent-strong "#173D70" :success "#247A50" :warning "#986B13" + :danger "#B83C45" :border "#8A93A6"))) + +(defun etaf-playground--showcase-palette-value (palette key) + "Return KEY from reactive Showcase PALETTE." + (plist-get (etaf-value palette) key)) + +(defun etaf-playground--showcase-sync-viewport (buffer) + "Rerender Showcase BUFFER against its visible window dimensions. + +This keeps viewport-height shells and full-surface theme changes correct after +the buffer is displayed. Return nil when BUFFER is not visible yet." + (when-let ((window (get-buffer-window buffer t))) + (let ((width (window-pixel-width window)) + (height (window-body-height window))) + (when (and (> width 0) (> height 0)) + (ebox-rerender-buffer-with-context buffer width height))))) + +(defun etaf-playground--showcase-schedule-viewport-sync (buffer) + "Schedule a viewport sync for visible Showcase BUFFER. + +The first window geometry reported while a frame is being displayed can be +stale. Deferring one sync to the next event-loop turn lets Ebox receive the +window dimensions that are actually visible without making the component own +any frame lifecycle state." + (run-at-time + 0 nil + (lambda () + (when (buffer-live-p buffer) + (etaf-playground--showcase-sync-viewport buffer))))) + +(defun etaf-playground--showcase-border (palette) + "Return a border using the current Showcase PALETTE." + (list '(1) 'solid + (etaf-playground--showcase-palette-value palette :border))) + +(defun etaf-playground--showcase-button + (label ref callback palette &optional active-p) + "Return a Showcase button for LABEL, REF, CALLBACK, PALETTE, and ACTIVE-P." + (etaf-view + (text + :class (if active-p "etaf-showcase-button active" "etaf-showcase-button") + :ref ref + :role 'button + :tab-index 0 + :on-press callback + :color (etaf-playground--showcase-palette-value + palette (if active-p :bgcolor :accent-strong)) + :bgcolor (etaf-playground--showcase-palette-value + palette (if active-p :accent :surface-alt)) + :border (etaf-playground--showcase-border palette) + :padding '(0 (10)) + (expr :value label)))) + +(defun etaf-playground--showcase-metric (label value palette) + "Return a metric card for LABEL, VALUE, and PALETTE." + (etaf-view + (column + :class "etaf-showcase-metric" + :width '(280) + :bgcolor (etaf-playground--showcase-palette-value palette :surface) + :border (etaf-playground--showcase-border palette) + (text + :color (etaf-playground--showcase-palette-value palette :muted) + (expr :value label)) + (text + :face 'bold + :color (etaf-playground--showcase-palette-value palette :accent) + (expr :value value))))) + +(defun etaf-playground--showcase-nav (page palette on-page) + "Return the Showcase navigation for PAGE, PALETTE, and ON-PAGE." + (etaf-view + (column + :class "etaf-showcase-sidebar" + :width '(220) + :bgcolor (etaf-playground--showcase-palette-value palette :surface) + :border (etaf-playground--showcase-border palette) + (text :face 'bold "NAVIGATION") + (expr :value + (etaf-playground--showcase-button + "Overview" 'showcase-nav-overview + (lambda () (funcall on-page 'overview)) palette + (eq page 'overview))) + (expr :value + (etaf-playground--showcase-button + "Work items" 'showcase-nav-work-items + (lambda () (funcall on-page 'work-items)) palette + (eq page 'work-items))) + (expr :value + (etaf-playground--showcase-button + "Theme" 'showcase-nav-theme + (lambda () (funcall on-page 'theme)) palette + (eq page 'theme))) + (text + :color (etaf-playground--showcase-palette-value palette :muted) + "\nKeyboard: Tab, RET")))) + +(defun etaf-playground--showcase-page-title (page) + "Return the display title for Showcase PAGE." + (or (cdr (assq page '((overview . "Overview") + (work-items . "Work items") + (theme . "Theme and semantics")))) + "Overview")) + +(defun etaf-playground--showcase-task-row + (task selected-id palette on-select) + "Return one interactive table row for TASK, SELECTED-ID, PALETTE, and ON-SELECT." + (let ((id (plist-get task :id)) + (selected-p (equal (plist-get task :id) selected-id))) + (etaf-view + (row + :key id + :ref (intern (format "showcase-task-%s" id)) + :role 'button + :tab-index 0 + :on-press (lambda () (funcall on-select id)) + :class (if selected-p "etaf-showcase-task selected" + "etaf-showcase-task") + :bgcolor (etaf-playground--showcase-palette-value + palette (if selected-p :surface-alt :surface)) + :border (etaf-playground--showcase-border palette) + (text :width '(110) (expr :value id)) + (text :width '(330) (expr :value (plist-get task :title))) + (text :width '(170) (expr :value (plist-get task :owner))) + (text :width '(150) + :color (etaf-playground--showcase-palette-value + palette + (if (equal (plist-get task :status) "done") + :success :accent)) + (expr :value (capitalize (plist-get task :status)))))))) + +(defun etaf-playground--showcase-task-table + (items selected-id palette on-select) + "Return the task table for ITEMS, SELECTED-ID, PALETTE, and ON-SELECT." + (etaf-view + (column + :class "etaf-showcase-table" + (row + :bgcolor (etaf-playground--showcase-palette-value palette :surface-alt) + :border (etaf-playground--showcase-border palette) + (text :face 'bold :width '(110) "ID") + (text :face 'bold :width '(330) "TASK") + (text :face 'bold :width '(170) "OWNER") + (text :face 'bold :width '(150) "STATUS")) + (expr :value + (mapcar + (lambda (task) + (etaf-playground--showcase-task-row + task selected-id palette on-select)) + items))))) + +(defun etaf-playground--showcase-overview + (palette controller notice actions) + "Return the Showcase overview page. + +PALETTE, CONTROLLER, NOTICE, and ACTIONS provide its live data and behavior." + (let ((items (etaf-value (etaf-data-items controller))) + (selected-count + (length (etaf-value (etaf-data-selection controller))))) + (etaf-view + (column + :class "etaf-showcase-section" + (text :face 'bold "A complete text application in one View tree") + (text + :color (etaf-playground--showcase-palette-value palette :muted) + "The shell, Grid cards, retained state, Data Controller, and events all share one model.") + (grid + :width '(880) + :grid-template-columns '((280) (280) (280)) + :gap '(1 (12)) + (expr :value + (etaf-playground--showcase-metric + "VISIBLE WORK ITEMS" (format "%d" (length items)) palette)) + (expr :value + (etaf-playground--showcase-metric + "SELECTED" (format "%d" selected-count) palette)) + (expr :value + (etaf-playground--showcase-metric + "RUNTIME STATUS" + (capitalize (symbol-name (etaf-value + (etaf-data-status controller)))) + palette))) + (row + :gap '(0 (12)) + (column + :width '(430) + :bgcolor (etaf-playground--showcase-palette-value palette :surface) + :border (etaf-playground--showcase-border palette) + (text :face 'bold "Try the interaction path") + (text :color (etaf-playground--showcase-palette-value palette :muted) + "Open Work items, select a row, add a task, then toggle Theme.") + (expr :value + (etaf-playground--showcase-button + "Open Work items" 'showcase-open-work-items + (plist-get actions :open-work-items) palette))) + (column + :width '(430) + :bgcolor (etaf-playground--showcase-palette-value palette :surface) + :border (etaf-playground--showcase-border palette) + (text :face 'bold "Latest application feedback") + (text + :color (etaf-playground--showcase-palette-value palette :accent) + (expr :value notice)))))))) + +(defun etaf-playground--showcase-work-items + (palette controller filter selected-id notice actions) + "Return the Showcase work-items page. + +PALETTE, CONTROLLER, FILTER, SELECTED-ID, NOTICE, and ACTIONS provide its +live state." + (let* ((items (etaf-value (etaf-data-items controller))) + (page (etaf-value (etaf-data-page controller))) + (page-size (etaf-value (etaf-data-page-size controller))) + (total (etaf-value (etaf-data-total controller))) + (last-page (max 1 (ceiling (/ (float total) page-size))))) + (etaf-view + (column + :class "etaf-showcase-section" + (row + (column + :width '(520) + (text :face 'bold "Work items") + (text :color (etaf-playground--showcase-palette-value palette :muted) + (expr :value (format "%d matching records · page %d/%d" + total page last-page)))) + (expr :value + (etaf-playground--showcase-button + (format "Filter: %s" (capitalize (symbol-name filter))) + 'showcase-filter (plist-get actions :cycle-filter) palette + (not (eq filter 'all)))) + (expr :value + (etaf-playground--showcase-button + "Add task" 'showcase-add-task + (plist-get actions :add-task) palette))) + (text :color (etaf-playground--showcase-palette-value palette :muted) + (expr :value notice)) + (expr :value + (etaf-playground--showcase-task-table + items selected-id palette (plist-get actions :select-task))) + (row + :gap '(0 (12)) + (expr :value + (etaf-playground--showcase-button + "Previous" 'showcase-previous-page + (plist-get actions :previous-page) palette)) + (expr :value + (etaf-playground--showcase-button + "Next" 'showcase-next-page + (plist-get actions :next-page) palette)) + (expr :value + (etaf-playground--showcase-button + "Clear selection" 'showcase-clear-selection + (plist-get actions :clear-selection) palette))) + (column + :bgcolor (etaf-playground--showcase-palette-value palette :surface) + :border (etaf-playground--showcase-border palette) + (text :face 'bold "Selected record") + (text + :color (etaf-playground--showcase-palette-value palette :accent) + (expr :value + (or (and selected-id + (format "%s · use the row key to keep identity stable" + selected-id)) + "No row selected")))))))) + +(defun etaf-playground--showcase-swatch-foreground (dark-p label palette) + "Return a readable swatch foreground for DARK-P, LABEL, and PALETTE." + (if (equal label "Surface") + (etaf-playground--showcase-palette-value palette :color) + (if dark-p + (etaf-playground--showcase-palette-value palette :bgcolor) + "#FFFFFF"))) + +(defun etaf-playground--showcase-swatch + (label color foreground palette) + "Return a color swatch for LABEL, COLOR, FOREGROUND, and PALETTE." + (etaf-view + (column + :width '(280) + :height 3 + :bgcolor color + :color foreground + :border (etaf-playground--showcase-border palette) + (text :face 'bold (expr :value label)) + (text (expr :value color))))) + +(defun etaf-playground--showcase-theme (dark-p palette actions) + "Return the Showcase theme page for DARK-P, PALETTE, and ACTIONS." + (let ((colors (etaf-value palette))) + (etaf-view + (column + :class "etaf-showcase-section" + (text :face 'bold "Theme and semantics") + (text :color (etaf-playground--showcase-palette-value palette :muted) + "Theme is an inherited Context value; explicit Host properties remain local overrides.") + (row + (text :face 'bold (expr :value (if dark-p "Dark theme" "Light theme"))) + (expr :value + (etaf-playground--showcase-button + (if dark-p "Switch to light" "Switch to dark") + 'showcase-theme-toggle + (plist-get actions :toggle-theme) palette))) + (grid + :width '(880) + :grid-template-columns '((280) (280) (280)) + :gap '(1 (12)) + (expr :value + (mapcar + (lambda (entry) + (etaf-playground--showcase-swatch + (car entry) (cdr entry) + (etaf-playground--showcase-swatch-foreground + dark-p (car entry) palette) + palette)) + (list (cons "Surface" (plist-get colors :surface)) + (cons "Accent" (plist-get colors :accent)) + (cons "Success" (plist-get colors :success)) + (cons "Warning" (plist-get colors :warning)) + (cons "Danger" (plist-get colors :danger)) + (cons "Muted" (plist-get colors :muted))))) + (column + :bgcolor (etaf-playground--showcase-palette-value palette :surface) + :border (etaf-playground--showcase-border palette) + (text :face 'bold "Why this is useful") + (text + :color (etaf-playground--showcase-palette-value palette :muted) + "The same Component grammar handles static structure, computed values, state, data, and interaction."))))))) + +(defun etaf-playground--showcase-shell + (page dark-p filter controller selected-id notice palette actions title) + "Return the complete Showcase shell. + +PAGE, DARK-P, FILTER, CONTROLLER, SELECTED-ID, NOTICE, PALETTE, ACTIONS, +and TITLE provide its live state and presentation." + (etaf-view + (column + :class "etaf-showcase-root" + :width '(viewport) + :height '(viewport-height) + :color (etaf-playground--showcase-palette-value palette :color) + :bgcolor (etaf-playground--showcase-palette-value palette :bgcolor) + :border (etaf-playground--showcase-border palette) + (row + :class "etaf-showcase-header" + :width '(viewport) + :bgcolor (etaf-playground--showcase-palette-value palette :surface) + :border (etaf-playground--showcase-border palette) + (column :width '(220) + :bgcolor (etaf-playground--showcase-palette-value palette :surface) + (text :face 'bold (expr :value title)) + (text :color (etaf-playground--showcase-palette-value + palette :muted) + "A runnable composition")) + (column :width '(650) + :bgcolor (etaf-playground--showcase-palette-value palette :surface) + (text :face 'bold + (expr :value (etaf-playground--showcase-page-title page))) + (text :color (etaf-playground--showcase-palette-value + palette :muted) + (expr :value + (if dark-p "Dark semantic surface" "Light semantic surface")))) + (expr :value + (etaf-playground--showcase-button + (if dark-p "Light" "Dark") 'showcase-header-theme-toggle + (plist-get actions :toggle-theme) palette))) + (row + :width '(viewport) + :color (etaf-playground--showcase-palette-value palette :color) + (expr :value + (etaf-playground--showcase-nav + page palette (plist-get actions :set-page))) + (column + :width '(880) + (expr :value + (cond + ((eq page 'work-items) + (etaf-playground--showcase-work-items + palette controller filter selected-id notice actions)) + ((eq page 'theme) + (etaf-playground--showcase-theme dark-p palette actions)) + (t + (etaf-playground--showcase-overview + palette controller notice actions)))))) + (row + :class "etaf-showcase-status" + :width '(viewport) + :color (etaf-playground--showcase-palette-value palette :muted) + :bgcolor (etaf-playground--showcase-palette-value palette :surface) + (text (expr :value (format "STATUS %s" notice))) + (text (expr :value (format " · %s records" (etaf-value + (etaf-data-total controller))))))))) + +;;;###autoload +(etaf-define-component etaf-playground-showcase (&key title) + "Render a complex interactive ETAF application showcase." + :setup + (let* ((page (etaf-ref 'overview :name 'showcase-page)) + (dark-p (etaf-ref nil :name 'showcase-dark-theme)) + (filter (etaf-ref 'all :name 'showcase-filter)) + (selected-id (etaf-ref nil :name 'showcase-selected-id)) + (notice (etaf-ref "Ready · choose a page" :name 'showcase-notice)) + (next-id (etaf-ref 107 :name 'showcase-next-id)) + (source + (etaf-data-memory-source + '((:id "T-101" :title "Stabilize incremental commits" + :owner "Ada" :status "open") + (:id "T-102" :title "Verify Grid track sizing" + :owner "Grace" :status "review") + (:id "T-103" :title "Document the public View grammar" + :owner "Lin" :status "done") + (:id "T-104" :title "Exercise Context inheritance" + :owner "Edsger" :status "open") + (:id "T-105" :title "Measure native fallback behavior" + :owner "Margaret" :status "review") + (:id "T-106" :title "Polish keyboard focus paths" + :owner "Alan" :status "open")) + :id-key :id + :name 'showcase-work-items)) + (controller + (etaf-data-controller source :page-size 4)) + (palette + (etaf-computed + (lambda () + (etaf-playground--showcase-palette (etaf-value dark-p))))) + (theme + (etaf-computed + (lambda () + (let ((colors (etaf-value palette))) + (list :color (plist-get colors :color)))))) + (runtime (etaf-current-runtime)) + (last-rendered-dark-p nil) + actions) + (etaf-theme-provide theme) + (etaf-on-mounted (lambda () (etaf-data-load controller))) + (etaf-on-unmounted (lambda () (etaf-data-stop controller))) + (etaf-on-updated + (lambda () + (let ((current-dark-p (etaf-value dark-p))) + (when (not (eq current-dark-p last-rendered-dark-p)) + (setq last-rendered-dark-p current-dark-p) + (etaf-playground--showcase-schedule-viewport-sync + (etaf-runtime-buffer runtime)))))) + (setq actions + (list + :set-page (lambda (next-page) + (setf (etaf-value page) next-page + (etaf-value notice) + (format "Opened %s" + (etaf-playground--showcase-page-title + next-page)))) + :open-work-items (lambda () + (setf (etaf-value page) 'work-items + (etaf-value notice) "Opened Work items")) + :toggle-theme (lambda () + (setf (etaf-value dark-p) (not (etaf-value dark-p)) + (etaf-value notice) + (if (etaf-value dark-p) + "Dark theme enabled" + "Light theme enabled"))) + :add-task (lambda () + (let* ((number (etaf-value next-id)) + (id (format "T-%d" number))) + (cl-incf (etaf-value next-id)) + (etaf-data-mutate + controller 'insert + (list :id id + :title "New task from the interaction path" + :owner "You" + :status "open")) + (setf (etaf-value notice) + (format "Added %s" id)))) + :cycle-filter (lambda () + (let ((next (pcase (etaf-value filter) + ('all 'open) + ('open 'review) + (_ 'all)))) + (setf (etaf-value filter) next) + (etaf-data-set-query + controller + (pcase next + ('all nil) + (_ (symbol-name next)))) + (etaf-data-load controller) + (setf (etaf-value notice) + (format "Filter: %s" + (capitalize (symbol-name next)))))) + :previous-page (lambda () + (etaf-data-previous-page controller) + (etaf-data-load controller) + (setf (etaf-value notice) "Moved to the previous page")) + :next-page (lambda () + (etaf-data-next-page controller) + (etaf-data-load controller) + (setf (etaf-value notice) "Moved to the next page")) + :clear-selection (lambda () + (etaf-data-clear-selection controller) + (setf (etaf-value selected-id) nil + (etaf-value notice) "Selection cleared")) + :select-task (lambda (id) + (etaf-data-select controller id) + (setf (etaf-value selected-id) id + (etaf-value notice) + (format "Selected %s" id))))) + (lambda () + (etaf-playground--showcase-shell + (etaf-value page) (etaf-value dark-p) (etaf-value filter) + controller (etaf-value selected-id) (etaf-value notice) + palette actions title)))) + ;;;###autoload (defun etaf-playground-view () "Return the core ETAF playground View without side effects." @@ -66,6 +588,11 @@ (message "Checkbox value: %s" value)))) (etaf-playground-counter :title "Core counter")))) +;;;###autoload +(defun etaf-playground-showcase-view () + "Return the complex ETAF Showcase View without side effects." + (etaf-view (etaf-playground-showcase :title "ETAF Showcase"))) + ;;;###autoload (defun etaf-playground-open (&optional buffer-name) "Mount the core playground in BUFFER-NAME and return its buffer." @@ -80,6 +607,26 @@ (etaf-mount (or buffer-name etaf-playground-buffer-name) (etaf-playground-ui-view))) +;;;###autoload +(defun etaf-playground-open-showcase (&optional buffer-name) + "Mount the interactive Showcase in BUFFER-NAME and return its buffer. + +Use the visible navigation and buttons with the mouse or keyboard. The +Showcase demonstrates Grid layout, Context theme defaults, reactive state, +Data Controller updates, stable row keys, and public event dispatch." + (interactive) + (let* ((buffer + (etaf-mount (or buffer-name etaf-playground-showcase-buffer-name) + (etaf-playground-showcase-view))) + (window (or (get-buffer-window buffer t) + (progn + (pop-to-buffer buffer) + (get-buffer-window buffer t))))) + (when (window-live-p window) + (etaf-playground--showcase-sync-viewport buffer) + (etaf-playground--showcase-schedule-viewport-sync buffer)) + buffer)) + ;;;###autoload (defun etaf-playground-close (&optional buffer-name) "Unmount and kill the playground BUFFER-NAME, returning its buffer." diff --git a/tests/etaf-playground-tests.el b/tests/etaf-playground-tests.el index 7cdc3b0..d63bfb4 100644 --- a/tests/etaf-playground-tests.el +++ b/tests/etaf-playground-tests.el @@ -36,6 +36,63 @@ (when-let ((buffer (get-buffer buffer-name))) (kill-buffer buffer))))) +(ert-deftest etaf-playground-showcase-covers-public-interaction-path () + "Mount the Showcase and exercise its navigation, data, and theme events." + (let ((buffer-name " *etaf-playground-showcase-test*")) + (unwind-protect + (progn + (etaf-mount buffer-name (etaf-playground-showcase-view)) + (with-current-buffer buffer-name + (should (string-match-p "ETAF Showcase" (buffer-string))) + (should (string-match-p "VISIBLE WORK ITEMS" (buffer-string))) + (should (string-match-p "4" (buffer-string)))) + (let ((runtime (etaf-runtime-for-buffer buffer-name))) + (etaf-dispatch-event runtime 'showcase-open-work-items 'press) + (with-current-buffer buffer-name + (should (string-match-p "Work items" (buffer-string))) + (should (string-match-p "T-101" (buffer-string)))) + (etaf-dispatch-event runtime 'showcase-task-T-101 'press) + (with-current-buffer buffer-name + (should (string-match-p "Selected T-101" (buffer-string)))) + (etaf-dispatch-event runtime 'showcase-add-task 'press) + (with-current-buffer buffer-name + (should (string-match-p "T-107" (buffer-string)))) + (etaf-dispatch-event runtime 'showcase-nav-theme 'press) + (with-current-buffer buffer-name + (should (string-match-p "Theme and semantics" (buffer-string)))) + (etaf-dispatch-event runtime 'showcase-theme-toggle 'press) + (with-current-buffer buffer-name + (should (string-match-p "Dark theme" (buffer-string))) + (should (string-match-p "Dark semantic surface" (buffer-string)))) + (etaf-dispatch-event runtime 'showcase-nav-overview 'press) + (with-current-buffer buffer-name + (should (string-match-p "Overview" (buffer-string))) + (should (string-match-p "Dark semantic surface" (buffer-string)))))) + (when-let ((runtime (etaf-runtime-for-buffer buffer-name))) + (etaf-unmount runtime)) + (when-let ((buffer (get-buffer buffer-name))) + (kill-buffer buffer))))) + +(ert-deftest etaf-playground-showcase-keeps-backgrounds-layered () + "Keep the root canvas background separate from semantic surface boxes." + (let ((buffer-name " *etaf-playground-showcase-layering-test*")) + (unwind-protect + (progn + (etaf-mount buffer-name (etaf-playground-showcase-view)) + (let* ((runtime (etaf-runtime-for-buffer buffer-name)) + (root (etaf-runtime-root-node runtime)) + (layout (plist-get root :ebox-content-node)) + (children (plist-get layout :children))) + (should (equal (plist-get root :bgcolor) "#F4F6FB")) + (should-not (plist-member root :surface-properties)) + (should (equal (mapcar (lambda (node) (plist-get node :bgcolor)) + children) + '("#FFFFFF" nil "#FFFFFF"))))) + (when-let ((runtime (etaf-runtime-for-buffer buffer-name))) + (etaf-unmount runtime)) + (when-let ((buffer (get-buffer buffer-name))) + (kill-buffer buffer))))) + (provide 'etaf-playground-tests) ;;; etaf-playground-tests.el ends here