diff --git a/plans/agent-briefings/sx-gate-loop.md b/plans/agent-briefings/sx-gate-loop.md index 5a9a8f0d..29c1a97e 100644 --- a/plans/agent-briefings/sx-gate-loop.md +++ b/plans/agent-briefings/sx-gate-loop.md @@ -76,7 +76,10 @@ Pin each confirmed-and-fixed finding with a minimal repro. Add suites to logging; 5 pins incl. the S10 map-over-perform probe (CEK keeps all elements — the drop class is serving-JIT-side). Runner-only (needs cek-* driver bindings) -- [ ] C23 — adapter-dom render-output tests +- [x] C23 — adapter-dom render-output tests + (`web/tests/test-adapter-dom-render.sx`, 8 tests vs runner mock DOM; + follow-up depth still open: boolean attrs, on-*/bind/ref/key, + reactive attrs, hydration cursor) ### D. WASM corpus runner - [ ] F2 — promote conformance's `run_wasm.js` prototype into CI @@ -91,6 +94,20 @@ Pin each confirmed-and-fixed finding with a minimal repro. Add suites to ## Progress log (newest first) +- 2026-07-04 — **C23 adapter-dom render-output tests (item C.4) — section C + COMPLETE**. Key discovery: the "browser-only" exclusion of adapter-dom + testing is FALSE for render output — `(import (web adapter-dom))` + disk-resolves in the OCaml runner and `render-to-dom` works against its + mock DOM (dom-* → host-* → mock elements). New + `web/tests/test-adapter-dom-render.sx` (8 tests): tag/text-child-node, + class+id, ordered children, void element, when-false empty FRAGMENT, + when-true branch-in-fragment, map N-children-in-fragment, if inlines + branch. Probed the adapter's output contract first (text = nodeType-3 + child; control flow = FRAGMENT wrapper; if inlines). Auto-included in + default runs (not on the exclusion list) — first render-output coverage + of the 1512-line adapter in the standard gate. Follow-up depth (boolean + attrs, on-*/bind/ref/key, reactive, hydration) noted on the checklist. + 254/0 standalone. Test-only. - 2026-07-04 — **C21 perform-mode harness (item C.3)**. Added `harness-run-perform` to spec/harness.sx (exported): drives `make-cek-state`/`cek-step-loop`, services each diff --git a/web/tests/test-adapter-dom-render.sx b/web/tests/test-adapter-dom-render.sx new file mode 100644 index 00000000..646c7eff --- /dev/null +++ b/web/tests/test-adapter-dom-render.sx @@ -0,0 +1,93 @@ +;; ========================================================================== +;; test-adapter-dom-render.sx — C23 (W14): actual render-OUTPUT tests for +;; the DOM adapter. The pre-existing test-adapter-dom.sx asserts membership +;; predicates only ("if is a render form") — zero tests inspected what +;; render-to-dom actually builds, leaving the 1512-line adapter the +;; thinnest-tested relative to size (hosts.md C23). +;; +;; Runs against the OCaml runner's mock DOM (host-* primitives) through the +;; real (web adapter-dom) library, disk-resolved by import. +;; +;; Adapter output contract (probed 2026-07-04): +;; - text renders as a CHILD TEXT NODE (nodeType 3, textContent set); +;; the parent's own textContent prop stays "" in the mock +;; - control-flow forms (when/map/...) wrap their output in a FRAGMENT +;; child; `if` inlines the chosen branch directly +;; ========================================================================== +(import (web adapter-dom)) + +(defsuite + "adapter-dom-render-output" + (deftest + "simple element: tag, text child node" + (let + ((el (render-to-dom (quote (div "hello")) (make-env)))) + (assert= (dom-node-name el) "DIV") + (let + ((kids (dom-get-prop el "children"))) + (assert= (len kids) 1) + (assert= (dom-get-prop (first kids) "nodeType") 3) + (assert= (dom-text-content (first kids)) "hello")))) + (deftest + "class and id land on the element" + (let + ((el (render-to-dom (quote (div :class "card" :id "main" "x")) (make-env)))) + (assert= (dom-get-prop el "className") "card") + (assert + (or + (= (dom-get-prop el "id") "main") + (= (get (dom-get-prop el "attributes") "id") "main")) + "id not set as prop or attribute"))) + (deftest + "nested children are appended in order" + (let + ((el (render-to-dom (quote (ul (li "a") (li "b") (li "c"))) (make-env)))) + (let + ((kids (dom-get-prop el "children"))) + (assert= (len kids) 3) + (assert= (dom-node-name (first kids)) "LI") + (assert= + (dom-text-content + (first (dom-get-prop (nth kids 2) "children"))) + "c")))) + (deftest + "void element renders with no children" + (let + ((el (render-to-dom (quote (input :type "text")) (make-env)))) + (assert= (dom-node-name el) "INPUT") + (assert= (len (dom-get-prop el "children")) 0))) + (deftest + "when false yields an empty fragment" + (let + ((el (render-to-dom (quote (div (when false (span "hidden")))) (make-env)))) + (let + ((kids (dom-get-prop el "children"))) + (assert= (len kids) 1) + (assert= (dom-node-name (first kids)) "FRAGMENT") + (assert= (len (dom-get-prop (first kids) "children")) 0)))) + (deftest + "when true renders the branch inside its fragment" + (let + ((el (render-to-dom (quote (div (when true (span "shown")))) (make-env)))) + (let + ((frag (first (dom-get-prop el "children")))) + (assert= (dom-node-name frag) "FRAGMENT") + (assert= + (dom-node-name (first (dom-get-prop frag "children"))) + "SPAN")))) + (deftest + "map render form produces one child per element inside its fragment" + (let + ((el (render-to-dom (quote (ul (map (fn (x) (li x)) (list "1" "2" "3")))) (make-env)))) + (let + ((frag (first (dom-get-prop el "children")))) + (assert= (dom-node-name frag) "FRAGMENT") + (let + ((items (dom-get-prop frag "children"))) + (assert= (len items) 3) + (assert= (dom-node-name (first items)) "LI"))))) + (deftest + "if render form inlines the chosen branch directly" + (let + ((el (render-to-dom (quote (div (if true (b "yes") (i "no")))) (make-env)))) + (assert= (dom-node-name (first (dom-get-prop el "children"))) "B"))))