;; ==========================================================================
;; 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 "&lt;script&gt;"))))

  (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))"))))
