(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 random-int (fn (lo hi) lo)) (define run-handler (fn (hdef) (do (let ((params (get hdef "params")) (closure (get hdef "closure"))) (when (and params (not (empty? params))) (for-each (fn (p) (let ((val (get _mock-args p))) (env-bind! closure p (or val nil)))) params))) (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:infinite-scroll" (deftest "page 1 appends items to container" (reset-mocks!) (set! _mock-args {:page "1"}) (let ((page "(div :id \"scroll-items\" (div \"Item 0 — seed\"))") (response (run-handler handler:ex-scroll))) (let ((result (sx-swap page "beforeend" "scroll-items" response))) (do (assert-true (contains? result "Item 0")) (assert-true (contains? result "Item 1")) (assert-true (contains? result "page 1")) (assert-true (contains? result "scroll-sentinel")))))) (deftest "page 2 appends more items" (reset-mocks!) (set! _mock-args {:page "2"}) (let ((page "(div :id \"scroll-items\" (div \"page 1 items\"))") (response (run-handler handler:ex-scroll))) (let ((result (sx-swap page "beforeend" "scroll-items" response))) (do (assert-true (contains? result "page 1 items")) (assert-true (contains? result "Item 6")) (assert-true (contains? result "page 2")))))) (deftest "last page has no sentinel" (reset-mocks!) (set! _mock-args {:page "6"}) (let ((page "(div :id \"scroll-items\")") (response (run-handler handler:ex-scroll))) (let ((result (sx-swap page "beforeend" "scroll-items" response))) (do (assert-true (contains? result "All items loaded")) (assert-false (contains? result "scroll-sentinel"))))))) (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"))))))) (defsuite "swap:dashboard" (deftest "innerHTML replaces with stats" (reset-mocks!) (let ((page "(div :id \"dash-content\" (p \"Loading...\"))") (response (run-handler handler:ex-dashboard))) (let ((result (sx-swap page "innerHTML" "dash-content" response))) (do (assert-true (contains? result "142")) (assert-true (contains? result "Revenue")) (assert-false (contains? result "Loading"))))))) (defsuite "swap:lazy-load" (deftest "innerHTML replaces placeholder" (reset-mocks!) (let ((page "(div :id \"lazy-target\" (p \"Loading...\"))") (response (run-handler handler:ex-lazy))) (let ((result (sx-swap page "innerHTML" "lazy-target" response))) (do (assert-true (contains? result "~examples/lazy-result")) (assert-true (contains? result "12:00:00")) (assert-false (contains? result "Loading"))))))) (defsuite "swap:dialog" (deftest "innerHTML opens modal" (reset-mocks!) (let ((page "(div :id \"dialog-target\")") (response (run-handler handler:ex-dialog))) (let ((result (sx-swap page "innerHTML" "dialog-target" response))) (do (assert-true (contains? result "~examples/dialog-modal")) (assert-true (contains? result "Confirm Action")))))) (deftest "innerHTML closes modal" (reset-mocks!) (let ((page "(div :id \"dialog-target\" (~examples/dialog-modal))") (response (run-handler handler:ex-dialog-close))) (let ((result (sx-swap page "innerHTML" "dialog-target" response))) (assert-false (contains? result "dialog-modal")))))) (defsuite "swap:keyboard" (deftest "dispatches action for known key" (reset-mocks!) (set! _mock-args {:key "s"}) (let ((page "(div :id \"kbd-result\" (p \"Press a key\"))") (response (run-handler handler:ex-keyboard))) (let ((result (sx-swap page "innerHTML" "kbd-result" response))) (do (assert-true (contains? result "Search panel activated")) (assert-false (contains? result "Press a key")))))) (deftest "shows unknown for unmapped key" (reset-mocks!) (set! _mock-args {:key "z"}) (let ((page "(div :id \"kbd-result\")") (response (run-handler handler:ex-keyboard))) (let ((result (sx-swap page "innerHTML" "kbd-result" response))) (assert-true (contains? result "Unknown key: z")))))) (defsuite "swap:validate" (deftest "empty email shows required" (reset-mocks!) (set! _mock-args {:email ""}) (let ((page "(div :id \"validate-result\")") (response (run-handler handler:ex-validate))) (let ((result (sx-swap page "innerHTML" "validate-result" response))) (assert-true (contains? result "Email is required"))))) (deftest "missing @ shows format error" (reset-mocks!) (set! _mock-args {:email "bad"}) (let ((page "(div :id \"validate-result\")") (response (run-handler handler:ex-validate))) (let ((result (sx-swap page "innerHTML" "validate-result" response))) (assert-true (contains? result "Invalid email format"))))) (deftest "valid email shows ok" (reset-mocks!) (set! _mock-args {:email "new@example.com"}) (let ((page "(div :id \"validate-result\")") (response (run-handler handler:ex-validate))) (let ((result (sx-swap page "innerHTML" "validate-result" response))) (assert-true (contains? result "~examples/validation-ok")))))) (defsuite "swap:validate-submit" (deftest "valid email submits" (reset-mocks!) (set! _mock-form {:email "hi@example.com"}) (let ((page "(div :id \"validate-form-result\")") (response (run-handler handler:ex-validate-submit))) (let ((result (sx-swap page "innerHTML" "validate-form-result" response))) (assert-true (contains? result "hi@example.com"))))) (deftest "empty email rejects" (reset-mocks!) (set! _mock-form {:email ""}) (let ((page "(div :id \"validate-form-result\")") (response (run-handler handler:ex-validate-submit))) (let ((result (sx-swap page "innerHTML" "validate-form-result" response))) (assert-true (contains? result "valid email")))))) (defsuite "swap:dependent-select" (deftest "returns options for category" (reset-mocks!) (set! _mock-args {:category "Languages"}) (let ((page "(select :id \"values-result\")") (response (run-handler handler:ex-values))) (let ((result (sx-swap page "innerHTML" "values-result" response))) (do (assert-true (contains? result "Python")) (assert-true (contains? result "Rust")))))) (deftest "empty category returns no items" (reset-mocks!) (set! _mock-args {:category "Nonexistent"}) (let ((page "(select :id \"values-result\")") (response (run-handler handler:ex-values))) (let ((result (sx-swap page "innerHTML" "values-result" response))) (assert-true (contains? result "No items")))))) (defsuite "swap:form-reset" (deftest "echoes submitted message" (reset-mocks!) (set! _mock-form {:message "Hello world"}) (let ((page "(div :id \"reset-result\")") (response (run-handler handler:ex-reset-submit))) (let ((result (sx-swap page "innerHTML" "reset-result" response))) (assert-true (contains? result "Hello world")))))) (defsuite "swap:swap-modes-log" (deftest "beforeend appends log entry with OOB counter" (reset-mocks!) (set! _mock-args {:mode "beforeend"}) (let ((page "(div (div :id \"swap-log\") (span :id \"swap-counter\" \"Count: 0\"))") (response (run-handler handler:ex-swap-log))) (let ((result (apply-response page response "beforeend" "swap-log"))) (do (assert-true (contains? result "beforeend")) (assert-true (contains? result "Count: 1"))))))) (defsuite "swap:json-echo" (deftest "echoes content type and body" (reset-mocks!) (set! _mock-body "{\"key\":\"val\"}") (set! _mock-content-type "application/json") (let ((page "(div :id \"json-result\")") (response (run-handler handler:ex-json-echo))) (let ((result (sx-swap page "innerHTML" "json-result" response))) (do (assert-true (contains? result "application/json")) (assert-true (contains? result "~examples/json-result"))))))) (defsuite "swap:retry" (deftest "first two calls return empty (503)" (reset-mocks!) (let ((r1 (run-handler handler:ex-flaky))) (let ((r2 (run-handler handler:ex-flaky))) (do (assert-equal r1 "") (assert-equal r2 ""))))) (deftest "third call succeeds" (reset-mocks!) (let ((r1 (run-handler handler:ex-flaky))) (let ((r2 (run-handler handler:ex-flaky))) (let ((r3 (run-handler handler:ex-flaky))) (let ((page "(div :id \"retry-result\" (p \"Retrying...\"))") (result (sx-swap page "innerHTML" "retry-result" r3))) (do (assert-true (contains? result "~examples/retry-result")) (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 "returns color and timestamp" (reset-mocks!) (let ((page "(div :id \"anim-result\")") (response (run-handler handler:ex-animate))) (let ((result (str (sx-swap page "innerHTML" "anim-result" response)))) (do (assert-true (string-contains? result "anim-result")) (assert-true (string-contains? result "12:00:00"))))))) (defsuite "swap:inline-edit" (deftest "edit form loads current value" (reset-mocks!) (set! _mock-args {:value "Hello" :id "42"}) (let ((page "(div :id \"edit-target\" (span \"Hello\"))") (response (run-handler handler:ex-edit-form))) (let ((result (sx-swap page "innerHTML" "edit-target" response))) (assert-true (contains? result "~examples/inline-edit-form"))))) (deftest "save returns updated view" (reset-mocks!) (set! _mock-form {:value "Updated"}) (let ((page "(div :id \"edit-target\" (input))") (response (run-handler handler:ex-edit-save))) (let ((result (sx-swap page "innerHTML" "edit-target" response))) (assert-true (contains? result "Updated"))))) (deftest "cancel restores original" (reset-mocks!) (set! _mock-args {:value "Original" :id "42"}) (let ((page "(div :id \"edit-target\" (input))") (response (run-handler handler:ex-edit-cancel))) (let ((result (sx-swap page "innerHTML" "edit-target" response))) (assert-true (contains? result "Original")))))) (defsuite "swap:row-editing" (deftest "form loads row data" (reset-mocks!) (set! _mock-args {:row-id "r1"}) (set! _mock-state {:ex-row-r1 {:stock "5" :id "r1" :price "$10" :name "Widget"}}) (let ((page "(tr :id \"row-r1\" (td \"Widget\"))") (response (run-handler handler:ex-editrow-form))) (let ((result (sx-swap page "outerHTML" "row-r1" response))) (do (assert-true (contains? result "~examples/edit-row-form")) (assert-true (contains? result "Widget")))))) (deftest "save stores and returns view" (reset-mocks!) (set! _mock-form {:id "r1" :price "$20" :name "Gadget"}) (let ((page "(tr :id \"row-r1\" (input))") (response (run-handler handler:ex-editrow-save))) (let ((result (sx-swap page "outerHTML" "row-r1" response))) (assert-true (contains? result "Gadget")))))) (defsuite "swap:profile-editing" (deftest "edit loads profile fields" (reset-mocks!) (set! _mock-state {:ex-profile {:email "ada@example.com" :role "Engineer" :name "Ada"}}) (let ((page "(div :id \"profile-target\" (p \"Ada\"))") (response (run-handler handler:ex-putpatch-edit-all))) (let ((result (sx-swap page "innerHTML" "profile-target" response))) (assert-true (contains? result "~examples/pp-form-full"))))) (deftest "put saves and returns view" (reset-mocks!) (set! _mock-form {:email "grace@example.com" :role "Captain" :name "Grace"}) (let ((page "(div :id \"profile-target\" (form))") (response (run-handler handler:ex-putpatch))) (let ((result (sx-swap page "innerHTML" "profile-target" response))) (assert-true (contains? result "Grace")))))) (defsuite "swap:tabs" (deftest "returns tab content with OOB buttons" (reset-mocks!) (set! _mock-args {:tab "tab1"}) (let ((page "(div (div :id \"tab-content\" (p \"old\")) (div :id \"tab-buttons\" (button \"old\")))") (response (run-handler handler:ex-tabs))) (let ((result (apply-response page response "innerHTML" "tab-content"))) (do (assert-true (contains? result "~examples/tab-btn")) (assert-false (contains? result "\"old\""))))))) (defsuite "swap:loading-indicator" (deftest "slow handler returns content after mock sleep" (reset-mocks!) (let ((page "(div :id \"loading-result\" (p \"Loading...\"))") (response (run-handler handler:ex-slow))) (let ((result (sx-swap page "innerHTML" "loading-result" response))) (do (assert-true (contains? result "~examples/loading-result")) (assert-true (contains? result "12:00:00")) (assert-false (contains? result "Loading"))))))) (defsuite "swap:bulk-operations" (deftest "activates selected users" (reset-mocks!) (set! _mock-args {:action "activate"}) (set! _mock-form {:ids (list "u1" "u2")}) (set! _mock-state {:ex-bulk-u2 {:status "inactive" :id "u2" :name "Bob"} :ex-bulk-u1 {:status "inactive" :id "u1" :name "Alice"}}) (let ((page "(tbody :id \"bulk-rows\" (tr \"old\"))") (response (run-handler handler:ex-bulk))) (let ((result (sx-swap page "innerHTML" "bulk-rows" response))) (assert-true (contains? result "~examples/bulk-row")))))) (defsuite "swap:dedup-search" (deftest "returns search result" (reset-mocks!) (set! _mock-args {:q "test"}) (let ((page "(div :id \"search-result\" (p \"Searching...\"))") (response (run-handler handler:ex-slow-search))) (let ((result (sx-swap page "innerHTML" "search-result" response))) (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"))))))