Merge sx-tools: test coverage + bug fixes + Playwright fixes

- 7 new test files (~268 tests): stdlib, adapter-html, adapter-dom,
  boot-helpers, page-helpers, layout, tw-layout
- Fix component-pure? transitive scan, render-target crash on unknown
  components, &rest param binding (String vs Symbol), swap! extra args
- Fix 5 Playwright marshes tests: timing + test logic
- 2522/2522 OCaml tests, 173/173 Playwright tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

# Conflicts:
#	shared/static/wasm/sx/orchestration.sxbc
#	shared/static/wasm/sx_browser.bc.js
#	shared/static/wasm/sx_browser.bc.wasm.js
#	sx/sx/not-found.sx
#	tests/playwright/isomorphic.spec.js
This commit is contained in:
2026-04-02 18:59:45 +00:00
29 changed files with 1770 additions and 1359 deletions

View File

@@ -432,15 +432,6 @@
(run-post-render-hooks)
(flush-collected-styles)
(set-timeout (fn () (process-elements nil)) 0)
(dom-listen
(dom-window)
"popstate"
(fn
(e)
(let
((state (host-get e "state"))
(scrollY (if state (or (dict-get state "scrollY") 0) 0)))
(handle-popstate scrollY))))
(dom-set-attr
(host-get (dom-document) "documentElement")
"data-sx-ready"

View File

@@ -358,9 +358,14 @@
(=
(dom-get-attr old-node "data-sx-island")
(dom-get-attr new-node "data-sx-island")))
(do
(let
((old-state (dom-get-attr old-node "data-sx-state"))
(new-state (dom-get-attr new-node "data-sx-state")))
(sync-attrs old-node new-node)
(morph-island-children old-node new-node))
(if
(and new-state (not (= old-state new-state)))
(do (dispose-island old-node) (hydrate-island old-node))
(morph-island-children old-node new-node)))
(or
(not (= (dom-node-type old-node) (dom-node-type new-node)))
(not (= (dom-node-name old-node) (dom-node-name new-node))))

View File

@@ -346,7 +346,51 @@
(resp-ok status get-header text)
(when
resp-ok
(dom-set-inner-html main text)
(let
((ct (or (get-header "content-type") "")))
(if
(contains? ct "text/html")
(let
((parser (host-new "DOMParser"))
(doc (host-call parser "parseFromString" text "text/html"))
(content (host-call doc "querySelector" "#sx-content")))
(if
content
(dom-set-inner-html main (host-get content "innerHTML"))
(dom-set-inner-html main text)))
(let
((container (dom-create-element "div")))
(let
((rendered (sx-render text)))
(when
rendered
(dom-append container rendered)
(process-oob-swaps
container
(fn
(t oob (s :as string))
(dispose-islands-in t)
(swap-dom-nodes
t
(if
(= s "innerHTML")
(children-to-fragment oob)
oob)
s)
(post-swap t)))
(let
((content (select-from-container container "#sx-content")))
(if
content
(do
(dispose-islands-in main)
(dom-set-inner-html main "")
(dom-append main content))
(do
(dispose-islands-in main)
(dom-set-inner-html
main
(dom-get-inner-html container))))))))))
(post-swap main)
(host-call (dom-window) "scrollTo" 0 scroll-y)))
(fn (err) (log-warn (str "fetch-and-restore error: " err))))))

View File

@@ -553,9 +553,7 @@
(and settle-expr (not (empty? settle-expr)))
(let
((exprs (sx-parse settle-expr)))
(for-each
(fn (expr) (eval-expr expr (env-extend (dict))))
exprs))))))
(for-each (fn (expr) (cek-eval expr)) exprs))))))
(define
activate-scripts
@@ -1558,12 +1556,9 @@
(pathname (url-pathname url)))
(when
target
(if
(try-client-route pathname target-sel)
(browser-scroll-to 0 scrollY)
(let
((headers (build-request-headers target "GET" url)))
(fetch-and-restore target url headers scrollY)))))))
(let
((headers (dict "SX-Request" "true")))
(fetch-and-restore target url headers scrollY))))))
(define
engine-init

View File

@@ -489,6 +489,11 @@
(assert-true (contains? result "Success"))
(assert-false (contains? result "Retrying")))))))))
(defcomp
~examples/anim-result
(&key color time)
(div :class color (p (str "Color: " color)) (p (str "Time: " time))))
(defsuite
"swap:animate"
(deftest
@@ -498,10 +503,10 @@
((page "(div :id \"anim-result\")")
(response (run-handler handler:ex-animate)))
(let
((result (sx-swap page "innerHTML" "anim-result" response)))
((result (str (sx-swap page "innerHTML" "anim-result" response))))
(do
(assert-true (contains? result "~anim-result"))
(assert-true (contains? result "12:00:00")))))))
(assert-true (string-contains? result "anim-result"))
(assert-true (string-contains? result "12:00:00")))))))
(defsuite
"swap:inline-edit"
@@ -644,3 +649,28 @@
(do
(assert-true (contains? result "~examples/sync-result"))
(assert-false (contains? result "Searching")))))))
(defsuite
"swap:popstate-oob-nav"
(deftest
"aser preserves sx-swap-oob attribute in OOB elements"
(let
((src (quote (<> (div :id "sx-nav" :sx-swap-oob "innerHTML" (span "Updated Nav")) (div :id "sx-content" (p "Page content"))))))
(let
((result (serialize (aser src))))
(assert-true (contains? result "sx-swap-oob"))
(assert-true (contains? result "innerHTML"))
(assert-true (contains? result "sx-nav"))
(assert-true (contains? result "Updated Nav"))
(assert-true (contains? result "Page content")))))
(deftest
"aser OOB response preserves both targets"
(let
((src (quote (<> (div :id "sx-nav" :sx-swap-oob "innerHTML" (span "Nav A")) (div :id "sidebar" :sx-swap-oob "innerHTML" (span "Sidebar B")) (div :id "sx-content" (p "Main"))))))
(let
((result (serialize (aser src))))
(assert-true (contains? result "sx-nav"))
(assert-true (contains? result "sidebar"))
(assert-true (contains? result "Nav A"))
(assert-true (contains? result "Sidebar B"))
(assert-true (contains? result "Main"))))))