;; ========================================================================== ;; 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 "
hello
" (render-html "(do (defcomp ~r-simple () (p \"hello\")) (~r-simple))"))) (deftest "component with &key renders keyword arg value" (assert-equal "Sub
")))) (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 "inside
")))) (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 "visible")) (assert-false (string-contains? html-without "")))) (deftest "component with conditional rendering via if" (assert-equal "yes
" (render-html "(do (defcomp ~r-if (&key flag) (if flag (p \"yes\") (p \"no\"))) (~r-if :flag true))")) (assert-equal "no
" (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 "fallback" (render-html "(do (defcomp ~r-default (&key label) (span (or label \"fallback\"))) (~r-default))")) (assert-equal "given" (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 "