;; ==========================================================================
;; 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 "
"
(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 "
a
b
c
"
(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. alpha
2. beta
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 "