;; ========================================================================== ;; test-render-advanced.sx — Advanced HTML rendering tests ;; ;; Requires: test-framework.sx loaded first. ;; Modules tested: render.sx, adapter-html.sx, eval.sx ;; ;; Platform functions required (beyond test framework): ;; render-html (sx-source) -> HTML string ;; Parses the sx-source string, evaluates via render-to-html in a ;; fresh env, and returns the resulting HTML string. ;; ;; Covers advanced rendering scenarios not addressed in test-render.sx: ;; - Deeply nested component calls ;; - Dynamic content (let, define, cond, case) ;; - List processing patterns (map, filter, reduce, map-indexed) ;; - Component patterns (defaults, nil bodies, map over children) ;; - Special element edge cases (fragments, void attrs, nil content) ;; ========================================================================== ;; -------------------------------------------------------------------------- ;; Nested component rendering ;; -------------------------------------------------------------------------- (defsuite "render-nested-components" (deftest "component calling another component" ;; Inner component renders a span; outer wraps it in a div (let ((html (render-html "(do (defcomp ~inner (&key label) (span label)) (defcomp ~outer (&key text) (div (~inner :label text))) (~outer :text \"hello\"))"))) (assert-true (string-contains? html "
")) (assert-true (string-contains? html "hello")) (assert-true (string-contains? html "
")))) (deftest "three levels of nesting" ;; A → B → C, each wrapping the next (let ((html (render-html "(do (defcomp ~c () (em \"deep\")) (defcomp ~b () (strong (~c))) (defcomp ~a () (p (~b))) (~a))"))) (assert-true (string-contains? html "

")) (assert-true (string-contains? html "")) (assert-true (string-contains? html "deep")) (assert-true (string-contains? html "")) (assert-true (string-contains? html "

")))) (deftest "component with children that are components" ;; ~badge renders as a span; ~toolbar wraps whatever children it gets (let ((html (render-html "(do (defcomp ~badge (&key text) (span :class \"badge\" text)) (defcomp ~toolbar (&rest children) (nav children)) (~toolbar (~badge :text \"Home\") (~badge :text \"About\")))"))) (assert-true (string-contains? html "")))) (deftest "component that wraps children in a div" ;; Classic container pattern: keyword title + arbitrary children (let ((html (render-html "(do (defcomp ~card (&key title &rest children) (div :class \"card\" (h3 title) children)) (~card :title \"My Card\" (p \"First\") (p \"Second\")))"))) (assert-true (string-contains? html "class=\"card\"")) (assert-true (string-contains? html "

My Card

")) (assert-true (string-contains? html "

First

")) (assert-true (string-contains? html "

Second

"))))) ;; -------------------------------------------------------------------------- ;; Dynamic content ;; -------------------------------------------------------------------------- (defsuite "render-dynamic-content" (deftest "let binding computed values" ;; let computes a value and uses it in the rendered output (assert-equal "30" (render-html "(let ((x 10) (y 20)) (span (+ x y)))"))) (deftest "define inside do block" ;; Definitions accumulate across do statements (assert-equal "

hello world

" (render-html "(do (define greeting \"hello\") (define target \"world\") (p (str greeting \" \" target)))"))) (deftest "nested let scoping" ;; Inner let shadows outer binding; outer binding restored after (assert-equal "
innerouter
" (render-html "(do (define label \"outer\") (div (let ((label \"inner\")) (span label)) (span label)))"))) (deftest "cond dispatching different elements" ;; Different cond branches produce different tags (assert-equal "

big

" (render-html "(let ((size \"large\")) (cond (= size \"large\") (h1 \"big\") (= size \"small\") (h6 \"small\") :else (p \"medium\")))")) (assert-equal "
small
" (render-html "(let ((size \"small\")) (cond (= size \"large\") (h1 \"big\") (= size \"small\") (h6 \"small\") :else (p \"medium\")))")) (assert-equal "

medium

" (render-html "(let ((size \"other\")) (cond (= size \"large\") (h1 \"big\") (= size \"small\") (h6 \"small\") :else (p \"medium\")))"))) (deftest "cond dispatching different elements" ;; cond on a value selects between different rendered elements (assert-equal "bold" (render-html "(let ((style \"bold\")) (cond (= style \"bold\") (strong \"bold\") (= style \"italic\") (em \"italic\") :else (span \"normal\")))")) (assert-equal "italic" (render-html "(let ((style \"italic\")) (cond (= style \"bold\") (strong \"bold\") (= style \"italic\") (em \"italic\") :else (span \"normal\")))")) (assert-equal "normal" (render-html "(let ((style \"other\")) (cond (= style \"bold\") (strong \"bold\") (= style \"italic\") (em \"italic\") :else (span \"normal\")))")))) ;; -------------------------------------------------------------------------- ;; List processing patterns ;; -------------------------------------------------------------------------- (defsuite "render-list-patterns" (deftest "map producing li items inside ul" (assert-equal "" (render-html "(ul (map (fn (x) (li x)) (list \"a\" \"b\" \"c\")))"))) (deftest "filter then map inside container" ;; Keep only even numbers, render each as a span (assert-equal "
24
" (render-html "(div (map (fn (x) (span x)) (filter (fn (x) (= (mod x 2) 0)) (list 1 2 3 4 5))))"))) (deftest "reduce building a string inside a span" ;; Join words with a separator via reduce, wrap in span (assert-equal "a-b-c" (render-html "(let ((words (list \"a\" \"b\" \"c\"))) (span (reduce (fn (acc w) (if (= acc \"\") w (str acc \"-\" w))) \"\" words)))"))) (deftest "map-indexed producing numbered items" ;; map-indexed provides both the index and the value (assert-equal "
  1. 1. alpha
  2. 2. beta
  3. 3. gamma
" (render-html "(ol (map-indexed (fn (i x) (li (str (+ i 1) \". \" x))) (list \"alpha\" \"beta\" \"gamma\")))"))) (deftest "nested map (map inside map)" ;; Each outer item produces a ul; inner items produce li (let ((html (render-html "(div (map (fn (row) (ul (map (fn (cell) (li cell)) row))) (list (list \"a\" \"b\") (list \"c\" \"d\"))))"))) (assert-true (string-contains? html "
")) ;; Both inner uls must appear (assert-true (string-contains? html "
  • a
  • ")) (assert-true (string-contains? html "
  • b
  • ")) (assert-true (string-contains? html "
  • c
  • ")) (assert-true (string-contains? html "
  • d
  • ")))) (deftest "empty map produces no children" ;; mapping over an empty list contributes nothing to the parent (assert-equal "" (render-html "(ul (map (fn (x) (li x)) (list)))")))) ;; -------------------------------------------------------------------------- ;; Component patterns ;; -------------------------------------------------------------------------- (defsuite "render-component-patterns" (deftest "component with conditional rendering (when)" ;; when true → renders child; when false → renders nothing (let ((html-on (render-html "(do (defcomp ~toggle (&key active) (div (when active (span \"on\")))) (~toggle :active true))")) (html-off (render-html "(do (defcomp ~toggle (&key active) (div (when active (span \"on\")))) (~toggle :active false))"))) (assert-true (string-contains? html-on "on")) (assert-false (string-contains? html-off "")))) (deftest "component with default keyword value (or pattern)" ;; Missing keyword falls back to default; explicit value overrides it (let ((with-default (render-html "(do (defcomp ~btn (&key label) (button (or label \"Click me\"))) (~btn))")) (with-value (render-html "(do (defcomp ~btn (&key label) (button (or label \"Click me\"))) (~btn :label \"Submit\"))"))) (assert-equal "" with-default) (assert-equal "" with-value))) (deftest "component composing other components" ;; ~page uses ~header and ~footer as sub-components (let ((html (render-html "(do (defcomp ~header () (header (h1 \"Top\"))) (defcomp ~footer () (footer \"Bottom\")) (defcomp ~page () (div (~header) (~footer))) (~page))"))) (assert-true (string-contains? html "
    ")) (assert-true (string-contains? html "

    Top

    ")) (assert-true (string-contains? html "