;; ==========================================================================
;; 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"
    (assert-equal "<span>World</span>"
      (render-html "(do (defcomp ~k-single (&key title) (span title)) (~k-single :title \"World\"))")))

  (deftest "multiple &key params"
    (assert-equal "<span>Ada Lovelace</span>"
      (render-html "(do (defcomp ~k-multi (&key first last) (span (str first \" \" last)))
                        (~k-multi :first \"Ada\" :last \"Lovelace\"))")))

  (deftest "missing &key param is nil"
    ;; When subtitle is nil, the span should be empty
    (assert-equal "<span></span>"
      (render-html "(do (defcomp ~k-missing (&key title subtitle) (span (or subtitle \"\")))
                        (~k-missing :title \"Only title\"))")))

  (deftest "&key param default via or"
    (let ((custom (render-html "(do (defcomp ~k-def (&key label) (span (or label \"default-label\")))
                                    (~k-def :label \"custom\"))"))
          (default (render-html "(do (defcomp ~k-def2 (&key label) (span (or label \"default-label\")))
                                     (~k-def2))")))
      (assert-equal "<span>custom</span>" custom)
      (assert-equal "<span>default-label</span>" default)))

  (deftest "&key params can be numbers"
    (assert-equal "<span>84</span>"
      (render-html "(do (defcomp ~k-num (&key value) (span (* value 2)))
                        (~k-num :value 42))")))

  (deftest "&key params can be lists"
    (assert-equal "<span>3</span>"
      (render-html "(do (defcomp ~k-list (&key items) (span (len items)))
                        (~k-list :items (list \"a\" \"b\" \"c\")))"))))


;; --------------------------------------------------------------------------
;; Rest / children convention
;; --------------------------------------------------------------------------

(defsuite "defcomp-rest-children"
  (deftest "&rest captures positional args as content"
    (let ((html (render-html "(do (defcomp ~r-basic (&rest children) (div children))
                                  (~r-basic \"a\" \"b\" \"c\"))")))
      (assert-true (string-contains? html "a"))
      (assert-true (string-contains? html "b"))
      (assert-true (string-contains? html "c"))))

  (deftest "&rest with &key separates keywords from positional"
    (let ((html (render-html "(do (defcomp ~r-mixed (&key title &rest children)
                                    (div (h2 title) children))
                                  (~r-mixed :title \"T\" (p \"c1\") (p \"c2\")))")))
      (assert-true (string-contains? html "<h2>T</h2>"))
      (assert-true (string-contains? html "<p>c1</p>"))
      (assert-true (string-contains? html "<p>c2</p>"))))

  (deftest "empty children when no positional args provided"
    (assert-equal "<div></div>"
      (render-html "(do (defcomp ~r-empty (&rest children) (div children)) (~r-empty))")))

  (deftest "multiple children rendered in order"
    (let ((html (render-html "(do (defcomp ~r-order (&rest children) (ul children))
                                  (~r-order (li \"x\") (li \"y\") (li \"z\")))")))
      (assert-true (string-contains? html "<li>x</li>"))
      (assert-true (string-contains? html "<li>y</li>"))
      (assert-true (string-contains? html "<li>z</li>")))))


;; --------------------------------------------------------------------------
;; Component rendering to HTML
;; --------------------------------------------------------------------------

(defsuite "defcomp-rendering"
  (deftest "simplest component renders to HTML"
    (assert-equal "<p>hello</p>"
      (render-html "(do (defcomp ~r-simple () (p \"hello\")) (~r-simple))")))

  (deftest "component with &key renders keyword arg value"
    (assert-equal "<h1>Greetings</h1>"
      (render-html "(do (defcomp ~r-title (&key text) (h1 text))
                        (~r-title :text \"Greetings\"))")))

  (deftest "component with multiple &key args"
    (let ((html (render-html
                  "(do (defcomp ~r-card (&key title subtitle)
                         (div :class \"card\" (h2 title) (p subtitle)))
                       (~r-card :title \"Hi\" :subtitle \"Sub\"))")))
      (assert-true (string-contains? html "class=\"card\""))
      (assert-true (string-contains? html "<h2>Hi</h2>"))
      (assert-true (string-contains? html "<p>Sub</p>"))))

  (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 "<div>"))
      (assert-true (string-contains? html "<span>nested</span>"))))

  (deftest "component with children rendered inside wrapper"
    (let ((html (render-html
                  "(do (defcomp ~r-box (&key &rest children)
                         (div :class \"box\" children))
                       (~r-box (p \"inside\")))")))
      (assert-true (string-contains? html "class=\"box\""))
      (assert-true (string-contains? html "<p>inside</p>"))))

  (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    "<span>visible</span>"))
      (assert-false (string-contains? html-without "<span>"))))

  (deftest "component with conditional rendering via if"
    (assert-equal "<p>yes</p>"
      (render-html "(do (defcomp ~r-if (&key flag)
                           (if flag (p \"yes\") (p \"no\")))
                         (~r-if :flag true))"))
    (assert-equal "<p>no</p>"
      (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 "<span>fallback</span>"
      (render-html "(do (defcomp ~r-default (&key label)
                           (span (or label \"fallback\")))
                         (~r-default))"))
    (assert-equal "<span>given</span>"
      (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 "<li>a</li>"))
      (assert-true (string-contains? html "<li>b</li>"))
      (assert-true (string-contains? html "<li>c</li>")))))
