Files
rose-ash/web/tests/test-swap-integration.sx
giles aa508bad77 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>
2026-04-01 18:00:51 +00:00

205 lines
5.7 KiB
Plaintext

(define _mock-form (dict))
(define _mock-args (dict))
(define _mock-state (dict))
(define _mock-body "")
(define _mock-content-type "")
(define _mock-headers (dict))
(define _mock-now "12:00:00")
(define
reset-mocks!
(fn
()
(do
(set! _mock-form (dict))
(set! _mock-args (dict))
(set! _mock-state (dict))
(set! _mock-body "")
(set! _mock-content-type "")
(set! _mock-headers (dict)))))
(define
helper
(fn
(name a1 a2)
(cond
(= name "request-form")
(let
((key (or a1 "")) (default (if (nil? a2) "" a2)))
(let ((val (get _mock-form key))) (if (nil? val) default val)))
(= name "request-arg")
(let
((key (or a1 "")) (default a2))
(let ((val (get _mock-args key))) (if (nil? val) default val)))
(= name "state-get")
(let
((key (or a1 "")) (default a2))
(let ((val (get _mock-state key))) (if (nil? val) default val)))
(= name "state-set!")
(do (set! _mock-state (assoc _mock-state a1 a2)) nil)
(= name "now")
_mock-now
(= name "component-source")
(str "(defcomp " a1 " () (div))")
(= name "request-json")
_mock-body
(= name "request-content-type")
_mock-content-type
(= name "request-form-list")
(or (get _mock-form a1) (list))
(= name "request-args-all")
_mock-args
(= name "request-headers-all")
_mock-headers
(= name "request-form-all")
_mock-form
(= name "request-header")
(or (get _mock-headers a1) a2)
(= name "request-file-name")
(or (get _mock-form a1) "")
(= name "into")
(let
((coll (if (nil? a2) a1 a2)))
(if
(dict? coll)
(map (fn (key) (list key (get coll key))) (keys coll))
(if (nil? coll) (list) coll)))
:else nil)))
(define sleep (fn (ms) nil))
(define set-response-status (fn (code) nil))
(define json-encode (fn (val) (inspect val)))
(define
run-handler
(fn
(hdef)
(let
((result (aser (get hdef "body") (get hdef "closure"))))
(if
(sx-expr? result)
(sx-expr-source result)
(if (string? result) result (str result))))))
(defsuite
"swap:click-to-load"
(deftest
"innerHTML replaces target content"
(reset-mocks!)
(let
((page "(div :id \"click-result\" (p \"Click the button\"))")
(response (run-handler handler:ex-click)))
(let
((result (sx-swap page "innerHTML" "click-result" response)))
(do
(assert-true (contains? result "~examples/click-result"))
(assert-true (contains? result "12:00:00"))
(assert-false (contains? result "Click the button")))))))
(defsuite
"swap:form-submission"
(deftest
"innerHTML replaces with greeting"
(reset-mocks!)
(set! _mock-form {:name "Alice"})
(let
((page "(div :id \"form-result\" (p \"Submit the form\"))")
(response (run-handler handler:ex-form)))
(let
((result (sx-swap page "innerHTML" "form-result" response)))
(do
(assert-true (contains? result "Alice"))
(assert-false (contains? result "Submit the form")))))))
(defsuite
"swap:polling"
(deftest
"innerHTML shows counter after increment"
(reset-mocks!)
(let
((page "(div :id \"poll-result\" (p \"Waiting...\"))")
(response (run-handler handler:ex-poll)))
(let
((result (sx-swap page "innerHTML" "poll-result" response)))
(do
(assert-true (contains? result "1"))
(assert-false (contains? result "Waiting")))))))
(defsuite
"swap:oob-updates"
(deftest
"primary + OOB both update"
(reset-mocks!)
(let
((page "(div (div :id \"oob-box-a\" (p \"A old\")) (div :id \"oob-box-b\" (p \"B old\")))")
(response (run-handler handler:ex-oob)))
(let
((result (apply-response page response "innerHTML" "oob-box-a")))
(do
(assert-true (contains? result "Box A updated!"))
(assert-true (contains? result "Box B updated!"))
(assert-false (contains? result "A old"))
(assert-false (contains? result "B old")))))))
(defsuite
"swap:search"
(deftest
"innerHTML replaces search results"
(reset-mocks!)
(set! _mock-args {:q "Python"})
(let
((page "(div :id \"search-result\" (p \"Type to search\"))")
(response (run-handler handler:ex-search)))
(let
((result (sx-swap page "innerHTML" "search-result" response)))
(do
(assert-true (contains? result "Python"))
(assert-false (contains? result "Type to search")))))))
(defsuite
"swap:delete-row"
(deftest
"outerHTML removes element"
(let
((page "(table (tr :id \"row-1\" (td \"Item 1\")) (tr :id \"row-2\" (td \"Item 2\")))")
(response ""))
(let
((result (sx-swap page "outerHTML" "row-1" response)))
(do
(assert-false (contains? result "Item 1"))
(assert-true (contains? result "Item 2")))))))
(defsuite
"swap:beforeend"
(deftest
"appends new item to list"
(let
((page "(ul :id \"items\" (li \"first\"))") (response "(li \"second\")"))
(let
((result (sx-swap page "beforeend" "items" response)))
(do
(assert-true (contains? result "first"))
(assert-true (contains? result "second")))))))
(defsuite
"swap:state-across-calls"
(deftest
"counter increments across handler calls"
(reset-mocks!)
(let
((r1 (run-handler handler:ex-poll)))
(let
((r2 (run-handler handler:ex-poll)))
(let
((page "(div :id \"counter\" (span \"0\"))")
(result (sx-swap page "innerHTML" "counter" r2)))
(assert-true (contains? result "2")))))))