New test files expose fundamental evaluator issues: - define doesn't create self-referencing closures (13 failures) - let doesn't isolate scope from parent env (2 failures) - set! doesn't walk scope chain for closed-over vars (3 failures) - Component calls return kwargs object instead of evaluating body (10 failures) 485/516 passing (94%). Parser tests: 100% pass. Macro tests: 96% pass. These failures map the exact work needed for tree-walk removal. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
198 lines
7.9 KiB
Plaintext
198 lines
7.9 KiB
Plaintext
;; ==========================================================================
|
|
;; test-defcomp.sx — Tests for component (defcomp) calling conventions
|
|
;;
|
|
;; Requires: test-framework.sx loaded first.
|
|
;; Modules tested: eval.sx (defcomp, component call), render.sx
|
|
;;
|
|
;; Component calling convention:
|
|
;; (defcomp ~name (&key k1 k2 &rest children) body...)
|
|
;; Keyword args: (~name :k1 v1 :k2 v2)
|
|
;; Children: (~name :k1 v1 child1 child2) — positional after keywords
|
|
;; Defaults: (or k1 "fallback")
|
|
;;
|
|
;; render-html takes an SX source string, evaluates + renders to HTML string.
|
|
;; For multi-form programs use (do ...) or define forms before the call.
|
|
;; ==========================================================================
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Basic defcomp behaviour
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "defcomp-basics"
|
|
(deftest "defcomp binds the component name"
|
|
(defcomp ~no-params ()
|
|
(span "hello"))
|
|
(assert-true (not (nil? ~no-params))))
|
|
|
|
(deftest "defcomp with positional params"
|
|
;; Components can accept plain positional params (not &key).
|
|
(defcomp ~greet (name)
|
|
(span name))
|
|
(assert-true (not (nil? ~greet))))
|
|
|
|
(deftest "defcomp body can reference defined names"
|
|
;; Body is evaluated in the defining env — outer defines are visible.
|
|
(define greeting "hi")
|
|
(defcomp ~uses-outer ()
|
|
(span greeting))
|
|
(assert-true (not (nil? ~uses-outer))))
|
|
|
|
(deftest "defcomp is a component type"
|
|
(defcomp ~typed-comp (&key x)
|
|
(div x))
|
|
;; component-affinity is available on all component values
|
|
(assert-equal "auto" (component-affinity ~typed-comp))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Keyword argument (&key) convention
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "defcomp-keyword-args"
|
|
(deftest "single &key param receives keyword argument"
|
|
;; Evaluation: component body is called with title bound to "World".
|
|
(defcomp ~k-single (&key title)
|
|
title)
|
|
;; We call it and check the returned value (not HTML).
|
|
(assert-equal "World" (~k-single :title "World")))
|
|
|
|
(deftest "multiple &key params"
|
|
(defcomp ~k-multi (&key first last)
|
|
(str first " " last))
|
|
(assert-equal "Ada Lovelace" (~k-multi :first "Ada" :last "Lovelace")))
|
|
|
|
(deftest "missing &key param is nil"
|
|
(defcomp ~k-missing (&key title subtitle)
|
|
subtitle)
|
|
(assert-nil (~k-missing :title "Only title")))
|
|
|
|
(deftest "&key param default via or"
|
|
(defcomp ~k-default (&key label)
|
|
(or label "default-label"))
|
|
(assert-equal "custom" (~k-default :label "custom"))
|
|
(assert-equal "default-label" (~k-default)))
|
|
|
|
(deftest "&key params can be numbers"
|
|
(defcomp ~k-num (&key value)
|
|
(* value 2))
|
|
(assert-equal 84 (~k-num :value 42)))
|
|
|
|
(deftest "&key params can be lists"
|
|
(defcomp ~k-list (&key items)
|
|
(len items))
|
|
(assert-equal 3 (~k-list :items (list "a" "b" "c")))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Rest / children convention
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "defcomp-rest-children"
|
|
(deftest "&rest captures all positional args"
|
|
(defcomp ~r-basic (&rest children)
|
|
(len children))
|
|
(assert-equal 3 (~r-basic "a" "b" "c")))
|
|
|
|
(deftest "&rest with &key separates keywords from positional"
|
|
(defcomp ~r-mixed (&key title &rest children)
|
|
(list title (len children)))
|
|
(let ((result (~r-mixed :title "T" "c1" "c2")))
|
|
(assert-equal "T" (first result))
|
|
(assert-equal 2 (nth result 1))))
|
|
|
|
(deftest "empty children when no positional args provided"
|
|
(defcomp ~r-empty (&rest children)
|
|
children)
|
|
(assert-true (empty? (~r-empty))))
|
|
|
|
(deftest "multiple children are captured in order"
|
|
(defcomp ~r-order (&rest children)
|
|
children)
|
|
(let ((kids (~r-order "x" "y" "z")))
|
|
(assert-equal "x" (nth kids 0))
|
|
(assert-equal "y" (nth kids 1))
|
|
(assert-equal "z" (nth kids 2)))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Component rendering to HTML
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "defcomp-rendering"
|
|
(deftest "simplest component renders to HTML"
|
|
(assert-equal "<p>hello</p>"
|
|
(render-html "(do (defcomp ~r-simple () (p \"hello\")) (~r-simple))")))
|
|
|
|
(deftest "component with &key renders keyword arg value"
|
|
(assert-equal "<h1>Greetings</h1>"
|
|
(render-html "(do (defcomp ~r-title (&key text) (h1 text))
|
|
(~r-title :text \"Greetings\"))")))
|
|
|
|
(deftest "component with multiple &key args"
|
|
(let ((html (render-html
|
|
"(do (defcomp ~r-card (&key title subtitle)
|
|
(div :class \"card\" (h2 title) (p subtitle)))
|
|
(~r-card :title \"Hi\" :subtitle \"Sub\"))")))
|
|
(assert-true (string-contains? html "class=\"card\""))
|
|
(assert-true (string-contains? html "<h2>Hi</h2>"))
|
|
(assert-true (string-contains? html "<p>Sub</p>"))))
|
|
|
|
(deftest "nested component calls"
|
|
(let ((html (render-html
|
|
"(do
|
|
(defcomp ~r-inner (&key label) (span label))
|
|
(defcomp ~r-outer (&key text) (div (~r-inner :label text)))
|
|
(~r-outer :text \"nested\"))")))
|
|
(assert-true (string-contains? html "<div>"))
|
|
(assert-true (string-contains? html "<span>nested</span>"))))
|
|
|
|
(deftest "component with children rendered inside wrapper"
|
|
(let ((html (render-html
|
|
"(do (defcomp ~r-box (&key &rest children)
|
|
(div :class \"box\" children))
|
|
(~r-box (p \"inside\")))")))
|
|
(assert-true (string-contains? html "class=\"box\""))
|
|
(assert-true (string-contains? html "<p>inside</p>"))))
|
|
|
|
(deftest "component with conditional rendering via when"
|
|
(let ((html-with (render-html
|
|
"(do (defcomp ~r-cond (&key show)
|
|
(div (when show (span \"visible\"))))
|
|
(~r-cond :show true))"))
|
|
(html-without (render-html
|
|
"(do (defcomp ~r-cond (&key show)
|
|
(div (when show (span \"visible\"))))
|
|
(~r-cond :show false))")))
|
|
(assert-true (string-contains? html-with "<span>visible</span>"))
|
|
(assert-false (string-contains? html-without "<span>"))))
|
|
|
|
(deftest "component with conditional rendering via if"
|
|
(assert-equal "<p>yes</p>"
|
|
(render-html "(do (defcomp ~r-if (&key flag)
|
|
(if flag (p \"yes\") (p \"no\")))
|
|
(~r-if :flag true))"))
|
|
(assert-equal "<p>no</p>"
|
|
(render-html "(do (defcomp ~r-if (&key flag)
|
|
(if flag (p \"yes\") (p \"no\")))
|
|
(~r-if :flag false))")))
|
|
|
|
(deftest "component default via or renders correctly"
|
|
(assert-equal "<span>fallback</span>"
|
|
(render-html "(do (defcomp ~r-default (&key label)
|
|
(span (or label \"fallback\")))
|
|
(~r-default))"))
|
|
(assert-equal "<span>given</span>"
|
|
(render-html "(do (defcomp ~r-default (&key label)
|
|
(span (or label \"fallback\")))
|
|
(~r-default :label \"given\"))")))
|
|
|
|
(deftest "component with multiple children rendered in order"
|
|
(let ((html (render-html
|
|
"(do (defcomp ~r-multi (&rest children)
|
|
(ul children))
|
|
(~r-multi (li \"a\") (li \"b\") (li \"c\")))")))
|
|
(assert-true (string-contains? html "<li>a</li>"))
|
|
(assert-true (string-contains? html "<li>b</li>"))
|
|
(assert-true (string-contains? html "<li>c</li>")))))
|