etaf/tests/etaf-tests.el
Kinneyzhang 0185c4e05a feat: establish unified etaf view foundation
Implement the P0 View grammar, expr bridge, stateless view Components, and Ebox mount path in a new independent package. Include bilingual architecture and implementation documents plus contract tests.
2026-08-05 00:36:52 +08:00

163 lines
5.6 KiB
EmacsLisp

;;; etaf-tests.el --- ETAF P0 contract tests -*- lexical-binding: t; -*-
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Code:
(require 'ert)
(require 'etaf)
(defun etaf-test--render-text (view)
"Return plain rendered text for VIEW."
(substring-no-properties (ebox-render (etaf-render view))))
(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)))
(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-rejects-unimplemented-definition-forms ()
"Reject future clauses instead of assigning them accidental semantics."
(should-error
(macroexpand
'(etaf-define-component future-component (&key value)
:setup value))
:type 'etaf-component-definition-error)
(should-error
(macroexpand
'(etaf-define-component styled-component ()
:styles (styles ("&" :color "red"))
:view (text "x")))
:type 'etaf-component-definition-error))
(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