Merge branch 'hs-e40-fetch' into loops/hs
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 16s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 16s
This commit is contained in:
@@ -1838,7 +1838,7 @@
|
|||||||
(list (quote fn) (list) (hs-to-sx (nth ast 1)))
|
(list (quote fn) (list) (hs-to-sx (nth ast 1)))
|
||||||
(list (quote fn) (list) (hs-to-sx (nth ast 2)))))
|
(list (quote fn) (list) (hs-to-sx (nth ast 2)))))
|
||||||
((= head (quote fetch))
|
((= head (quote fetch))
|
||||||
(list (quote hs-fetch) (hs-to-sx (nth ast 1)) (nth ast 2)))
|
(list (quote hs-fetch) (hs-to-sx (nth ast 1)) (nth ast 2) (nth ast 3) (quote me)))
|
||||||
((= head (quote fetch-gql))
|
((= head (quote fetch-gql))
|
||||||
(list
|
(list
|
||||||
(quote hs-fetch-gql)
|
(quote hs-fetch-gql)
|
||||||
|
|||||||
@@ -1772,7 +1772,7 @@
|
|||||||
((url (if (and (= (tp-type) "keyword") (= (tp-val) "from")) (do (adv!) (parse-arith (parse-poss (parse-atom)))) nil)))
|
((url (if (and (= (tp-type) "keyword") (= (tp-val) "from")) (do (adv!) (parse-arith (parse-poss (parse-atom)))) nil)))
|
||||||
(list (quote fetch-gql) gql-source url))))
|
(list (quote fetch-gql) gql-source url))))
|
||||||
(let
|
(let
|
||||||
((url-atom (if (and (= (tp-type) "op") (= (tp-val) "/")) (do (adv!) (let ((path-parts (list "/"))) (define read-path (fn () (when (and (not (at-end?)) (or (= (tp-type) "ident") (= (tp-type) "op") (= (tp-type) "dot") (= (tp-type) "number"))) (append! path-parts (tp-val)) (adv!) (read-path)))) (read-path) (join "" path-parts))) (parse-atom))))
|
((url-atom (if (and (= (tp-type) "op") (= (tp-val) "/")) (do (adv!) (let ((path-parts (list "/"))) (define read-path (fn () (when (and (not (at-end?)) (or (and (= (tp-type) "ident") (not (string-contains? (tp-val) "'"))) (= (tp-type) "op") (= (tp-type) "dot") (= (tp-type) "number"))) (append! path-parts (tp-val)) (adv!) (read-path)))) (read-path) (join "" path-parts))) (parse-atom))))
|
||||||
(let
|
(let
|
||||||
((url (if (nil? url-atom) url-atom (parse-arith (parse-poss url-atom)))))
|
((url (if (nil? url-atom) url-atom (parse-arith (parse-poss url-atom)))))
|
||||||
(let
|
(let
|
||||||
@@ -1788,7 +1788,27 @@
|
|||||||
((fmt-after (if (and (not fmt-before) (match-kw "as")) (do (when (and (or (= (tp-type) "ident") (= (tp-type) "keyword")) (or (= (tp-val) "an") (= (tp-val) "a"))) (adv!)) (let ((f (tp-val))) (adv!) f)) nil)))
|
((fmt-after (if (and (not fmt-before) (match-kw "as")) (do (when (and (or (= (tp-type) "ident") (= (tp-type) "keyword")) (or (= (tp-val) "an") (= (tp-val) "a"))) (adv!)) (let ((f (tp-val))) (adv!) f)) nil)))
|
||||||
(let
|
(let
|
||||||
((fmt (or fmt-before fmt-after "text")))
|
((fmt (or fmt-before fmt-after "text")))
|
||||||
(list (quote fetch) url fmt)))))))))
|
(let
|
||||||
|
((do-not-throw
|
||||||
|
(cond
|
||||||
|
((and (or (= (tp-type) "keyword") (= (tp-type) "ident")) (= (tp-val) "do"))
|
||||||
|
(do
|
||||||
|
(adv!)
|
||||||
|
(if (and (or (= (tp-type) "keyword") (= (tp-type) "ident")) (= (tp-val) "not"))
|
||||||
|
(do
|
||||||
|
(adv!)
|
||||||
|
(if (and (or (= (tp-type) "keyword") (= (tp-type) "ident")) (= (tp-val) "throw"))
|
||||||
|
(do (adv!) true)
|
||||||
|
false))
|
||||||
|
false)))
|
||||||
|
((and (= (tp-type) "ident") (= (tp-val) "don't"))
|
||||||
|
(do
|
||||||
|
(adv!)
|
||||||
|
(if (and (or (= (tp-type) "keyword") (= (tp-type) "ident")) (= (tp-val) "throw"))
|
||||||
|
(do (adv!) true)
|
||||||
|
false)))
|
||||||
|
(true false))))
|
||||||
|
(list (quote fetch) url fmt do-not-throw))))))))))
|
||||||
(define
|
(define
|
||||||
parse-call-args
|
parse-call-args
|
||||||
(fn
|
(fn
|
||||||
|
|||||||
@@ -869,12 +869,33 @@
|
|||||||
(define
|
(define
|
||||||
hs-fetch
|
hs-fetch
|
||||||
(fn
|
(fn
|
||||||
(url format)
|
(url format do-not-throw target)
|
||||||
(let
|
(let
|
||||||
((fmt (cond ((nil? format) "text") ((or (= format "json") (= format "JSON") (= format "Object")) "json") ((or (= format "html") (= format "HTML")) "html") ((or (= format "response") (= format "Response")) "response") ((or (= format "text") (= format "Text")) "text") (true format))))
|
((fmt (cond ((nil? format) "text") ((or (= format "json") (= format "JSON") (= format "Object")) "json") ((or (= format "html") (= format "HTML")) "html") ((or (= format "response") (= format "Response")) "response") ((or (= format "text") (= format "Text")) "text") ((or (= format "number") (= format "Number")) "number") (true format))))
|
||||||
(let
|
(do
|
||||||
((raw (perform (list "io-fetch" url fmt))))
|
(when (not (nil? target))
|
||||||
(cond ((= fmt "json") (hs-host-to-sx raw)) (true raw))))))
|
(dom-dispatch target "hyperscript:beforeFetch" nil))
|
||||||
|
(let
|
||||||
|
((raw (perform (list "io-fetch" url "response" (dict)))))
|
||||||
|
(do
|
||||||
|
(when (get raw :_network-error) (raise {:response raw :message "Network error" :_hs-error "FetchError"}))
|
||||||
|
(when
|
||||||
|
(and (not (get raw :ok)) (not (= fmt "response")) (not do-not-throw))
|
||||||
|
(raise {:response raw :status (get raw :status) :message "Fetch error" :_hs-error "FetchError"}))
|
||||||
|
(cond
|
||||||
|
((= fmt "response") raw)
|
||||||
|
((= fmt "json")
|
||||||
|
(let
|
||||||
|
((parsed (perform (list "io-parse-json" (get raw :_json)))))
|
||||||
|
(hs-host-to-sx parsed)))
|
||||||
|
((= fmt "html")
|
||||||
|
(perform (list "io-parse-html" (get raw :_html))))
|
||||||
|
((= fmt "number")
|
||||||
|
(or
|
||||||
|
(parse-number (get raw :_number))
|
||||||
|
(parse-number (get raw :_body))
|
||||||
|
0))
|
||||||
|
(true (get raw :_body)))))))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-json-escape
|
hs-json-escape
|
||||||
@@ -965,6 +986,8 @@
|
|||||||
(true (str value))))
|
(true (str value))))
|
||||||
((= type-name "JSON")
|
((= type-name "JSON")
|
||||||
(cond
|
(cond
|
||||||
|
((and (dict? value) (dict-has? value :_json))
|
||||||
|
(guard (_e (true value)) (json-parse (get value :_json))))
|
||||||
((string? value) (guard (_e (true value)) (json-parse value)))
|
((string? value) (guard (_e (true value)) (json-parse value)))
|
||||||
((dict? value) (hs-json-stringify value))
|
((dict? value) (hs-json-stringify value))
|
||||||
((list? value) (hs-json-stringify value))
|
((list? value) (hs-json-stringify value))
|
||||||
|
|||||||
@@ -568,10 +568,26 @@
|
|||||||
(do
|
(do
|
||||||
(let
|
(let
|
||||||
((word (read-ident start)))
|
((word (read-ident start)))
|
||||||
(hs-emit!
|
(let
|
||||||
(if (hs-keyword? word) "keyword" "ident")
|
((full-word
|
||||||
word
|
(if
|
||||||
start))
|
(and
|
||||||
|
(< pos src-len)
|
||||||
|
(= (hs-cur) "'")
|
||||||
|
(< (+ pos 1) src-len)
|
||||||
|
(hs-letter? (hs-peek 1))
|
||||||
|
(not
|
||||||
|
(and
|
||||||
|
(= (hs-peek 1) "s")
|
||||||
|
(or
|
||||||
|
(>= (+ pos 2) src-len)
|
||||||
|
(not (hs-ident-char? (hs-peek 2)))))))
|
||||||
|
(do (hs-advance! 1) (str word "'" (read-ident pos)))
|
||||||
|
word)))
|
||||||
|
(hs-emit!
|
||||||
|
(if (hs-keyword? full-word) "keyword" "ident")
|
||||||
|
full-word
|
||||||
|
start)))
|
||||||
(scan!))
|
(scan!))
|
||||||
(and
|
(and
|
||||||
(or (= ch "=") (= ch "!") (= ch "<") (= ch ">"))
|
(or (= ch "=") (= ch "!") (= ch "<") (= ch ">"))
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ Remaining: ~192 tests (clusters 17/29(partial)/31 blocked; 33/34 partial)
|
|||||||
| 37 | Tokenizer-as-API | design-done | `plans/designs/e37-tokenizer-api.md` |
|
| 37 | Tokenizer-as-API | design-done | `plans/designs/e37-tokenizer-api.md` |
|
||||||
| 38 | SourceInfo API | design-done | `plans/designs/e38-sourceinfo.md` |
|
| 38 | SourceInfo API | design-done | `plans/designs/e38-sourceinfo.md` |
|
||||||
| 39 | WebWorker plugin | design-done | `plans/designs/e39-webworker.md` |
|
| 39 | WebWorker plugin | design-done | `plans/designs/e39-webworker.md` |
|
||||||
| 40 | Fetch non-2xx / before-fetch / real response | design-done | `plans/designs/e40-real-fetch.md` |
|
| 40 | Fetch non-2xx / before-fetch / real response | done | +7 | d7244d1d |
|
||||||
|
|
||||||
### Bucket F — generator translation gaps
|
### Bucket F — generator translation gaps
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ Defer until A–D drain. Estimated ~25 recoverable tests.
|
|||||||
| B | 7 | 0 | 0 | 0 | 0 | — | 7 |
|
| B | 7 | 0 | 0 | 0 | 0 | — | 7 |
|
||||||
| C | 4 | 1 | 0 | 0 | 0 | — | 5 |
|
| C | 4 | 1 | 0 | 0 | 0 | — | 5 |
|
||||||
| D | 2 | 2 | 0 | 0 | 1 | — | 5 |
|
| D | 2 | 2 | 0 | 0 | 1 | — | 5 |
|
||||||
| E | 0 | 0 | 0 | 0 | 0 | 5 | 5 |
|
| E | 1 | 0 | 0 | 0 | 0 | 4 | 5 |
|
||||||
| F | — | — | — | ~10 | — | — | ~10 |
|
| F | — | — | — | ~10 | — | — | ~10 |
|
||||||
|
|
||||||
## Maintenance
|
## Maintenance
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ All five have design docs on their own worktree branches pending review + merge.
|
|||||||
|
|
||||||
39. **[design-done, pending review — `plans/designs/e39-webworker.md` on `hs-design-e39-webworker`] WebWorker plugin** — 1 test. Parser-only stub that errors with a link to upstream docs; no runtime, no mock Worker class. Hand-write the test (don't patch the generator). Single commit.
|
39. **[design-done, pending review — `plans/designs/e39-webworker.md` on `hs-design-e39-webworker`] WebWorker plugin** — 1 test. Parser-only stub that errors with a link to upstream docs; no runtime, no mock Worker class. Hand-write the test (don't patch the generator). Single commit.
|
||||||
|
|
||||||
40. **[design-done, pending review — `plans/designs/e40-real-fetch.md` on `worktree-agent-a94612a4283eaa5e0`] Fetch non-2xx / before-fetch event / real response object** — 7 tests. SX-dict Response wrapper `{:_hs-response :ok :status :url :_body :_json :_html}`; restructured `hs-fetch` that always fetches wrapper then converts by format; test-name-keyed `_fetchScripts`. 11-step checklist. Watch for regression on cluster-1 JSON unwrap.
|
40. **[done +7 — d7244d1d] Fetch non-2xx / before-fetch event / real response object** — 7 tests. SX-dict Response wrapper `{:_hs-response :ok :status :url :_body :_json :_html}`; restructured `hs-fetch` that always fetches wrapper then converts by format; test-name-keyed `_fetchScripts`. 11-step checklist. Watch for regression on cluster-1 JSON unwrap.
|
||||||
|
|
||||||
### Bucket F: generator translation gaps (after bucket A-D)
|
### Bucket F: generator translation gaps (after bucket A-D)
|
||||||
|
|
||||||
|
|||||||
@@ -1838,7 +1838,7 @@
|
|||||||
(list (quote fn) (list) (hs-to-sx (nth ast 1)))
|
(list (quote fn) (list) (hs-to-sx (nth ast 1)))
|
||||||
(list (quote fn) (list) (hs-to-sx (nth ast 2)))))
|
(list (quote fn) (list) (hs-to-sx (nth ast 2)))))
|
||||||
((= head (quote fetch))
|
((= head (quote fetch))
|
||||||
(list (quote hs-fetch) (hs-to-sx (nth ast 1)) (nth ast 2)))
|
(list (quote hs-fetch) (hs-to-sx (nth ast 1)) (nth ast 2) (nth ast 3) (quote me)))
|
||||||
((= head (quote fetch-gql))
|
((= head (quote fetch-gql))
|
||||||
(list
|
(list
|
||||||
(quote hs-fetch-gql)
|
(quote hs-fetch-gql)
|
||||||
|
|||||||
@@ -1772,7 +1772,7 @@
|
|||||||
((url (if (and (= (tp-type) "keyword") (= (tp-val) "from")) (do (adv!) (parse-arith (parse-poss (parse-atom)))) nil)))
|
((url (if (and (= (tp-type) "keyword") (= (tp-val) "from")) (do (adv!) (parse-arith (parse-poss (parse-atom)))) nil)))
|
||||||
(list (quote fetch-gql) gql-source url))))
|
(list (quote fetch-gql) gql-source url))))
|
||||||
(let
|
(let
|
||||||
((url-atom (if (and (= (tp-type) "op") (= (tp-val) "/")) (do (adv!) (let ((path-parts (list "/"))) (define read-path (fn () (when (and (not (at-end?)) (or (= (tp-type) "ident") (= (tp-type) "op") (= (tp-type) "dot") (= (tp-type) "number"))) (append! path-parts (tp-val)) (adv!) (read-path)))) (read-path) (join "" path-parts))) (parse-atom))))
|
((url-atom (if (and (= (tp-type) "op") (= (tp-val) "/")) (do (adv!) (let ((path-parts (list "/"))) (define read-path (fn () (when (and (not (at-end?)) (or (and (= (tp-type) "ident") (not (string-contains? (tp-val) "'"))) (= (tp-type) "op") (= (tp-type) "dot") (= (tp-type) "number"))) (append! path-parts (tp-val)) (adv!) (read-path)))) (read-path) (join "" path-parts))) (parse-atom))))
|
||||||
(let
|
(let
|
||||||
((url (if (nil? url-atom) url-atom (parse-arith (parse-poss url-atom)))))
|
((url (if (nil? url-atom) url-atom (parse-arith (parse-poss url-atom)))))
|
||||||
(let
|
(let
|
||||||
@@ -1788,7 +1788,27 @@
|
|||||||
((fmt-after (if (and (not fmt-before) (match-kw "as")) (do (when (and (or (= (tp-type) "ident") (= (tp-type) "keyword")) (or (= (tp-val) "an") (= (tp-val) "a"))) (adv!)) (let ((f (tp-val))) (adv!) f)) nil)))
|
((fmt-after (if (and (not fmt-before) (match-kw "as")) (do (when (and (or (= (tp-type) "ident") (= (tp-type) "keyword")) (or (= (tp-val) "an") (= (tp-val) "a"))) (adv!)) (let ((f (tp-val))) (adv!) f)) nil)))
|
||||||
(let
|
(let
|
||||||
((fmt (or fmt-before fmt-after "text")))
|
((fmt (or fmt-before fmt-after "text")))
|
||||||
(list (quote fetch) url fmt)))))))))
|
(let
|
||||||
|
((do-not-throw
|
||||||
|
(cond
|
||||||
|
((and (or (= (tp-type) "keyword") (= (tp-type) "ident")) (= (tp-val) "do"))
|
||||||
|
(do
|
||||||
|
(adv!)
|
||||||
|
(if (and (or (= (tp-type) "keyword") (= (tp-type) "ident")) (= (tp-val) "not"))
|
||||||
|
(do
|
||||||
|
(adv!)
|
||||||
|
(if (and (or (= (tp-type) "keyword") (= (tp-type) "ident")) (= (tp-val) "throw"))
|
||||||
|
(do (adv!) true)
|
||||||
|
false))
|
||||||
|
false)))
|
||||||
|
((and (= (tp-type) "ident") (= (tp-val) "don't"))
|
||||||
|
(do
|
||||||
|
(adv!)
|
||||||
|
(if (and (or (= (tp-type) "keyword") (= (tp-type) "ident")) (= (tp-val) "throw"))
|
||||||
|
(do (adv!) true)
|
||||||
|
false)))
|
||||||
|
(true false))))
|
||||||
|
(list (quote fetch) url fmt do-not-throw))))))))))
|
||||||
(define
|
(define
|
||||||
parse-call-args
|
parse-call-args
|
||||||
(fn
|
(fn
|
||||||
|
|||||||
@@ -869,12 +869,33 @@
|
|||||||
(define
|
(define
|
||||||
hs-fetch
|
hs-fetch
|
||||||
(fn
|
(fn
|
||||||
(url format)
|
(url format do-not-throw target)
|
||||||
(let
|
(let
|
||||||
((fmt (cond ((nil? format) "text") ((or (= format "json") (= format "JSON") (= format "Object")) "json") ((or (= format "html") (= format "HTML")) "html") ((or (= format "response") (= format "Response")) "response") ((or (= format "text") (= format "Text")) "text") (true format))))
|
((fmt (cond ((nil? format) "text") ((or (= format "json") (= format "JSON") (= format "Object")) "json") ((or (= format "html") (= format "HTML")) "html") ((or (= format "response") (= format "Response")) "response") ((or (= format "text") (= format "Text")) "text") ((or (= format "number") (= format "Number")) "number") (true format))))
|
||||||
(let
|
(do
|
||||||
((raw (perform (list "io-fetch" url fmt))))
|
(when (not (nil? target))
|
||||||
(cond ((= fmt "json") (hs-host-to-sx raw)) (true raw))))))
|
(dom-dispatch target "hyperscript:beforeFetch" nil))
|
||||||
|
(let
|
||||||
|
((raw (perform (list "io-fetch" url "response" (dict)))))
|
||||||
|
(do
|
||||||
|
(when (get raw :_network-error) (raise {:response raw :message "Network error" :_hs-error "FetchError"}))
|
||||||
|
(when
|
||||||
|
(and (not (get raw :ok)) (not (= fmt "response")) (not do-not-throw))
|
||||||
|
(raise {:response raw :status (get raw :status) :message "Fetch error" :_hs-error "FetchError"}))
|
||||||
|
(cond
|
||||||
|
((= fmt "response") raw)
|
||||||
|
((= fmt "json")
|
||||||
|
(let
|
||||||
|
((parsed (perform (list "io-parse-json" (get raw :_json)))))
|
||||||
|
(hs-host-to-sx parsed)))
|
||||||
|
((= fmt "html")
|
||||||
|
(perform (list "io-parse-html" (get raw :_html))))
|
||||||
|
((= fmt "number")
|
||||||
|
(or
|
||||||
|
(parse-number (get raw :_number))
|
||||||
|
(parse-number (get raw :_body))
|
||||||
|
0))
|
||||||
|
(true (get raw :_body)))))))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-json-escape
|
hs-json-escape
|
||||||
@@ -965,6 +986,8 @@
|
|||||||
(true (str value))))
|
(true (str value))))
|
||||||
((= type-name "JSON")
|
((= type-name "JSON")
|
||||||
(cond
|
(cond
|
||||||
|
((and (dict? value) (dict-has? value :_json))
|
||||||
|
(guard (_e (true value)) (json-parse (get value :_json))))
|
||||||
((string? value) (guard (_e (true value)) (json-parse value)))
|
((string? value) (guard (_e (true value)) (json-parse value)))
|
||||||
((dict? value) (hs-json-stringify value))
|
((dict? value) (hs-json-stringify value))
|
||||||
((list? value) (hs-json-stringify value))
|
((list? value) (hs-json-stringify value))
|
||||||
|
|||||||
@@ -568,10 +568,26 @@
|
|||||||
(do
|
(do
|
||||||
(let
|
(let
|
||||||
((word (read-ident start)))
|
((word (read-ident start)))
|
||||||
(hs-emit!
|
(let
|
||||||
(if (hs-keyword? word) "keyword" "ident")
|
((full-word
|
||||||
word
|
(if
|
||||||
start))
|
(and
|
||||||
|
(< pos src-len)
|
||||||
|
(= (hs-cur) "'")
|
||||||
|
(< (+ pos 1) src-len)
|
||||||
|
(hs-letter? (hs-peek 1))
|
||||||
|
(not
|
||||||
|
(and
|
||||||
|
(= (hs-peek 1) "s")
|
||||||
|
(or
|
||||||
|
(>= (+ pos 2) src-len)
|
||||||
|
(not (hs-ident-char? (hs-peek 2)))))))
|
||||||
|
(do (hs-advance! 1) (str word "'" (read-ident pos)))
|
||||||
|
word)))
|
||||||
|
(hs-emit!
|
||||||
|
(if (hs-keyword? full-word) "keyword" "ident")
|
||||||
|
full-word
|
||||||
|
start)))
|
||||||
(scan!))
|
(scan!))
|
||||||
(and
|
(and
|
||||||
(or (= ch "=") (= ch "!") (= ch "<") (= ch ">"))
|
(or (= ch "=") (= ch "!") (= ch "<") (= ch ">"))
|
||||||
|
|||||||
@@ -7429,7 +7429,14 @@
|
|||||||
;; ── fetch (23 tests) ──
|
;; ── fetch (23 tests) ──
|
||||||
(defsuite "hs-upstream-fetch"
|
(defsuite "hs-upstream-fetch"
|
||||||
(deftest "Response can be converted to JSON via as JSON"
|
(deftest "Response can be converted to JSON via as JSON"
|
||||||
(error "SKIP (skip-list): Response can be converted to JSON via as JSON"))
|
(hs-cleanup!)
|
||||||
|
(let ((_el-div (dom-create-element "div")))
|
||||||
|
(dom-set-attr _el-div "_" "on click fetch /test as Response then put (it as JSON).name into me")
|
||||||
|
(dom-append (dom-body) _el-div)
|
||||||
|
(hs-activate! _el-div)
|
||||||
|
(dom-dispatch _el-div "click" nil)
|
||||||
|
(assert= (dom-text-content _el-div) "Joe")
|
||||||
|
))
|
||||||
(deftest "allows the event handler to change the fetch parameters"
|
(deftest "allows the event handler to change the fetch parameters"
|
||||||
(hs-cleanup!)
|
(hs-cleanup!)
|
||||||
(let ((_el-div (dom-create-element "div")))
|
(let ((_el-div (dom-create-element "div")))
|
||||||
@@ -7440,9 +7447,23 @@
|
|||||||
(assert= (dom-text-content _el-div) "yay")
|
(assert= (dom-text-content _el-div) "yay")
|
||||||
))
|
))
|
||||||
(deftest "as response does not throw on 404"
|
(deftest "as response does not throw on 404"
|
||||||
(error "SKIP (skip-list): as response does not throw on 404"))
|
(hs-cleanup!)
|
||||||
|
(let ((_el-div (dom-create-element "div")))
|
||||||
|
(dom-set-attr _el-div "_" "on click fetch /test as response then put it.status into me")
|
||||||
|
(dom-append (dom-body) _el-div)
|
||||||
|
(hs-activate! _el-div)
|
||||||
|
(dom-dispatch _el-div "click" nil)
|
||||||
|
(assert= (dom-text-content _el-div) "404")
|
||||||
|
))
|
||||||
(deftest "can catch an error that occurs when using fetch"
|
(deftest "can catch an error that occurs when using fetch"
|
||||||
(error "SKIP (skip-list): can catch an error that occurs when using fetch"))
|
(hs-cleanup!)
|
||||||
|
(let ((_el-div (dom-create-element "div")))
|
||||||
|
(dom-set-attr _el-div "_" "on click fetch /test catch e log e put \"yay\" into me")
|
||||||
|
(dom-append (dom-body) _el-div)
|
||||||
|
(hs-activate! _el-div)
|
||||||
|
(dom-dispatch _el-div "click" nil)
|
||||||
|
(assert= (dom-text-content _el-div) "yay")
|
||||||
|
))
|
||||||
(deftest "can do a simple fetch"
|
(deftest "can do a simple fetch"
|
||||||
(hs-cleanup!)
|
(hs-cleanup!)
|
||||||
(let ((_el-div (dom-create-element "div")))
|
(let ((_el-div (dom-create-element "div")))
|
||||||
@@ -7563,9 +7584,23 @@
|
|||||||
(assert= (dom-text-content _el-div) "yay")
|
(assert= (dom-text-content _el-div) "yay")
|
||||||
))
|
))
|
||||||
(deftest "do not throw passes through 404 response"
|
(deftest "do not throw passes through 404 response"
|
||||||
(error "SKIP (skip-list): do not throw passes through 404 response"))
|
(hs-cleanup!)
|
||||||
|
(let ((_el-div (dom-create-element "div")))
|
||||||
|
(dom-set-attr _el-div "_" "on click fetch /test do not throw then put it into me")
|
||||||
|
(dom-append (dom-body) _el-div)
|
||||||
|
(hs-activate! _el-div)
|
||||||
|
(dom-dispatch _el-div "click" nil)
|
||||||
|
(assert= (dom-text-content _el-div) "the body")
|
||||||
|
))
|
||||||
(deftest "don't throw passes through 404 response"
|
(deftest "don't throw passes through 404 response"
|
||||||
(error "SKIP (skip-list): don't throw passes through 404 response"))
|
(hs-cleanup!)
|
||||||
|
(let ((_el-div (dom-create-element "div")))
|
||||||
|
(dom-set-attr _el-div "_" "on click fetch /test don't throw then put it into me")
|
||||||
|
(dom-append (dom-body) _el-div)
|
||||||
|
(hs-activate! _el-div)
|
||||||
|
(dom-dispatch _el-div "click" nil)
|
||||||
|
(assert= (dom-text-content _el-div) "the body")
|
||||||
|
))
|
||||||
(deftest "submits the fetch parameters to the event handler"
|
(deftest "submits the fetch parameters to the event handler"
|
||||||
(hs-cleanup!)
|
(hs-cleanup!)
|
||||||
(host-set! (host-global "window") "headerCheckPassed" false)
|
(host-set! (host-global "window") "headerCheckPassed" false)
|
||||||
@@ -7577,9 +7612,26 @@
|
|||||||
(assert= (dom-text-content _el-div) "yay")
|
(assert= (dom-text-content _el-div) "yay")
|
||||||
))
|
))
|
||||||
(deftest "throws on non-2xx response by default"
|
(deftest "throws on non-2xx response by default"
|
||||||
(error "SKIP (skip-list): throws on non-2xx response by default"))
|
(hs-cleanup!)
|
||||||
|
(let ((_el-div (dom-create-element "div")))
|
||||||
|
(dom-set-attr _el-div "_" "on click fetch /test catch e put \"caught\" into me")
|
||||||
|
(dom-append (dom-body) _el-div)
|
||||||
|
(hs-activate! _el-div)
|
||||||
|
(dom-dispatch _el-div "click" nil)
|
||||||
|
(assert= (dom-text-content _el-div) "caught")
|
||||||
|
))
|
||||||
(deftest "triggers an event just before fetching"
|
(deftest "triggers an event just before fetching"
|
||||||
(error "SKIP (skip-list): triggers an event just before fetching"))
|
(hs-cleanup!)
|
||||||
|
(host-call (host-global "window") "addEventListener" "hyperscript:beforeFetch" (fn (_event) (dom-set-attr (host-get _event "target") "class" "foo-set")))
|
||||||
|
(let ((_el-div (dom-create-element "div")))
|
||||||
|
(dom-set-attr _el-div "_" "on click fetch \"/test\" then put it into my.innerHTML end")
|
||||||
|
(dom-append (dom-body) _el-div)
|
||||||
|
(hs-activate! _el-div)
|
||||||
|
(assert (not (dom-has-class? _el-div "foo-set")))
|
||||||
|
(dom-dispatch _el-div "click" nil)
|
||||||
|
(assert (dom-has-class? _el-div "foo-set"))
|
||||||
|
(assert= (dom-text-content _el-div) "yay")
|
||||||
|
))
|
||||||
)
|
)
|
||||||
|
|
||||||
;; ── focus (3 tests) ──
|
;; ── focus (3 tests) ──
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class El {
|
|||||||
hasAttribute(n) { return n in this.attributes; }
|
hasAttribute(n) { return n in this.attributes; }
|
||||||
addEventListener(e,f) { if(!this._listeners[e])this._listeners[e]=[]; this._listeners[e].push(f); }
|
addEventListener(e,f) { if(!this._listeners[e])this._listeners[e]=[]; this._listeners[e].push(f); }
|
||||||
removeEventListener(e,f) { if(this._listeners[e])this._listeners[e]=this._listeners[e].filter(x=>x!==f); }
|
removeEventListener(e,f) { if(this._listeners[e])this._listeners[e]=this._listeners[e].filter(x=>x!==f); }
|
||||||
dispatchEvent(ev) { ev.target=ev.target||this; ev.currentTarget=this; const fns=[...(this._listeners[ev.type]||[])]; for(const f of fns){if(ev._si)break;try{f.call(this,ev);}catch(e){}} if(ev.bubbles&&!ev._sp&&this.parentElement){this.parentElement.dispatchEvent(ev);} return !ev.defaultPrevented; }
|
dispatchEvent(ev) { ev.target=ev.target||this; ev.currentTarget=this; const fns=[...(this._listeners[ev.type]||[])]; for(const f of fns){if(ev._si)break;try{f.call(this,ev);}catch(e){}} if(ev.bubbles&&!ev._sp){if(this.parentElement){this.parentElement.dispatchEvent(ev);}else if(globalThis._windowListeners){globalThis.dispatchEvent(ev);}} return !ev.defaultPrevented; }
|
||||||
appendChild(c) { if(c.parentElement)c.parentElement.removeChild(c); c.parentElement=this; c.parentNode=this; this.children.push(c); this.childNodes.push(c); if(this.tagName==='SELECT'&&c.tagName==='OPTION'){this.options.push(c);if(c.selected&&this.selectedIndex<0)this.selectedIndex=this.options.length-1;} this._syncText(); return c; }
|
appendChild(c) { if(c.parentElement)c.parentElement.removeChild(c); c.parentElement=this; c.parentNode=this; this.children.push(c); this.childNodes.push(c); if(this.tagName==='SELECT'&&c.tagName==='OPTION'){this.options.push(c);if(c.selected&&this.selectedIndex<0)this.selectedIndex=this.options.length-1;} this._syncText(); return c; }
|
||||||
removeChild(c) { this.children=this.children.filter(x=>x!==c); this.childNodes=this.childNodes.filter(x=>x!==c); c.parentElement=null; c.parentNode=null; this._syncText(); return c; }
|
removeChild(c) { this.children=this.children.filter(x=>x!==c); this.childNodes=this.childNodes.filter(x=>x!==c); c.parentElement=null; c.parentNode=null; this._syncText(); return c; }
|
||||||
insertBefore(n,r) { if(n.parentElement)n.parentElement.removeChild(n); const i=this.children.indexOf(r); if(i>=0){this.children.splice(i,0,n);this.childNodes.splice(i,0,n);}else{this.children.push(n);this.childNodes.push(n);} n.parentElement=this;n.parentNode=this; this._syncText(); return n; }
|
insertBefore(n,r) { if(n.parentElement)n.parentElement.removeChild(n); const i=this.children.indexOf(r); if(i>=0){this.children.splice(i,0,n);this.childNodes.splice(i,0,n);}else{this.children.push(n);this.childNodes.push(n);} n.parentElement=this;n.parentNode=this; this._syncText(); return n; }
|
||||||
@@ -336,6 +336,11 @@ const document = {
|
|||||||
createEvent(t){return new Ev(t);}, addEventListener(){}, removeEventListener(){},
|
createEvent(t){return new Ev(t);}, addEventListener(){}, removeEventListener(){},
|
||||||
};
|
};
|
||||||
globalThis.document=document; globalThis.window=globalThis; globalThis.HTMLElement=El; globalThis.Element=El;
|
globalThis.document=document; globalThis.window=globalThis; globalThis.HTMLElement=El; globalThis.Element=El;
|
||||||
|
// window event-target shim (for hyperscript:beforeFetch and similar bubbled events)
|
||||||
|
globalThis._windowListeners={};
|
||||||
|
globalThis.addEventListener=function(e,f){if(!globalThis._windowListeners[e])globalThis._windowListeners[e]=[];globalThis._windowListeners[e].push(f);};
|
||||||
|
globalThis.removeEventListener=function(e,f){if(globalThis._windowListeners[e])globalThis._windowListeners[e]=globalThis._windowListeners[e].filter(x=>x!==f);};
|
||||||
|
globalThis.dispatchEvent=function(ev){const fns=[...(globalThis._windowListeners[ev.type]||[])];for(const f of fns){if(ev&&ev._si)break;try{f.call(globalThis,ev);}catch(e){}}return ev?!ev.defaultPrevented:true;};
|
||||||
// cluster-33: cookie store + document.cookie + cookies Proxy.
|
// cluster-33: cookie store + document.cookie + cookies Proxy.
|
||||||
globalThis.__hsCookieStore = new Map();
|
globalThis.__hsCookieStore = new Map();
|
||||||
Object.defineProperty(document, 'cookie', {
|
Object.defineProperty(document, 'cookie', {
|
||||||
@@ -584,9 +589,28 @@ const _fetchRoutes = {
|
|||||||
'/number': { status: 200, body: '1.2' },
|
'/number': { status: 200, body: '1.2' },
|
||||||
'/users/Joe': { status: 200, body: 'Joe', json: '{"name":"Joe"}' },
|
'/users/Joe': { status: 200, body: 'Joe', json: '{"name":"Joe"}' },
|
||||||
};
|
};
|
||||||
|
// Per-test fetch overrides keyed by test name; takes priority over _fetchRoutes.
|
||||||
|
const _fetchScripts = {
|
||||||
|
"as response does not throw on 404":
|
||||||
|
{ "/test": { status: 404, body: "not found" } },
|
||||||
|
"do not throw passes through 404 response":
|
||||||
|
{ "/test": { status: 404, body: "the body" } },
|
||||||
|
"don't throw passes through 404 response":
|
||||||
|
{ "/test": { status: 404, body: "the body" } },
|
||||||
|
"throws on non-2xx response by default":
|
||||||
|
{ "/test": { status: 404, body: "not found" } },
|
||||||
|
"Response can be converted to JSON via as JSON":
|
||||||
|
{ "/test": { status: 200, body: '{"name":"Joe"}', json: '{"name":"Joe"}',
|
||||||
|
contentType: "application/json" } },
|
||||||
|
"can catch an error that occurs when using fetch":
|
||||||
|
{ "/test": { networkError: true } },
|
||||||
|
"triggers an event just before fetching":
|
||||||
|
{ "/test": { status: 200, body: "yay", contentType: "text/html" } },
|
||||||
|
};
|
||||||
function _mockFetch(url) {
|
function _mockFetch(url) {
|
||||||
const route = _fetchRoutes[url] || _fetchRoutes['/test'];
|
const scriptRoutes = _fetchScripts[globalThis.__currentHsTestName];
|
||||||
return { ok: route.status < 400, status: route.status || 200, url: url || '/test',
|
const route = (scriptRoutes && scriptRoutes[url]) || _fetchRoutes[url] || _fetchRoutes['/test'];
|
||||||
|
return { ok: (route.status||200) < 400, status: route.status || 200, url: url || '/test',
|
||||||
_body: route.body || '', _json: route.json || route.body || '', _html: route.html || route.body || '' };
|
_body: route.body || '', _json: route.json || route.body || '', _html: route.html || route.body || '' };
|
||||||
}
|
}
|
||||||
globalThis._driveAsync=function driveAsync(r,d){d=d||0;if(d>500||!r||!r.suspended)return;if(_testDeadline && Date.now()>_testDeadline)throw new Error('TIMEOUT: wall clock exceeded');const req=r.request;const items=req&&(req.items||req);const op=items&&items[0];const opName=typeof op==='string'?op:(op&&op.name)||String(op);
|
globalThis._driveAsync=function driveAsync(r,d){d=d||0;if(d>500||!r||!r.suspended)return;if(_testDeadline && Date.now()>_testDeadline)throw new Error('TIMEOUT: wall clock exceeded');const req=r.request;const items=req&&(req.items||req);const op=items&&items[0];const opName=typeof op==='string'?op:(op&&op.name)||String(op);
|
||||||
@@ -594,13 +618,10 @@ globalThis._driveAsync=function driveAsync(r,d){d=d||0;if(d>500||!r||!r.suspende
|
|||||||
if(opName==='io-sleep'||opName==='wait')doResume(null);
|
if(opName==='io-sleep'||opName==='wait')doResume(null);
|
||||||
else if(opName==='io-fetch'){
|
else if(opName==='io-fetch'){
|
||||||
const url=typeof items[1]==='string'?items[1]:'/test';
|
const url=typeof items[1]==='string'?items[1]:'/test';
|
||||||
const fmt=typeof items[2]==='string'?items[2]:'text';
|
const scriptRoutes=_fetchScripts[globalThis.__currentHsTestName];
|
||||||
const route=_fetchRoutes[url]||_fetchRoutes['/test'];
|
const route=(scriptRoutes&&scriptRoutes[url])||_fetchRoutes[url]||_fetchRoutes['/test'];
|
||||||
if(fmt==='json'){try{doResume(JSON.parse(route.json||route.body||'{}'));}catch(e){doResume(null);}}
|
if(route&&route.networkError){doResume({_type:'dict','_network-error':true,message:'aborted'});}
|
||||||
else if(fmt==='html'){const frag=new El('fragment');frag.nodeType=11;frag.innerHTML=route.html||route.body||'';frag.textContent=frag.innerHTML.replace(/<[^>]*>/g,'');doResume(frag);}
|
else{const st=route.status||200;doResume({_type:'dict',ok:st<400,status:st,url,_body:route.body||'',_json:route.json||route.body||'',_html:route.html||route.body||'',_number:route.number||route.body||''});}
|
||||||
else if(fmt==='response')doResume({ok:(route.status||200)<400,status:route.status||200,url});
|
|
||||||
else if(fmt.toLowerCase()==='number')doResume(parseFloat(route.number||route.body||'0'));
|
|
||||||
else doResume(route.body||'');
|
|
||||||
}
|
}
|
||||||
else if(opName==='io-parse-text'){const resp=items&&items[1];doResume(resp&&resp._body?resp._body:typeof resp==='string'?resp:'');}
|
else if(opName==='io-parse-text'){const resp=items&&items[1];doResume(resp&&resp._body?resp._body:typeof resp==='string'?resp:'');}
|
||||||
else if(opName==='io-parse-json'){const resp=items&&items[1];try{doResume(JSON.parse(typeof resp==='string'?resp:resp&&resp._json?resp._json:'{}'));}catch(e){doResume(null);}}
|
else if(opName==='io-parse-json'){const resp=items&&items[1];try{doResume(JSON.parse(typeof resp==='string'?resp:resp&&resp._json?resp._json:'{}'));}catch(e){doResume(null);}}
|
||||||
@@ -697,6 +718,7 @@ for(let i=startTest;i<Math.min(endTest,testCount);i++){
|
|||||||
globalThis.__hsCookieStore.clear();
|
globalThis.__hsCookieStore.clear();
|
||||||
globalThis.__hsMutationRegistry.length = 0;
|
globalThis.__hsMutationRegistry.length = 0;
|
||||||
globalThis.__hsMutationActive = false;
|
globalThis.__hsMutationActive = false;
|
||||||
|
globalThis._windowListeners={};
|
||||||
globalThis.__currentHsTestName = name;
|
globalThis.__currentHsTestName = name;
|
||||||
|
|
||||||
// Enable step limit for timeout protection
|
// Enable step limit for timeout protection
|
||||||
|
|||||||
@@ -125,19 +125,9 @@ SKIP_TEST_NAMES = {
|
|||||||
"can ignore when target doesn't exist",
|
"can ignore when target doesn't exist",
|
||||||
"can ignore when target doesn\\'t exist",
|
"can ignore when target doesn\\'t exist",
|
||||||
"can handle an or after a from clause",
|
"can handle an or after a from clause",
|
||||||
# upstream 'fetch' category — depend on per-test sinon stubs for 404 / thrown errors,
|
# upstream 'fetch' category — real DocumentFragment semantics (`its childElementCount`
|
||||||
# or on real DocumentFragment semantics (`its childElementCount` after `as html`).
|
# after `as html`) not exercisable with our DOM mock.
|
||||||
# Our generic test-runner mock returns a fixed 200 response, so these cases
|
|
||||||
# (non-2xx handling, error path, before-fetch event, real DOM fragment) can't be
|
|
||||||
# exercised here.
|
|
||||||
"can do a simple fetch w/ html",
|
"can do a simple fetch w/ html",
|
||||||
"triggers an event just before fetching",
|
|
||||||
"can catch an error that occurs when using fetch",
|
|
||||||
"throws on non-2xx response by default",
|
|
||||||
"do not throw passes through 404 response",
|
|
||||||
"don't throw passes through 404 response",
|
|
||||||
"as response does not throw on 404",
|
|
||||||
"Response can be converted to JSON via as JSON",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -973,6 +963,24 @@ def parse_dev_body(body, elements, var_names):
|
|||||||
else:
|
else:
|
||||||
pre_setups.append(('__hs_config__', op_expr))
|
pre_setups.append(('__hs_config__', op_expr))
|
||||||
continue
|
continue
|
||||||
|
# window.addEventListener(EVT, (param) => { param.target.PROP = 'VAL'; })
|
||||||
|
wa = re.search(
|
||||||
|
r"window\.addEventListener\(\s*(['\"])([^'\"]+)\1\s*,\s*"
|
||||||
|
r"\((\w+)\)\s*=>\s*\{\s*\3\.target\.(\w+)\s*=\s*['\"]([^'\"]+)['\"]\s*;?\s*\}",
|
||||||
|
m.group(1),
|
||||||
|
)
|
||||||
|
if wa:
|
||||||
|
ev_name = wa.group(2)
|
||||||
|
prop = wa.group(4)
|
||||||
|
val = wa.group(5)
|
||||||
|
attr = 'class' if prop == 'className' else prop
|
||||||
|
sx = (f'(host-call (host-global "window") "addEventListener" "{ev_name}" '
|
||||||
|
f'(fn (_event) (dom-set-attr (host-get _event "target") "{attr}" "{val}")))')
|
||||||
|
if seen_html:
|
||||||
|
ops.append(sx)
|
||||||
|
else:
|
||||||
|
pre_setups.append(('__hs_config__', sx))
|
||||||
|
continue
|
||||||
# fall through
|
# fall through
|
||||||
|
|
||||||
# evaluate(() => _hyperscript.config.X = ...) single-line variant.
|
# evaluate(() => _hyperscript.config.X = ...) single-line variant.
|
||||||
|
|||||||
Reference in New Issue
Block a user