The cond special form misclassified Clojure-style as scheme-style when the first test was a 2-element list like (nil? x) — treating it as a scheme clause ((test body)) instead of a function call. Define cond-scheme? using every? to check ALL clauses, fix eval.sx sf-cond and render.sx eval-cond, rewrite engine.sx parse-time/filter-params as nested if to avoid the ambiguity, add regression tests across eval/ render/aser specs. 378/378 tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
231 lines
9.1 KiB
Plaintext
231 lines
9.1 KiB
Plaintext
;; ==========================================================================
|
|
;; test-render.sx — Tests for the HTML rendering adapter
|
|
;;
|
|
;; Requires: test-framework.sx loaded first.
|
|
;; Modules tested: render.sx, adapter-html.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.
|
|
;; (This is a test-only convenience that wraps parse + render-to-html.)
|
|
;; ==========================================================================
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Basic element rendering
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "render-elements"
|
|
(deftest "simple div"
|
|
(assert-equal "<div>hello</div>"
|
|
(render-html "(div \"hello\")")))
|
|
|
|
(deftest "nested elements"
|
|
(assert-equal "<div><span>hi</span></div>"
|
|
(render-html "(div (span \"hi\"))")))
|
|
|
|
(deftest "multiple children"
|
|
(assert-equal "<div><p>a</p><p>b</p></div>"
|
|
(render-html "(div (p \"a\") (p \"b\"))")))
|
|
|
|
(deftest "text content"
|
|
(assert-equal "<p>hello world</p>"
|
|
(render-html "(p \"hello\" \" world\")")))
|
|
|
|
(deftest "number content"
|
|
(assert-equal "<span>42</span>"
|
|
(render-html "(span 42)"))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Attributes
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "render-attrs"
|
|
(deftest "string attribute"
|
|
(let ((html (render-html "(div :id \"main\" \"content\")")))
|
|
(assert-true (string-contains? html "id=\"main\""))
|
|
(assert-true (string-contains? html "content"))))
|
|
|
|
(deftest "class attribute"
|
|
(let ((html (render-html "(div :class \"foo bar\" \"x\")")))
|
|
(assert-true (string-contains? html "class=\"foo bar\""))))
|
|
|
|
(deftest "multiple attributes"
|
|
(let ((html (render-html "(a :href \"/home\" :class \"link\" \"Home\")")))
|
|
(assert-true (string-contains? html "href=\"/home\""))
|
|
(assert-true (string-contains? html "class=\"link\""))
|
|
(assert-true (string-contains? html "Home")))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Void elements
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "render-void"
|
|
(deftest "br is self-closing"
|
|
(assert-equal "<br />" (render-html "(br)")))
|
|
|
|
(deftest "img with attrs"
|
|
(let ((html (render-html "(img :src \"pic.jpg\" :alt \"A pic\")")))
|
|
(assert-true (string-contains? html "<img"))
|
|
(assert-true (string-contains? html "src=\"pic.jpg\""))
|
|
(assert-true (string-contains? html "/>"))
|
|
;; void elements should not have a closing tag
|
|
(assert-false (string-contains? html "</img>"))))
|
|
|
|
(deftest "input is self-closing"
|
|
(let ((html (render-html "(input :type \"text\" :name \"q\")")))
|
|
(assert-true (string-contains? html "<input"))
|
|
(assert-true (string-contains? html "/>")))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Boolean attributes
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "render-boolean-attrs"
|
|
(deftest "true boolean attr emits name only"
|
|
(let ((html (render-html "(input :disabled true :type \"text\")")))
|
|
(assert-true (string-contains? html "disabled"))
|
|
;; Should NOT have disabled="true"
|
|
(assert-false (string-contains? html "disabled=\""))))
|
|
|
|
(deftest "false boolean attr omitted"
|
|
(let ((html (render-html "(input :disabled false :type \"text\")")))
|
|
(assert-false (string-contains? html "disabled")))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Fragments
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "render-fragments"
|
|
(deftest "fragment renders children without wrapper"
|
|
(assert-equal "<p>a</p><p>b</p>"
|
|
(render-html "(<> (p \"a\") (p \"b\"))")))
|
|
|
|
(deftest "empty fragment"
|
|
(assert-equal ""
|
|
(render-html "(<>)"))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; HTML escaping
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "render-escaping"
|
|
(deftest "text content is escaped"
|
|
(let ((html (render-html "(p \"<script>alert(1)</script>\")")))
|
|
(assert-false (string-contains? html "<script>"))
|
|
(assert-true (string-contains? html "<script>"))))
|
|
|
|
(deftest "attribute values are escaped"
|
|
(let ((html (render-html "(div :title \"a\\\"b\" \"x\")")))
|
|
(assert-true (string-contains? html "title=")))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Control flow in render context
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "render-control-flow"
|
|
(deftest "if renders correct branch"
|
|
(assert-equal "<p>yes</p>"
|
|
(render-html "(if true (p \"yes\") (p \"no\"))"))
|
|
(assert-equal "<p>no</p>"
|
|
(render-html "(if false (p \"yes\") (p \"no\"))")))
|
|
|
|
(deftest "when renders or skips"
|
|
(assert-equal "<p>ok</p>"
|
|
(render-html "(when true (p \"ok\"))"))
|
|
(assert-equal ""
|
|
(render-html "(when false (p \"ok\"))")))
|
|
|
|
(deftest "map renders list"
|
|
(assert-equal "<li>1</li><li>2</li><li>3</li>"
|
|
(render-html "(map (fn (x) (li x)) (list 1 2 3))")))
|
|
|
|
(deftest "let in render context"
|
|
(assert-equal "<p>hello</p>"
|
|
(render-html "(let ((x \"hello\")) (p x))")))
|
|
|
|
(deftest "cond with 2-element predicate test"
|
|
;; Regression: cond misclassifies (nil? x) as scheme-style clause.
|
|
(assert-equal "<p>yes</p>"
|
|
(render-html "(cond (nil? nil) (p \"yes\") :else (p \"no\"))"))
|
|
(assert-equal "<p>no</p>"
|
|
(render-html "(cond (nil? \"x\") (p \"yes\") :else (p \"no\"))")))
|
|
|
|
(deftest "let preserves outer scope bindings"
|
|
;; Regression: process-bindings must preserve parent env scope chain.
|
|
;; Using merge() on Env objects returns empty dict (Env is not dict subclass).
|
|
(assert-equal "<p>outer</p>"
|
|
(render-html "(do (define theme \"outer\") (let ((x 1)) (p theme)))")))
|
|
|
|
(deftest "nested let preserves outer scope"
|
|
(assert-equal "<div><span>hello</span><span>world</span></div>"
|
|
(render-html "(do (define a \"hello\")
|
|
(define b \"world\")
|
|
(div (let ((x 1)) (span a))
|
|
(let ((y 2)) (span b))))"))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Component rendering
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "render-components"
|
|
(deftest "component with keyword args"
|
|
(assert-equal "<h1>Hello</h1>"
|
|
(render-html "(do (defcomp ~title (&key text) (h1 text)) (~title :text \"Hello\"))")))
|
|
|
|
(deftest "component with children"
|
|
(let ((html (render-html "(do (defcomp ~box (&key &rest children) (div :class \"box\" children)) (~box (p \"inside\")))")))
|
|
(assert-true (string-contains? html "class=\"box\""))
|
|
(assert-true (string-contains? html "<p>inside</p>")))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Map/filter producing multiple children (aser-adjacent regression tests)
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "render-map-children"
|
|
(deftest "map producing multiple children inside tag"
|
|
(assert-equal "<ul><li>a</li><li>b</li><li>c</li></ul>"
|
|
(render-html "(do (define items (list \"a\" \"b\" \"c\"))
|
|
(ul (map (fn (x) (li x)) items)))")))
|
|
|
|
(deftest "map with other siblings"
|
|
(assert-equal "<ul><li>first</li><li>a</li><li>b</li></ul>"
|
|
(render-html "(do (define items (list \"a\" \"b\"))
|
|
(ul (li \"first\") (map (fn (x) (li x)) items)))")))
|
|
|
|
(deftest "filter with nil results inside tag"
|
|
(assert-equal "<ul><li>a</li><li>c</li></ul>"
|
|
(render-html "(do (define items (list \"a\" nil \"c\"))
|
|
(ul (map (fn (x) (li x))
|
|
(filter (fn (x) (not (nil? x))) items))))")))
|
|
|
|
(deftest "nested map inside let"
|
|
(assert-equal "<div><span>1</span><span>2</span></div>"
|
|
(render-html "(let ((nums (list 1 2)))
|
|
(div (map (fn (n) (span n)) nums)))")))
|
|
|
|
(deftest "component with &rest receiving mapped results"
|
|
(let ((html (render-html "(do (defcomp ~list-box (&key &rest children) (div :class \"lb\" children))
|
|
(define items (list \"x\" \"y\"))
|
|
(~list-box (map (fn (x) (p x)) items)))")))
|
|
(assert-true (string-contains? html "class=\"lb\""))
|
|
(assert-true (string-contains? html "<p>x</p>"))
|
|
(assert-true (string-contains? html "<p>y</p>"))))
|
|
|
|
(deftest "map-indexed renders with index"
|
|
(assert-equal "<li>0: a</li><li>1: b</li>"
|
|
(render-html "(map-indexed (fn (i x) (li (str i \": \" x))) (list \"a\" \"b\"))")))
|
|
|
|
(deftest "for-each renders each item"
|
|
(assert-equal "<p>1</p><p>2</p>"
|
|
(render-html "(for-each (fn (x) (p x)) (list 1 2))"))))
|