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>
682 lines
18 KiB
Plaintext
682 lines
18 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")
|
|
(if (nil? a1) _mock-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
|
|
"example:click-to-load"
|
|
(deftest
|
|
"returns content with timestamp"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-click)))
|
|
(assert-true (contains? result "Content loaded!"))))
|
|
(deftest
|
|
"includes mock time"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-click)))
|
|
(assert-true (contains? result _mock-now))))
|
|
(deftest
|
|
"includes OOB code panel"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-click)))
|
|
(assert-true (contains? result "click-comp")))))
|
|
|
|
(defsuite
|
|
"example:form-submission"
|
|
(deftest
|
|
"greets by name"
|
|
(reset-mocks!)
|
|
(set! _mock-form {:name "Alice"})
|
|
(let
|
|
((result (run-handler handler:ex-form)))
|
|
(assert-true (contains? result "Alice"))))
|
|
(deftest
|
|
"greets stranger when empty"
|
|
(reset-mocks!)
|
|
(set! _mock-form {:name ""})
|
|
(let
|
|
((result (run-handler handler:ex-form)))
|
|
(assert-true (contains? result "stranger"))))
|
|
(deftest
|
|
"includes wire format OOB"
|
|
(reset-mocks!)
|
|
(set! _mock-form {:name "Bob"})
|
|
(let
|
|
((result (run-handler handler:ex-form)))
|
|
(assert-true (contains? result "form-wire"))))
|
|
(deftest
|
|
"wire shows component call with name"
|
|
(reset-mocks!)
|
|
(set! _mock-form {:name "Bob"})
|
|
(let
|
|
((result (run-handler handler:ex-form)))
|
|
(assert-true (contains? result "Bob")))))
|
|
|
|
(defsuite
|
|
"example:polling"
|
|
(deftest
|
|
"increments counter from zero"
|
|
(reset-mocks!)
|
|
(run-handler handler:ex-poll)
|
|
(assert-equal 1 (get _mock-state "ex-poll-n")))
|
|
(deftest
|
|
"increments counter on second call"
|
|
(reset-mocks!)
|
|
(run-handler handler:ex-poll)
|
|
(run-handler handler:ex-poll)
|
|
(assert-equal 2 (get _mock-state "ex-poll-n")))
|
|
(deftest
|
|
"returns timestamp"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-poll)))
|
|
(assert-true (contains? result _mock-now))))
|
|
(deftest
|
|
"returns counter value"
|
|
(reset-mocks!)
|
|
(run-handler handler:ex-poll)
|
|
(let
|
|
((result (run-handler handler:ex-poll)))
|
|
(assert-true (contains? result "2")))))
|
|
|
|
(defsuite
|
|
"example:delete-row"
|
|
(deftest
|
|
"returns OOB code panel"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-delete)))
|
|
(assert-true (string? result))))
|
|
(deftest
|
|
"is valid SX"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-delete)))
|
|
(assert-true (> (len result) 0)))))
|
|
|
|
(defsuite
|
|
"example:inline-edit"
|
|
(deftest
|
|
"edit-form returns current value"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:value "hello"})
|
|
(let
|
|
((result (run-handler handler:ex-edit-form)))
|
|
(assert-true (contains? result "hello"))))
|
|
(deftest
|
|
"edit-save returns new value"
|
|
(reset-mocks!)
|
|
(set! _mock-form {:value "updated"})
|
|
(let
|
|
((result (run-handler handler:ex-edit-save)))
|
|
(assert-true (contains? result "updated"))))
|
|
(deftest
|
|
"edit-cancel restores original"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:value "original"})
|
|
(let
|
|
((result (run-handler handler:ex-edit-cancel)))
|
|
(assert-true (contains? result "original"))))
|
|
(deftest
|
|
"full cycle: edit → save → cancel"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:value "v1"})
|
|
(let
|
|
((edit (run-handler handler:ex-edit-form)))
|
|
(do
|
|
(assert-true (contains? edit "v1"))
|
|
(set! _mock-form {:value "v2"})
|
|
(let
|
|
((saved (run-handler handler:ex-edit-save)))
|
|
(do
|
|
(assert-true (contains? saved "v2"))
|
|
(set! _mock-args {:value "v1"})
|
|
(let
|
|
((cancelled (run-handler handler:ex-edit-cancel)))
|
|
(assert-true (contains? cancelled "v1")))))))))
|
|
|
|
(defsuite
|
|
"example:oob-updates"
|
|
(deftest
|
|
"returns timestamp"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-oob)))
|
|
(assert-true (contains? result _mock-now))))
|
|
(deftest
|
|
"includes OOB swap target"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-oob)))
|
|
(assert-true (contains? result "sx-swap-oob")))))
|
|
|
|
(defsuite
|
|
"example:lazy-load"
|
|
(deftest
|
|
"returns timestamp"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-lazy)))
|
|
(assert-true (contains? result _mock-now))))
|
|
(deftest
|
|
"includes content message"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-lazy)))
|
|
(assert-true (contains? result "page render")))))
|
|
|
|
(defsuite
|
|
"example:infinite-scroll"
|
|
(deftest
|
|
"page 2 returns items"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:page "2"})
|
|
(let
|
|
((result (run-handler handler:ex-scroll)))
|
|
(assert-true (> (len result) 50))))
|
|
(deftest
|
|
"page 10 shows all loaded"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:page "10"})
|
|
(let
|
|
((result (run-handler handler:ex-scroll)))
|
|
(assert-true (contains? result "loaded")))))
|
|
|
|
(defsuite
|
|
"example:progress-bar"
|
|
(deftest
|
|
"start creates job counter"
|
|
(reset-mocks!)
|
|
(run-handler handler:ex-progress-start)
|
|
(assert-equal 1 (get _mock-state "ex-job-counter")))
|
|
(deftest
|
|
"start initializes job at 0"
|
|
(reset-mocks!)
|
|
(run-handler handler:ex-progress-start)
|
|
(assert-equal 0 (get _mock-state "ex-job-1")))
|
|
(deftest
|
|
"status increments progress"
|
|
(reset-mocks!)
|
|
(set! _mock-state (assoc _mock-state "ex-job-1" 50))
|
|
(set! _mock-args {:job "1"})
|
|
(run-handler handler:ex-progress-status)
|
|
(assert-true (> (get _mock-state "ex-job-1") 50)))
|
|
(deftest
|
|
"full cycle: start → poll status"
|
|
(reset-mocks!)
|
|
(run-handler handler:ex-progress-start)
|
|
(set! _mock-args {:job "1"})
|
|
(run-handler handler:ex-progress-status)
|
|
(assert-true (> (get _mock-state "ex-job-1") 0))))
|
|
|
|
(defsuite
|
|
"example:search"
|
|
(deftest
|
|
"finds Python"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:q "python"})
|
|
(let
|
|
((result (run-handler handler:ex-search)))
|
|
(assert-true (contains? result "Python"))))
|
|
(deftest
|
|
"finds multiple matches"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:q "java"})
|
|
(let
|
|
((result (run-handler handler:ex-search)))
|
|
(assert-true (contains? result "Java"))))
|
|
(deftest
|
|
"empty query returns prompt"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:q ""})
|
|
(let
|
|
((result (run-handler handler:ex-search)))
|
|
(assert-true (contains? result "type to search"))))
|
|
(deftest
|
|
"no match returns empty"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:q "zzzznothing"})
|
|
(let
|
|
((result (run-handler handler:ex-search)))
|
|
(assert-true (not (contains? result "Python"))))))
|
|
|
|
(defsuite
|
|
"example:validation"
|
|
(deftest
|
|
"rejects empty"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:email ""})
|
|
(let
|
|
((result (run-handler handler:ex-validate)))
|
|
(assert-true (contains? result "required"))))
|
|
(deftest
|
|
"rejects no @"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:email "bad"})
|
|
(let
|
|
((result (run-handler handler:ex-validate)))
|
|
(assert-true (contains? result "@"))))
|
|
(deftest
|
|
"rejects taken email"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:email "alice@example.com"})
|
|
(let
|
|
((result (run-handler handler:ex-validate)))
|
|
(assert-true (contains? result "taken"))))
|
|
(deftest
|
|
"accepts valid email"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:email "new@example.com"})
|
|
(let
|
|
((result (run-handler handler:ex-validate)))
|
|
(assert-true (contains? result "available"))))
|
|
(deftest
|
|
"submit with valid email succeeds"
|
|
(reset-mocks!)
|
|
(set! _mock-form {:email "ok@test.com"})
|
|
(let
|
|
((result (run-handler handler:ex-validate-submit)))
|
|
(assert-true (contains? result "ok@test.com")))))
|
|
|
|
(defsuite
|
|
"example:dependent-select"
|
|
(deftest
|
|
"returns options for frontend"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:category "frontend"})
|
|
(let
|
|
((result (run-handler handler:ex-values)))
|
|
(assert-true (contains? result "option"))))
|
|
(deftest
|
|
"empty category returns empty"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:category ""})
|
|
(let
|
|
((result (run-handler handler:ex-values)))
|
|
(assert-true (string? result)))))
|
|
|
|
(defsuite
|
|
"example:form-reset"
|
|
(deftest
|
|
"echoes submitted message"
|
|
(reset-mocks!)
|
|
(set! _mock-form {:message "Hello world"})
|
|
(let
|
|
((result (run-handler handler:ex-reset-submit)))
|
|
(assert-true (contains? result "Hello world"))))
|
|
(deftest
|
|
"includes timestamp"
|
|
(reset-mocks!)
|
|
(set! _mock-form {:message "test"})
|
|
(let
|
|
((result (run-handler handler:ex-reset-submit)))
|
|
(assert-true (contains? result _mock-now)))))
|
|
|
|
(defsuite
|
|
"example:row-editing"
|
|
(deftest
|
|
"form loads row data"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:row-id "1"})
|
|
(let
|
|
((result (run-handler handler:ex-editrow-form)))
|
|
(assert-true (> (len result) 10))))
|
|
(deftest
|
|
"save stores row in state"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:row-id "1"})
|
|
(set! _mock-form {:stock "50" :price "9.99" :name "Widget"})
|
|
(run-handler handler:ex-editrow-save)
|
|
(let
|
|
((row (get _mock-state "ex-row-1")))
|
|
(assert-equal "Widget" (get row "name"))))
|
|
(deftest
|
|
"save stores price"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:row-id "1"})
|
|
(set! _mock-form {:stock "5" :price "19.99" :name "X"})
|
|
(run-handler handler:ex-editrow-save)
|
|
(let
|
|
((row (get _mock-state "ex-row-1")))
|
|
(assert-equal "19.99" (get row "price"))))
|
|
(deftest
|
|
"cancel returns view"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:row-id "1"})
|
|
(let
|
|
((result (run-handler handler:ex-editrow-cancel)))
|
|
(assert-true (> (len result) 10))))
|
|
(deftest
|
|
"full cycle: edit → save → cancel"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:row-id "2"})
|
|
(let
|
|
((form (run-handler handler:ex-editrow-form)))
|
|
(do
|
|
(assert-true (string? form))
|
|
(set! _mock-form {:stock "100" :price "5.00" :name "Gadget"})
|
|
(run-handler handler:ex-editrow-save)
|
|
(assert-equal "Gadget" (get (get _mock-state "ex-row-2") "name"))
|
|
(let
|
|
((view (run-handler handler:ex-editrow-cancel)))
|
|
(assert-true (string? view)))))))
|
|
|
|
(defsuite
|
|
"example:profile-editing"
|
|
(deftest
|
|
"edit loads current profile"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-pp-edit-all)))
|
|
(assert-true (string? result))))
|
|
(deftest
|
|
"put saves all fields"
|
|
(reset-mocks!)
|
|
(set! _mock-form {:email "bob@test.com" :role "admin" :name "Bob"})
|
|
(run-handler handler:ex-pp-put)
|
|
(let
|
|
((p (get _mock-state "ex-profile")))
|
|
(do
|
|
(assert-equal "Bob" (get p "name"))
|
|
(assert-equal "bob@test.com" (get p "email"))
|
|
(assert-equal "admin" (get p "role")))))
|
|
(deftest
|
|
"cancel returns view"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-pp-cancel)))
|
|
(assert-true (string? result))))
|
|
(deftest
|
|
"full cycle: edit → put → view"
|
|
(reset-mocks!)
|
|
(run-handler handler:ex-pp-edit-all)
|
|
(set! _mock-form {:email "c@x.com" :role "user" :name "Carol"})
|
|
(run-handler handler:ex-pp-put)
|
|
(let
|
|
((view (run-handler handler:ex-pp-cancel)))
|
|
(assert-true (contains? view "Carol")))))
|
|
|
|
(defsuite
|
|
"example:bulk-operations"
|
|
(deftest
|
|
"activates selected users"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:action "activate"})
|
|
(set! _mock-form {:ids (list "u1" "u2")})
|
|
(let
|
|
((result (run-handler handler:ex-bulk)))
|
|
(assert-true (string? result)))))
|
|
|
|
(defsuite
|
|
"example:swap-modes"
|
|
(deftest
|
|
"increments counter"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:mode "beforeend"})
|
|
(run-handler handler:ex-swap-log)
|
|
(assert-equal 1 (get _mock-state "ex-swap-n")))
|
|
(deftest
|
|
"returns timestamp"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:mode "innerHTML"})
|
|
(let
|
|
((result (run-handler handler:ex-swap-log)))
|
|
(assert-true (contains? result _mock-now))))
|
|
(deftest
|
|
"includes OOB counter update"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:mode "beforeend"})
|
|
(let
|
|
((result (run-handler handler:ex-swap-log)))
|
|
(assert-true (contains? result "swap-counter")))))
|
|
|
|
(defsuite
|
|
"example:dashboard"
|
|
(deftest
|
|
"shows user count"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-dashboard)))
|
|
(assert-true (contains? result "142"))))
|
|
(deftest
|
|
"shows revenue"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-dashboard)))
|
|
(assert-true (contains? result "4.2k"))))
|
|
(deftest
|
|
"includes timestamp"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-dashboard)))
|
|
(assert-true (contains? result _mock-now)))))
|
|
|
|
(defsuite
|
|
"example:tabs"
|
|
(deftest
|
|
"returns tab content"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:tab "features"})
|
|
(let
|
|
((result (run-handler handler:ex-tabs)))
|
|
(assert-true (> (len result) 20))))
|
|
(deftest
|
|
"includes OOB tab buttons"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:tab "features"})
|
|
(let
|
|
((result (run-handler handler:ex-tabs)))
|
|
(assert-true (contains? result "tab-buttons")))))
|
|
|
|
(defsuite
|
|
"example:animation"
|
|
(deftest
|
|
"returns content"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-animate)))
|
|
(assert-true (> (len result) 20))))
|
|
(deftest
|
|
"includes timestamp"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-animate)))
|
|
(assert-true (contains? result _mock-now)))))
|
|
|
|
(defsuite
|
|
"example:dialog"
|
|
(deftest
|
|
"open returns modal content"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-dialog)))
|
|
(assert-true (> (len result) 20))))
|
|
(deftest
|
|
"close returns response"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-dialog-close)))
|
|
(assert-true (string? result)))))
|
|
|
|
(defsuite
|
|
"example:keyboard"
|
|
(deftest
|
|
"returns action for Enter"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:key "Enter"})
|
|
(let
|
|
((result (run-handler handler:ex-keyboard)))
|
|
(assert-true (> (len result) 10))))
|
|
(deftest
|
|
"returns action for Escape"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:key "Escape"})
|
|
(let
|
|
((result (run-handler handler:ex-keyboard)))
|
|
(assert-true (> (len result) 10)))))
|
|
|
|
(defsuite
|
|
"example:json-echo"
|
|
(deftest
|
|
"echoes content type"
|
|
(reset-mocks!)
|
|
(set! _mock-body "{\"key\":\"val\"}")
|
|
(set! _mock-content-type "application/json")
|
|
(let
|
|
((result (run-handler handler:ex-json-echo)))
|
|
(assert-true (contains? result "application/json"))))
|
|
(deftest
|
|
"echoes body"
|
|
(reset-mocks!)
|
|
(set! _mock-body "{\"hello\":\"world\"}")
|
|
(set! _mock-content-type "application/json")
|
|
(let
|
|
((result (run-handler handler:ex-json-echo)))
|
|
(assert-true (contains? result "hello")))))
|
|
|
|
(defsuite
|
|
"example:echo-vals"
|
|
(deftest
|
|
"echoes query args"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:foo "bar" :baz "qux"})
|
|
(let
|
|
((result (run-handler handler:ex-echo-vals)))
|
|
(assert-true (string? result)))))
|
|
|
|
(defsuite
|
|
"example:echo-headers"
|
|
(deftest
|
|
"echoes x-prefixed headers"
|
|
(reset-mocks!)
|
|
(set! _mock-headers {:x-custom "value" :content-type "text/plain"})
|
|
(let
|
|
((result (run-handler handler:ex-echo-headers)))
|
|
(assert-true (string? result)))))
|
|
|
|
(defsuite
|
|
"example:loading"
|
|
(deftest
|
|
"returns content (sleep mocked)"
|
|
(reset-mocks!)
|
|
(let
|
|
((result (run-handler handler:ex-slow)))
|
|
(assert-true (contains? result _mock-now)))))
|
|
|
|
(defsuite
|
|
"example:dedup-search"
|
|
(deftest
|
|
"returns search result"
|
|
(reset-mocks!)
|
|
(set! _mock-args {:q "test"})
|
|
(let
|
|
((result (run-handler handler:ex-slow-search)))
|
|
(assert-true (contains? result "test")))))
|
|
|
|
(defsuite
|
|
"example:retry"
|
|
(deftest
|
|
"first call increments counter"
|
|
(reset-mocks!)
|
|
(run-handler handler:ex-flaky)
|
|
(assert-equal 1 (get _mock-state "ex-flaky-n")))
|
|
(deftest
|
|
"third call succeeds (mod 3)"
|
|
(reset-mocks!)
|
|
(run-handler handler:ex-flaky)
|
|
(run-handler handler:ex-flaky)
|
|
(let
|
|
((result (run-handler handler:ex-flaky)))
|
|
(do
|
|
(assert-equal 3 (get _mock-state "ex-flaky-n"))
|
|
(assert-true (contains? result "Success"))))))
|