etaf/tests/etaf-tests.el
Kinneyzhang 43b17192d9 feat: implement unified etaf architecture
Deliver the unified View and Component model with retained Runtime, reactive scopes, Context, Behaviors, events, Actions, styles, Resources, Data, official UI Components, and Playground examples.\n\nVerification: make check and make load pass in the independent repository; sibling Ebox core tests pass 544/544.
2026-08-05 02:56:13 +08:00

701 lines
26 KiB
EmacsLisp

;;; etaf-tests.el --- ETAF contract tests -*- lexical-binding: t; -*-
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Code:
(require 'ert)
(require 'etaf)
(defvar etaf-test-state-cell nil)
(defvar etaf-test-setup-count 0)
(defvar etaf-test-mounted-count 0)
(defvar etaf-test-unmounted-count 0)
(defvar etaf-test-theme-cell nil)
(defvar etaf-test-event-count 0)
(defvar etaf-test-behavior-cleanups 0)
(defvar etaf-test-behavior-runtime nil)
(defvar etaf-test-prop-cell nil)
(defvar etaf-test-updated-count 0)
(defvar etaf-test-lifecycle-failure-p nil)
(defun etaf-test--render-text (view)
"Return plain rendered text for VIEW."
(replace-regexp-in-string
"[[:space:]]+$" ""
(substring-no-properties (ebox-render (etaf-render view)))
nil t))
(etaf-define-component etaf-test-badge (&key label)
"Render LABEL as a small semantic test Component."
:view
(text :face 'bold (expr :value label)))
(etaf-define-component etaf-list (&key label)
"Render LABEL using the collision-safe `list-view' alias."
:view
(text (expr :value label)))
(etaf-define-component etaf-test-slot-card (&key title)
"Render a title with default and named slot projections."
:view
(column
(text (expr :value title))
(slot :name 'header (text :face 'shadow "Default header"))
(slot (text :face 'shadow "Default body"))))
(etaf-define-component etaf-test-named-slot-consumer ()
"Render the named header slot for forwarding tests."
:view
(slot :name 'header))
(etaf-define-component etaf-test-named-slot-forwarder ()
"Forward a named slot through a nested Component call."
:view
(etaf-test-named-slot-consumer
(slot :name 'header (text "Forwarded"))))
(etaf-define-component etaf-test-styled-card ()
"Render style and Theme precedence fixtures."
:styles
(styles
("&" :color "style-root")
(".title" :color "style-title" :bgcolor "title-bg"))
:view
(column
(text :class "title" :color "inline-title" "Title")
(text "Body")))
(etaf-define-component etaf-test-styled-child ()
"Render a child Host for nested style scope tests."
:view
(text :class "nested-child" "Nested"))
(etaf-define-component etaf-test-styled-parent ()
"Apply a style rule through a nested Component boundary."
:styles
(styles
(".nested-child" :color "parent-color"))
:view
(etaf-test-styled-child))
(etaf-define-component etaf-test-themed-text ()
"Provide Theme defaults to one text Host."
:setup
(progn
(etaf-theme-provide '(:color "theme-color" :bgcolor "theme-bg"))
(lambda () (etaf-view (text "Themed")))))
(etaf-define-component etaf-test-stateful (&key label)
"Render a retained counter for Runtime tests."
:setup
(let ((cell (etaf-ref 0)))
(setq etaf-test-state-cell cell)
(cl-incf etaf-test-setup-count)
(etaf-on-mounted (lambda () (cl-incf etaf-test-mounted-count)))
(etaf-on-updated (lambda () (cl-incf etaf-test-updated-count)))
(etaf-on-unmounted (lambda () (cl-incf etaf-test-unmounted-count)))
(lambda ()
(etaf-view
(text
(expr :value (format "%s:%d" label (etaf-value cell))))))))
(etaf-define-component etaf-test-provider ()
"Provide a reactive theme to descendants."
:setup
(let ((theme (etaf-ref 'dark)))
(setq etaf-test-theme-cell theme)
(etaf-provide 'theme theme)
(lambda ()
(etaf-view (column (slot))))))
(etaf-define-component etaf-test-consumer ()
"Render the nearest Context theme."
:setup
(let ((theme (etaf-inject 'theme nil t)))
(lambda ()
(etaf-view (text (expr :value (symbol-name (etaf-value theme))))))))
(etaf-define-component etaf-test-prop-stateful (&key label)
"Render a retained label whose prop can change without rerunning setup."
:setup
(progn
(cl-incf etaf-test-setup-count)
(lambda ()
(etaf-view (text (expr :value label))))))
(etaf-define-component etaf-test-lifecycle-failure (&key label)
"Render LABEL and deliberately fail from an update lifecycle hook."
:setup
(progn
(etaf-on-updated
(lambda ()
(when etaf-test-lifecycle-failure-p
(error "test lifecycle failed"))))
(lambda ()
(etaf-view (text (expr :value label))))))
(etaf-define-component etaf-test-rollback (&key fail)
"Render a candidate that can deliberately fail during reconciliation."
:setup
(let ((cell (etaf-ref 0)))
(setq etaf-test-prop-cell cell)
(lambda ()
(when fail
(signal 'etaf-runtime-error (list "test candidate failed")))
(etaf-view
(text
(expr :value (format "stable:%d" (etaf-value cell))))))))
(etaf-define-behavior etaf-test-cleanup-behavior (&rest attributes)
"Construct a Behavior whose disposal is visible to tests."
(apply #'etaf-behavior-create
'etaf-test-cleanup-behavior
(append attributes
(list :install
(lambda ()
(setq etaf-test-behavior-runtime
(etaf-behavior-context-runtime
(etaf-current-behavior-context)))
(lambda () (cl-incf etaf-test-behavior-cleanups)))))))
(etaf-action-define etaf-test-action (runtime)
"Increment the event counter through a named Action."
(ignore runtime)
(cl-incf etaf-test-event-count))
(ert-deftest etaf-view-property-region-precedes-children ()
"Reject a property that appears after a structural child."
(should-error
(macroexpand '(etaf-view (text "Hello" :face 'bold)))
:type 'etaf-view-syntax-error))
(ert-deftest etaf-view-ordinary-control-flow-belongs-in-expr ()
"Reject ordinary Elisp control flow in the child region."
(should-error
(macroexpand '(etaf-view (text (if checked "yes" "no"))))
:type 'etaf-view-syntax-error))
(ert-deftest etaf-view-quote-is-not-needed-for-structure ()
"Construct an unquoted structural View and preserve quoted data values."
(let ((view (etaf-view (text :face 'bold "Hello"))))
(should (etaf--view-node-p view))
(should (equal 'bold (plist-get (etaf--view-node-props view) :face)))
(should (equal "Hello" (etaf-test--render-text view)))))
(ert-deftest etaf-view-attribute-values-are-ordinary-elisp ()
"Evaluate an attribute expression without an extra evaluation wrapper."
(let ((face 'bold)
(label "Ready"))
(let ((view (etaf-view (text :face face (expr :value label)))))
(should (equal "Ready" (etaf-test--render-text view))))))
(ert-deftest etaf-view-expr-evaluates-control-flow ()
"Use the sole child computation bridge for ordinary control flow."
(let ((checked t))
(should
(equal "yes"
(etaf-test--render-text
(etaf-view
(text (expr :value (if checked "yes" "no")))))))))
(ert-deftest etaf-view-expr-can-return-a-view ()
"Allow an expression to return a dynamically constructed View."
(let ((open t))
(should
(equal "Details"
(etaf-test--render-text
(etaf-view
(column
(expr
:value
(when open
(etaf-view (text "Details")))))))))))
(ert-deftest etaf-view-expr-can-return-a-sequence ()
"Flatten a sequence returned by `expr' into the surrounding Host."
(should
(equal "AB"
(etaf-test--render-text
(etaf-view
(text (expr :value (list "A" "B"))))))))
(ert-deftest etaf-view-expr-rejects-extra-properties ()
"Reject an expr property other than `:value'."
(should-error
(macroexpand '(etaf-view (text (expr :test checked :value "yes"))))
:type 'etaf-view-syntax-error))
(ert-deftest etaf-view-quoted-view-data-is-not-executed ()
"Reject quoted View data when it reaches the executable child boundary."
(should-error
(macroexpand '(etaf-view (text (quote (text "not-a-view")))))
:type 'etaf-view-syntax-error))
(ert-deftest etaf-view-layouts-lower-to-ebox ()
"Lower row and column Hosts through Ebox's public constructors."
(should
(equal "AB"
(etaf-test--render-text
(etaf-view (row (text "A") (text "B"))))))
(should
(equal "A\nB"
(etaf-test--render-text
(etaf-view (column (text "A") (text "B")))))))
(ert-deftest etaf-view-prefixed-host-alias-lowers-to-canonical-host ()
"Resolve an explicit `etaf-' Host spelling to its core Host name."
(should
(equal "Hello"
(etaf-test--render-text
(etaf-view (etaf-text "Hello"))))))
(ert-deftest etaf-component-view-renders-props ()
"Render a stateless Component from its declared props."
(let ((view (etaf-view (etaf-test-badge :label "Ready"))))
(should (etaf--component-call-p view))
(should (equal "Ready" (etaf-test--render-text view)))))
(ert-deftest etaf-component-prefixed-name-has-short-alias ()
"Resolve an `etaf-' Component through its public View alias."
(should
(equal "Ready"
(etaf-test--render-text
(etaf-view (test-badge :label "Ready"))))))
(ert-deftest etaf-component-alias-avoids-elisp-collision ()
"Use a semantic alias when the unprefixed name is an Elisp function."
(should
(equal "Items"
(etaf-test--render-text
(etaf-view (list-view :label "Items"))))))
(ert-deftest etaf-component-rejects-unknown-props ()
"Reject undeclared Component props at the Component boundary."
(should-error
(etaf-view (etaf-test-badge :unknown t))
:type 'etaf-component-call-error))
(ert-deftest etaf-component-definition-keywords-have-one-owner ()
"Accept the two definition modes and reject ambiguous combinations."
(should
(macroexpand
'(etaf-define-component setup-component (&key value)
:setup (lambda () (etaf-view (text (expr :value value)))))))
(should
(macroexpand
'(etaf-define-component styled-component ()
:styles (styles ("&" :color "red"))
:view (text "x"))))
(should-error
(macroexpand
'(etaf-define-component ambiguous-component (&key value)
:setup value
:view (text "x")))
:type 'etaf-component-definition-error)
(should-error
(macroexpand
'(etaf-define-component invalid-component ()
:behavior value))
:type 'etaf-component-definition-error))
(ert-deftest etaf-component-slots-share-one-default-and-named-model ()
"Render default children and explicit named slot contributions."
(should
(equal "Card\nHeader\nBody"
(etaf-test--render-text
(etaf-view
(etaf-test-slot-card :title "Card"
(slot :name 'header (text "Header"))
(text "Body"))))))
(should
(equal "Card\nDefault header\nDefault body"
(etaf-test--render-text
(etaf-view (etaf-test-slot-card :title "Card")))))
(should
(equal "Card\nDefault body"
(etaf-test--render-text
(etaf-view
(etaf-test-slot-card
:title "Card"
(slot :name 'header))))))
(should-error
(macroexpand '(etaf-view (etaf-test-slot-card (slot "bad"))))
:type 'etaf-view-syntax-error)
(should-error
(macroexpand
'(etaf-view
(etaf-test-slot-card (slot :name header (text "bad")))))
:type 'etaf-view-syntax-error)
(should-error
(macroexpand
'(etaf-view
(etaf-test-slot-card (slot :name nil (text "bad")))))
:type 'etaf-view-syntax-error))
(ert-deftest etaf-component-nested-slot-inputs-stay-named ()
"Treat a nested Component's slot child as input, not as projection."
(should
(equal "Forwarded"
(etaf-test--render-text
(etaf-view (etaf-test-named-slot-forwarder))))))
(ert-deftest etaf-styles-have-root-class-and-inline-precedence ()
"Apply component styles only at matching scope and preserve inline props."
(let* ((node (etaf-render (etaf-view (etaf-test-styled-card))))
(content-node (plist-get node :ebox-content-node))
(children (plist-get content-node :children))
(title (car children))
(body (cadr children)))
(should (equal "inline-title" (ebox-get title :color)))
(should (equal "title-bg" (ebox-get title :bgcolor)))
(should (equal "style-root" (ebox-get node :color)))
(should-not (ebox-get body :color))))
(ert-deftest etaf-styles-continue-through-nested-components ()
"Apply a parent Component selector to a nested Component Host."
(let ((node (etaf-render (etaf-view (etaf-test-styled-parent)))))
(should (equal "parent-color" (ebox-get node :color)))))
(ert-deftest etaf-mounted-styles-continue-through-nested-components ()
"Apply a parent Component selector through the mounted Runtime."
(let ((buffer-name " *etaf-mounted-style-test*"))
(unwind-protect
(progn
(etaf-mount buffer-name (etaf-view (etaf-test-styled-parent)))
(should
(equal "parent-color"
(ebox-get
(etaf-runtime-root-node
(etaf-runtime-for-buffer buffer-name))
:color))))
(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-theme-defaults-are-inherited-and-overridable ()
"Apply Theme defaults while allowing explicit Host properties to win."
(let ((buffer-name " *etaf-theme-test*"))
(unwind-protect
(progn
(etaf-mount buffer-name (etaf-view (etaf-test-themed-text)))
(let* ((runtime (etaf-runtime-for-buffer buffer-name))
(node (etaf-runtime-root-node runtime)))
(should (equal "theme-color" (ebox-get node :color)))
(should (equal "theme-bg" (ebox-get node :bgcolor)))))
(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-text-supports-inline-propertized-runs ()
"Lower nested text Hosts to one Ebox content surface with text properties."
(let* ((node (etaf-render
(etaf-view
(text "Hello " (text :face 'bold "world") "!"))))
(content (ebox-get node :content)))
(should (equal "Hello world!" (substring-no-properties content)))
(should (eq 'bold (get-text-property 6 'face content)))
(should-not (get-text-property 0 'face content))))
(ert-deftest etaf-runtime-retains-setup-and-reactively-commits ()
"Run setup once, update through a ref, and dispose on unmount."
(let ((buffer-name " *etaf-runtime-test*"))
(setq etaf-test-state-cell nil
etaf-test-setup-count 0
etaf-test-mounted-count 0
etaf-test-unmounted-count 0
etaf-test-updated-count 0)
(unwind-protect
(progn
(etaf-mount buffer-name
(etaf-view (etaf-test-stateful :label "Count")))
(should (equal "Count:0" (etaf-test--render-text
(etaf-view (text "Count:0")))))
(with-current-buffer buffer-name
(should (equal "Count:0" (buffer-string))))
(should (= etaf-test-setup-count 1))
(should (= etaf-test-mounted-count 1))
(setf (etaf-value etaf-test-state-cell) 1)
(with-current-buffer buffer-name
(should (equal "Count:1" (buffer-string))))
(should (= etaf-test-setup-count 1))
(should (= etaf-test-updated-count 1))
(etaf-unmount (etaf-runtime-for-buffer buffer-name))
(should (= etaf-test-unmounted-count 1)))
(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-context-provide-inject-follows-component-tree ()
"Resolve the nearest Context and react to its provided ref."
(let ((buffer-name " *etaf-context-test*"))
(unwind-protect
(progn
(etaf-mount buffer-name
(etaf-view
(etaf-test-provider
(etaf-test-consumer))))
(with-current-buffer buffer-name
(should (equal "dark" (buffer-string))))
(setf (etaf-value etaf-test-theme-cell) 'light)
(with-current-buffer buffer-name
(should (equal "light" (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-behavior-event-and-action-share-runtime-boundary ()
"Install a Behavior, dispatch its Host callback, and dispose it."
(let ((buffer-name " *etaf-event-test*"))
(setq etaf-test-event-count 0
etaf-test-behavior-cleanups 0)
(unwind-protect
(progn
(etaf-mount
buffer-name
(etaf-view
(text
:ref 'interactive
:use (list (etaf-test-cleanup-behavior))
:on-press (lambda () (etaf-dispatch 'etaf-test-action))
"Press")))
(let ((runtime (etaf-runtime-for-buffer buffer-name)))
(etaf-dispatch-event runtime 'interactive 'press)
(should (= etaf-test-event-count 1))
(should (equal '(press)
(mapcar #'car
(gethash 'interactive
(etaf-runtime-handlers runtime)))))
(etaf-unmount runtime))
(should (= etaf-test-behavior-cleanups 1)))
(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-behavior-replacement-disposes-previous-installer ()
"Dispose a replaced Behavior while retaining the public installer path."
(let ((buffer-name " *etaf-behavior-replace-test*")
(marker (etaf-ref 0)))
(setq etaf-test-behavior-cleanups 0
etaf-test-behavior-runtime nil)
(unwind-protect
(progn
(etaf-mount
buffer-name
(etaf-view
(text
:ref 'behavior-host
:use (list (etaf-test-cleanup-behavior
:class (format "mode-%d" (etaf-value marker))))
"Behavior")))
(should (etaf-runtime-p etaf-test-behavior-runtime))
(setf (etaf-value marker) 1)
(should (= 1 etaf-test-behavior-cleanups)))
(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-toggleable-reads-reactive-value-at-event-time ()
"Use the current reactive value when a toggleable Behavior is pressed."
(let ((value (etaf-ref nil)) received)
(let ((behavior
(etaf-toggleable
:value value
:on-change (lambda (next) (setq received next))
:class "toggle")))
(should (equal "toggle"
(plist-get (etaf-behavior-spec-attributes behavior)
:class)))
(funcall (plist-get (etaf-behavior-spec-attributes behavior) :on-press))
(should (eq t received))
(setf (etaf-value value) t)
(funcall (plist-get (etaf-behavior-spec-attributes behavior) :on-press))
(should (eq nil received)))))
(ert-deftest etaf-focusable-supplies-default-tab-stop ()
"Give a focusable Behavior the standard tab index unless overridden."
(should (= 0 (plist-get (etaf-behavior-spec-attributes (etaf-focusable))
:tab-index)))
(should-not (plist-get
(etaf-behavior-spec-attributes
(etaf-focusable :tab-index nil))
:tab-index)))
(ert-deftest etaf-reactive-computed-watch-and-cleanup-share-one-model ()
"Compute values, watch changes, and dispose watch cleanup deterministically."
(let* ((source (etaf-ref 1))
(computed (etaf-computed (lambda () (* 2 (etaf-value source)))))
(watch-values nil)
(cleanup-count 0)
(stop-watch
(etaf-watch source
(lambda (new old)
(push (list new old) watch-values))
:immediate t))
(stop-effect
(etaf-watch-effect
(lambda ()
(etaf-value source)
(lambda () (cl-incf cleanup-count))))))
(unwind-protect
(progn
(should (= 2 (etaf-value computed)))
(setf (etaf-value source) 2)
(should (= 4 (etaf-value computed)))
(should (equal '((2 1) (1 nil)) watch-values))
(should (= 1 cleanup-count))
(funcall stop-watch)
(funcall stop-effect)
(should (= 2 cleanup-count))
(setf (etaf-value source) 3)
(should (equal '((2 1) (1 nil)) watch-values)))
(funcall stop-watch)
(funcall stop-effect))))
(ert-deftest etaf-reactive-effect-restores-dependencies-after-failure ()
"Keep the previous dependency set when an effect run fails."
(let* ((source (etaf-ref 0))
(runs 0)
(fail-p nil)
(effect (etaf-reactive-effect-create
(lambda ()
(etaf-value source)
(cl-incf runs)
(when fail-p
(error "expected effect failure"))))))
(etaf-reactive-effect-run effect)
(setq fail-p t)
(should-error (setf (etaf-value source) 1))
(should (= 2 runs))
(setq fail-p nil)
(setf (etaf-value source) 2)
(should (= 3 runs))
(etaf--stop-effect effect)))
(ert-deftest etaf-view-raw-ebox-is-an-explicit-backend-escape ()
"Lower a public Ebox node only through the explicit raw escape."
(let ((node
(etaf-render
(etaf-view
(raw-ebox
:key 7
:value (ebox-create :content "Backend"))))))
(should (equal "Backend" (ebox-get node :content)))
(should (= 7 (ebox-get node :key)))))
(ert-deftest etaf-stateful-props-update-without-rerunning-setup ()
"Track a reactive root prop while retaining one Component setup Scope."
(let ((buffer-name " *etaf-prop-update-test*")
(label (etaf-ref "A")))
(setq etaf-test-setup-count 0)
(unwind-protect
(progn
(etaf-mount
buffer-name
(etaf-view
(etaf-test-prop-stateful :label (etaf-value label))))
(with-current-buffer buffer-name
(should (equal "A" (buffer-string))))
(setf (etaf-value label) "B")
(with-current-buffer buffer-name
(should (equal "B" (buffer-string))))
(should (= 1 etaf-test-setup-count)))
(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-runtime-keeps-last-committed-view-after-render-error ()
"Rollback a failed candidate without losing the last committed buffer."
(let ((buffer-name " *etaf-rollback-test*")
(fail-p (etaf-ref nil)))
(unwind-protect
(progn
(etaf-mount
buffer-name
(etaf-view (etaf-test-rollback :fail (etaf-value fail-p))))
(with-current-buffer buffer-name
(should (equal "stable:0" (buffer-string))))
(should-error (setf (etaf-value fail-p) t)
:type 'etaf-runtime-error)
(with-current-buffer buffer-name
(should (equal "stable:0" (buffer-string))))
(setf (etaf-value fail-p) nil)
(with-current-buffer buffer-name
(should (equal "stable:0" (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-runtime-keeps-published-state-when-lifecycle-fails ()
"Keep retained state aligned with the published tree after hook failure."
(let ((buffer-name " *etaf-lifecycle-error-test*")
(label (etaf-ref "A")))
(setq etaf-test-lifecycle-failure-p nil)
(unwind-protect
(progn
(etaf-mount
buffer-name
(etaf-view
(etaf-test-lifecycle-failure :label (etaf-value label))))
(setq etaf-test-lifecycle-failure-p t)
(should-error (setf (etaf-value label) "B") :type 'error)
(should (etaf-runtime-mounted-p
(etaf-runtime-for-buffer buffer-name)))
(with-current-buffer buffer-name
(should (equal "B" (buffer-string))))
(setq etaf-test-lifecycle-failure-p nil)
(setf (etaf-value label) "C")
(with-current-buffer buffer-name
(should (equal "C" (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-events-support-payload-and-cyclic-focus ()
"Dispatch a payload and cycle focus through visible tab-index Hosts."
(let ((buffer-name " *etaf-focus-test*")
payload)
(unwind-protect
(progn
(etaf-mount
buffer-name
(etaf-view
(column
(text :ref 'first :tab-index 0 "First")
(text :ref 'second :tab-index 1
:on-input (lambda (value) (setq payload value))
"Second"))))
(let ((runtime (etaf-runtime-for-buffer buffer-name)))
(should (eq 'first (etaf-focus-next runtime)))
(should (eq 'second (etaf-focus-next runtime)))
(should (eq 'first (etaf-focus-next runtime)))
(etaf-dispatch-event runtime 'second 'input "value" t)
(should (equal "value" payload))))
(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-mount-publishes-through-ebox ()
"Mount a View into a buffer using Ebox's buffer publication API."
(let ((buffer-name " *etaf-test-mount*"))
(unwind-protect
(progn
(etaf-mount buffer-name
(etaf-view (text :color "#F4F6FB" "Mounted")))
(with-current-buffer buffer-name
(should (equal "Mounted" (buffer-string)))))
(when-let ((buffer (get-buffer buffer-name)))
(kill-buffer buffer)))))
;;; etaf-tests.el ends here