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.
163 lines
5.6 KiB
EmacsLisp
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
|