Implement sx-swap pure tree rewriting and fix handler test infrastructure
Write lib/sx-swap.sx — string-level SX scanner that finds elements by :id and applies swap operations (innerHTML, outerHTML, beforeend, afterbegin, beforebegin, afterend, delete, none). Includes OOB extraction via find-oob-elements/strip-oob/apply-response for out-of-band targeted swaps. Fix &rest varargs bug in test-handlers.sx helper mock — fn doesn't support &rest, so change to positional (name a1 a2) with nil defaults. Fix into branch, add run-handler sx-expr unwrapping. Add missing primitives to run_tests.ml: scope-peek, callable?, make-sx-expr, sx-expr-source, sx-expr?, spread?, call-lambda. These unblock aser-based handler evaluation in tests. Add algebraic integration tests (test-swap-integration.sx) demonstrating the sx1 ⊕(mode,target) sx2 = sx3 pattern with real handler execution. 1219 → 1330 passing tests (+111). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
131
spec/tests/test-sx-swap.sx
Normal file
131
spec/tests/test-sx-swap.sx
Normal file
@@ -0,0 +1,131 @@
|
||||
(defsuite
|
||||
"sx-swap:innerHTML"
|
||||
(deftest
|
||||
"replaces children of target"
|
||||
(let
|
||||
((result (sx-swap "(div :id \"t\" (p \"old\"))" "innerHTML" "t" "(p \"new\")")))
|
||||
(assert-equal result "(div :id \"t\" (p \"new\"))")))
|
||||
(deftest
|
||||
"replaces multiple children"
|
||||
(let
|
||||
((result (sx-swap "(div :id \"t\" (p \"a\") (p \"b\"))" "innerHTML" "t" "(span \"x\")")))
|
||||
(assert-equal result "(div :id \"t\" (span \"x\"))")))
|
||||
(deftest
|
||||
"handles nested target"
|
||||
(let
|
||||
((result (sx-swap "(main (div :id \"t\" (p \"old\")))" "innerHTML" "t" "(p \"new\")")))
|
||||
(assert-equal result "(main (div :id \"t\" (p \"new\")))")))
|
||||
(deftest
|
||||
"preserves attrs"
|
||||
(let
|
||||
((result (sx-swap "(div :id \"t\" :class \"box\" (p \"old\"))" "innerHTML" "t" "(p \"new\")")))
|
||||
(assert-equal result "(div :id \"t\" :class \"box\" (p \"new\"))"))))
|
||||
|
||||
(defsuite
|
||||
"sx-swap:outerHTML"
|
||||
(deftest
|
||||
"replaces entire element"
|
||||
(let
|
||||
((result (sx-swap "(main (div :id \"t\" (p \"old\")) (footer \"f\"))" "outerHTML" "t" "(section \"new\")")))
|
||||
(assert-equal result "(main (section \"new\") (footer \"f\"))")))
|
||||
(deftest
|
||||
"replaces at root"
|
||||
(let
|
||||
((result (sx-swap "(div :id \"t\" (p \"old\"))" "outerHTML" "t" "(span \"new\")")))
|
||||
(assert-equal result "(span \"new\")"))))
|
||||
|
||||
(defsuite
|
||||
"sx-swap:beforeend"
|
||||
(deftest
|
||||
"appends to children"
|
||||
(let
|
||||
((result (sx-swap "(ul :id \"t\" (li \"a\"))" "beforeend" "t" "(li \"b\")")))
|
||||
(assert-equal result "(ul :id \"t\" (li \"a\") (li \"b\"))")))
|
||||
(deftest
|
||||
"appends to empty element"
|
||||
(let
|
||||
((result (sx-swap "(div :id \"t\")" "beforeend" "t" "(p \"new\")")))
|
||||
(assert-equal result "(div :id \"t\" (p \"new\"))"))))
|
||||
|
||||
(defsuite
|
||||
"sx-swap:afterbegin"
|
||||
(deftest
|
||||
"prepends to children"
|
||||
(let
|
||||
((result (sx-swap "(ul :id \"t\" (li \"b\"))" "afterbegin" "t" "(li \"a\")")))
|
||||
(assert-equal result "(ul :id \"t\" (li \"a\") (li \"b\"))"))))
|
||||
|
||||
(defsuite
|
||||
"sx-swap:beforebegin"
|
||||
(deftest
|
||||
"inserts before element"
|
||||
(let
|
||||
((result (sx-swap "(div (p :id \"t\" \"x\"))" "beforebegin" "t" "(hr)")))
|
||||
(assert-equal result "(div (hr)(p :id \"t\" \"x\"))"))))
|
||||
|
||||
(defsuite
|
||||
"sx-swap:afterend"
|
||||
(deftest
|
||||
"inserts after element"
|
||||
(let
|
||||
((result (sx-swap "(div (p :id \"t\" \"x\") (span \"y\"))" "afterend" "t" "(hr)")))
|
||||
(assert-equal result "(div (p :id \"t\" \"x\")(hr) (span \"y\"))"))))
|
||||
|
||||
(defsuite
|
||||
"sx-swap:delete"
|
||||
(deftest
|
||||
"removes element"
|
||||
(let
|
||||
((result (sx-swap "(div (p :id \"t\" \"bye\") (p \"stay\"))" "delete" "t" "")))
|
||||
(assert-true (contains? result "stay"))
|
||||
(assert-false (contains? result "bye")))))
|
||||
|
||||
(defsuite
|
||||
"sx-swap:none"
|
||||
(deftest
|
||||
"returns unchanged"
|
||||
(let
|
||||
((page "(div :id \"t\" (p \"x\"))"))
|
||||
(assert-equal (sx-swap page "none" "t" "(p \"y\")") page))))
|
||||
|
||||
(defsuite
|
||||
"sx-swap:missing-target"
|
||||
(deftest
|
||||
"returns unchanged when id not found"
|
||||
(let
|
||||
((page "(div :id \"other\" (p \"x\"))"))
|
||||
(assert-equal (sx-swap page "innerHTML" "missing" "(p \"y\")") page))))
|
||||
|
||||
(defsuite
|
||||
"sx-swap:oob"
|
||||
(deftest
|
||||
"finds oob elements"
|
||||
(let
|
||||
((src "(<> (p \"main\") (div :id \"oob-t\" :sx-swap-oob \"innerHTML\" (p \"oob\")))"))
|
||||
(let
|
||||
((oobs (find-oob-elements src)))
|
||||
(assert-equal (len oobs) 1)
|
||||
(assert-equal (get (first oobs) "id") "oob-t")
|
||||
(assert-equal (get (first oobs) "mode") "innerHTML"))))
|
||||
(deftest
|
||||
"strips oob from response"
|
||||
(let
|
||||
((src "(<> (p \"main\") (div :id \"oob-t\" :sx-swap-oob \"innerHTML\" (p \"oob\")))"))
|
||||
(let
|
||||
((oobs (find-oob-elements src)))
|
||||
(let
|
||||
((main (strip-oob src oobs)))
|
||||
(assert-true (contains? main "main"))
|
||||
(assert-false (contains? main "oob-t"))))))
|
||||
(deftest
|
||||
"full pipeline applies primary + oob"
|
||||
(let
|
||||
((page "(div (div :id \"a\" (p \"A old\")) (div :id \"b\" (p \"B old\")))")
|
||||
(response
|
||||
"(<> (p \"A new\") (div :id \"b\" :sx-swap-oob \"innerHTML\" (p \"B new\")))"))
|
||||
(let
|
||||
((result (apply-response page response "innerHTML" "a")))
|
||||
(assert-true (contains? result "A new"))
|
||||
(assert-true (contains? result "B new"))
|
||||
(assert-false (contains? result "A old"))
|
||||
(assert-false (contains? result "B old"))))))
|
||||
Reference in New Issue
Block a user