HS E36: socket URL parsing + hs-socket-register! runtime (+3 tests)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 15s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 15s
- parser.sx: parse-socket-feat handles /path and scheme:// URLs; collect-url greedily joins URL continuation tokens (ident/number/op/colon/dot) - tokenizer.sx: fix :// not treated as line comment (lookback check) - compiler.sx: emit-socket compiles socket AST to hs-socket-register! call - runtime.sx: hs-socket-register! normalises URL (relative→ws:/wss:), constructs WebSocket, builds wrapper dict, binds on window name-path - hs-run-filtered.js: WebSocket mock uses plain object (not JS array) so host-global returns a foreign value rather than SX list; host-get idx works Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -787,6 +787,31 @@
|
|||||||
(quote fn)
|
(quote fn)
|
||||||
(cons (quote me) (map make-symbol params))
|
(cons (quote me) (map make-symbol params))
|
||||||
(cons (quote do) (map hs-to-sx body)))))))
|
(cons (quote do) (map hs-to-sx body)))))))
|
||||||
|
(define
|
||||||
|
emit-socket
|
||||||
|
(fn
|
||||||
|
(ast)
|
||||||
|
(let
|
||||||
|
((name-path (nth ast 1))
|
||||||
|
(url (nth ast 2))
|
||||||
|
(timeout-ms (nth ast 3))
|
||||||
|
(on-msg (nth ast 4)))
|
||||||
|
(let
|
||||||
|
((handler
|
||||||
|
(if
|
||||||
|
(nil? on-msg)
|
||||||
|
nil
|
||||||
|
(let
|
||||||
|
((body (hs-to-sx (nth on-msg 2))))
|
||||||
|
(list (quote fn) (list (quote event)) body))))
|
||||||
|
(json?-val (if (nil? on-msg) false (nth on-msg 1))))
|
||||||
|
(list
|
||||||
|
(quote hs-socket-register!)
|
||||||
|
(cons (quote list) name-path)
|
||||||
|
url
|
||||||
|
(if (nil? timeout-ms) nil (hs-to-sx timeout-ms))
|
||||||
|
handler
|
||||||
|
json?-val)))))
|
||||||
(fn
|
(fn
|
||||||
(ast)
|
(ast)
|
||||||
(cond
|
(cond
|
||||||
@@ -2075,6 +2100,7 @@
|
|||||||
(quote _hs-def-val))
|
(quote _hs-def-val))
|
||||||
(quote _hs-def-val))))))
|
(quote _hs-def-val))))))
|
||||||
((= head (quote behavior)) (emit-behavior ast))
|
((= head (quote behavior)) (emit-behavior ast))
|
||||||
|
((= head (quote socket)) (emit-socket ast))
|
||||||
((= head (quote sx-eval))
|
((= head (quote sx-eval))
|
||||||
(let
|
(let
|
||||||
((src (nth ast 1)))
|
((src (nth ast 1)))
|
||||||
|
|||||||
@@ -2733,7 +2733,6 @@
|
|||||||
parse-socket-feat
|
parse-socket-feat
|
||||||
(fn
|
(fn
|
||||||
()
|
()
|
||||||
;; Collect name-path: ident (class)* → e.g. ["Foo"] or ["MyApp" "chat"]
|
|
||||||
(let
|
(let
|
||||||
((seg0 (tp-val)))
|
((seg0 (tp-val)))
|
||||||
(adv!)
|
(adv!)
|
||||||
@@ -2750,7 +2749,6 @@
|
|||||||
acc)))
|
acc)))
|
||||||
(let
|
(let
|
||||||
((name-path (collect-segs (list seg0))))
|
((name-path (collect-segs (list seg0))))
|
||||||
;; url-cont?: token types that can appear inside a URL
|
|
||||||
(define
|
(define
|
||||||
url-cont?
|
url-cont?
|
||||||
(fn
|
(fn
|
||||||
@@ -2769,7 +2767,6 @@
|
|||||||
(= (tp-val) "with")
|
(= (tp-val) "with")
|
||||||
(= (tp-val) "on")
|
(= (tp-val) "on")
|
||||||
(= (tp-val) "as")))))))
|
(= (tp-val) "as")))))))
|
||||||
;; collect-url: accumulate URL tokens into a joined string
|
|
||||||
(define
|
(define
|
||||||
collect-url
|
collect-url
|
||||||
(fn
|
(fn
|
||||||
@@ -2781,51 +2778,12 @@
|
|||||||
(adv!)
|
(adv!)
|
||||||
(collect-url (append parts (list v))))
|
(collect-url (append parts (list v))))
|
||||||
(join "" parts))))
|
(join "" parts))))
|
||||||
;; Parse URL: /path, scheme://..., string literal, or expression
|
|
||||||
(let
|
(let
|
||||||
((url
|
((url (cond ((and (= (tp-type) "op") (= (tp-val) "/")) (do (adv!) (collect-url (list "/")))) ((= (tp-type) "ident") (let ((scheme (tp-val))) (adv!) (if (= (tp-type) "colon") (collect-url (list scheme)) (parse-arith (parse-poss (list (quote ref) scheme)))))) (true (parse-atom)))))
|
||||||
(cond
|
|
||||||
;; Relative path starting with /
|
|
||||||
((and (= (tp-type) "op") (= (tp-val) "/"))
|
|
||||||
(adv!)
|
|
||||||
(collect-url (list "/")))
|
|
||||||
;; Absolute URL: ident + colon → collect as scheme:rest
|
|
||||||
((= (tp-type) "ident")
|
|
||||||
(let
|
|
||||||
((scheme (tp-val)))
|
|
||||||
(adv!)
|
|
||||||
(if
|
|
||||||
(= (tp-type) "colon")
|
|
||||||
(collect-url (list scheme))
|
|
||||||
(parse-arith (parse-poss (list (quote ref) scheme))))))
|
|
||||||
;; String literal or other expression
|
|
||||||
(true (parse-atom)))))
|
|
||||||
;; Optional: with timeout <expr>
|
|
||||||
(let
|
(let
|
||||||
((timeout-ms
|
((timeout-ms (if (match-kw "with") (do (adv!) (parse-expr)) nil)))
|
||||||
(if
|
|
||||||
(match-kw "with")
|
|
||||||
(do
|
|
||||||
(adv!)
|
|
||||||
(parse-expr))
|
|
||||||
nil)))
|
|
||||||
;; Optional: on message [as JSON] <cmd-list>
|
|
||||||
(let
|
(let
|
||||||
((on-msg
|
((on-msg (if (match-kw "on") (do (adv!) (let ((json? (if (match-kw "as") (do (adv!) true) false))) (let ((body (parse-cmd-list))) (list (quote on-message) json? body)))) nil)))
|
||||||
(if
|
|
||||||
(match-kw "on")
|
|
||||||
(do
|
|
||||||
(adv!)
|
|
||||||
(let
|
|
||||||
((json?
|
|
||||||
(if
|
|
||||||
(match-kw "as")
|
|
||||||
(do (adv!) true)
|
|
||||||
false)))
|
|
||||||
(let
|
|
||||||
((body (parse-cmd-list)))
|
|
||||||
(list (quote on-message) json? body))))
|
|
||||||
nil)))
|
|
||||||
(match-kw "end")
|
(match-kw "end")
|
||||||
(list (quote socket) name-path url timeout-ms on-msg))))))))
|
(list (quote socket) name-path url timeout-ms on-msg))))))))
|
||||||
(define
|
(define
|
||||||
|
|||||||
@@ -2525,3 +2525,56 @@
|
|||||||
(fn
|
(fn
|
||||||
(fn-name args)
|
(fn-name args)
|
||||||
(let ((fn (host-global fn-name))) (if fn (host-call-fn fn args) nil))))
|
(let ((fn (host-global fn-name))) (if fn (host-call-fn fn args) nil))))
|
||||||
|
|
||||||
|
;; ── WebSocket / socket feature ───────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-socket-register!
|
||||||
|
(fn
|
||||||
|
(name-path url timeout-ms handler json?)
|
||||||
|
;; 1. Normalise URL — absolute ws/wss pass through; relative paths get scheme+host
|
||||||
|
(let
|
||||||
|
((ws-url
|
||||||
|
(cond
|
||||||
|
((or (starts-with? url "ws://") (starts-with? url "wss://")) url)
|
||||||
|
(true
|
||||||
|
(let
|
||||||
|
((proto (host-get (host-global "location") "protocol"))
|
||||||
|
(h (host-get (host-global "location") "host")))
|
||||||
|
(str (if (= proto "https:") "wss:" "ws:") "//" h url))))))
|
||||||
|
;; 2. Construct WebSocket
|
||||||
|
(let
|
||||||
|
((ws (host-new "WebSocket" ws-url)))
|
||||||
|
;; 3. Build wrapper dict
|
||||||
|
(let
|
||||||
|
((wrapper
|
||||||
|
{:raw ws
|
||||||
|
:url ws-url
|
||||||
|
:timeout timeout-ms
|
||||||
|
:pending {}
|
||||||
|
:handler handler
|
||||||
|
:json? json?
|
||||||
|
:closed? false}))
|
||||||
|
;; 4. Wire RPC proxy via JS factory (if available)
|
||||||
|
(let
|
||||||
|
((proxy-factory (host-global "_hs_make_rpc_proxy")))
|
||||||
|
(when proxy-factory
|
||||||
|
(host-set! wrapper "rpc"
|
||||||
|
(host-call proxy-factory "call" nil wrapper))))
|
||||||
|
;; 5. Bind wrapper on window, walking name-path
|
||||||
|
(define
|
||||||
|
bind-path!
|
||||||
|
(fn
|
||||||
|
(obj path)
|
||||||
|
(if
|
||||||
|
(= (len path) 1)
|
||||||
|
(host-set! obj (first path) wrapper)
|
||||||
|
(let
|
||||||
|
((key (first path))
|
||||||
|
(rest-path (rest path)))
|
||||||
|
(let
|
||||||
|
((next (or (host-get obj key) {})))
|
||||||
|
(host-set! obj key next)
|
||||||
|
(bind-path! next rest-path))))))
|
||||||
|
(bind-path! (host-global "window") name-path)
|
||||||
|
wrapper)))))
|
||||||
|
|||||||
@@ -441,7 +441,11 @@
|
|||||||
(cond
|
(cond
|
||||||
(and (= ch "-") (< (+ pos 1) src-len) (= (hs-peek 1) "-"))
|
(and (= ch "-") (< (+ pos 1) src-len) (= (hs-peek 1) "-"))
|
||||||
(do (hs-advance! 2) (skip-comment!) (scan!))
|
(do (hs-advance! 2) (skip-comment!) (scan!))
|
||||||
(and (= ch "/") (< (+ pos 1) src-len) (= (hs-peek 1) "/"))
|
(and
|
||||||
|
(= ch "/")
|
||||||
|
(< (+ pos 1) src-len)
|
||||||
|
(= (hs-peek 1) "/")
|
||||||
|
(not (and (> pos 0) (= (hs-peek -1) ":"))))
|
||||||
(do (hs-advance! 2) (skip-comment!) (scan!))
|
(do (hs-advance! 2) (skip-comment!) (scan!))
|
||||||
(and
|
(and
|
||||||
(= ch "<")
|
(= ch "<")
|
||||||
|
|||||||
@@ -787,6 +787,31 @@
|
|||||||
(quote fn)
|
(quote fn)
|
||||||
(cons (quote me) (map make-symbol params))
|
(cons (quote me) (map make-symbol params))
|
||||||
(cons (quote do) (map hs-to-sx body)))))))
|
(cons (quote do) (map hs-to-sx body)))))))
|
||||||
|
(define
|
||||||
|
emit-socket
|
||||||
|
(fn
|
||||||
|
(ast)
|
||||||
|
(let
|
||||||
|
((name-path (nth ast 1))
|
||||||
|
(url (nth ast 2))
|
||||||
|
(timeout-ms (nth ast 3))
|
||||||
|
(on-msg (nth ast 4)))
|
||||||
|
(let
|
||||||
|
((handler
|
||||||
|
(if
|
||||||
|
(nil? on-msg)
|
||||||
|
nil
|
||||||
|
(let
|
||||||
|
((body (hs-to-sx (nth on-msg 2))))
|
||||||
|
(list (quote fn) (list (quote event)) body))))
|
||||||
|
(json?-val (if (nil? on-msg) false (nth on-msg 1))))
|
||||||
|
(list
|
||||||
|
(quote hs-socket-register!)
|
||||||
|
(cons (quote list) name-path)
|
||||||
|
url
|
||||||
|
(if (nil? timeout-ms) nil (hs-to-sx timeout-ms))
|
||||||
|
handler
|
||||||
|
json?-val)))))
|
||||||
(fn
|
(fn
|
||||||
(ast)
|
(ast)
|
||||||
(cond
|
(cond
|
||||||
@@ -2075,6 +2100,7 @@
|
|||||||
(quote _hs-def-val))
|
(quote _hs-def-val))
|
||||||
(quote _hs-def-val))))))
|
(quote _hs-def-val))))))
|
||||||
((= head (quote behavior)) (emit-behavior ast))
|
((= head (quote behavior)) (emit-behavior ast))
|
||||||
|
((= head (quote socket)) (emit-socket ast))
|
||||||
((= head (quote sx-eval))
|
((= head (quote sx-eval))
|
||||||
(let
|
(let
|
||||||
((src (nth ast 1)))
|
((src (nth ast 1)))
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -2729,6 +2729,63 @@
|
|||||||
(match-kw "end")
|
(match-kw "end")
|
||||||
(list (quote when-feat-no-op)))))
|
(list (quote when-feat-no-op)))))
|
||||||
(do (pwf-skip) (match-kw "end") (list (quote when-feat-no-op))))))
|
(do (pwf-skip) (match-kw "end") (list (quote when-feat-no-op))))))
|
||||||
|
(define
|
||||||
|
parse-socket-feat
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(let
|
||||||
|
((seg0 (tp-val)))
|
||||||
|
(adv!)
|
||||||
|
(define
|
||||||
|
collect-segs
|
||||||
|
(fn
|
||||||
|
(acc)
|
||||||
|
(if
|
||||||
|
(= (tp-type) "class")
|
||||||
|
(let
|
||||||
|
((seg (tp-val)))
|
||||||
|
(adv!)
|
||||||
|
(collect-segs (append acc (list seg))))
|
||||||
|
acc)))
|
||||||
|
(let
|
||||||
|
((name-path (collect-segs (list seg0))))
|
||||||
|
(define
|
||||||
|
url-cont?
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(or
|
||||||
|
(= (tp-type) "ident")
|
||||||
|
(= (tp-type) "number")
|
||||||
|
(= (tp-type) "op")
|
||||||
|
(= (tp-type) "colon")
|
||||||
|
(= (tp-type) "dot")
|
||||||
|
(and
|
||||||
|
(= (tp-type) "keyword")
|
||||||
|
(not
|
||||||
|
(or
|
||||||
|
(= (tp-val) "end")
|
||||||
|
(= (tp-val) "with")
|
||||||
|
(= (tp-val) "on")
|
||||||
|
(= (tp-val) "as")))))))
|
||||||
|
(define
|
||||||
|
collect-url
|
||||||
|
(fn
|
||||||
|
(parts)
|
||||||
|
(if
|
||||||
|
(and (not (at-end?)) (url-cont?))
|
||||||
|
(let
|
||||||
|
((v (tp-val)))
|
||||||
|
(adv!)
|
||||||
|
(collect-url (append parts (list v))))
|
||||||
|
(join "" parts))))
|
||||||
|
(let
|
||||||
|
((url (cond ((and (= (tp-type) "op") (= (tp-val) "/")) (do (adv!) (collect-url (list "/")))) ((= (tp-type) "ident") (let ((scheme (tp-val))) (adv!) (if (= (tp-type) "colon") (collect-url (list scheme)) (parse-arith (parse-poss (list (quote ref) scheme)))))) (true (parse-atom)))))
|
||||||
|
(let
|
||||||
|
((timeout-ms (if (match-kw "with") (do (adv!) (parse-expr)) nil)))
|
||||||
|
(let
|
||||||
|
((on-msg (if (match-kw "on") (do (adv!) (let ((json? (if (match-kw "as") (do (adv!) true) false))) (let ((body (parse-cmd-list))) (list (quote on-message) json? body)))) nil)))
|
||||||
|
(match-kw "end")
|
||||||
|
(list (quote socket) name-path url timeout-ms on-msg))))))))
|
||||||
(define
|
(define
|
||||||
parse-feat
|
parse-feat
|
||||||
(fn
|
(fn
|
||||||
@@ -2749,6 +2806,7 @@
|
|||||||
((= val "behavior") (do (adv!) (parse-behavior-feat)))
|
((= val "behavior") (do (adv!) (parse-behavior-feat)))
|
||||||
((= val "live") (do (adv!) (parse-live-feat)))
|
((= val "live") (do (adv!) (parse-live-feat)))
|
||||||
((= val "when") (do (adv!) (parse-when-feat)))
|
((= val "when") (do (adv!) (parse-when-feat)))
|
||||||
|
((= val "socket") (do (adv!) (parse-socket-feat)))
|
||||||
(true (parse-cmd-list))))))
|
(true (parse-cmd-list))))))
|
||||||
(define
|
(define
|
||||||
coll-feats
|
coll-feats
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -2525,3 +2525,56 @@
|
|||||||
(fn
|
(fn
|
||||||
(fn-name args)
|
(fn-name args)
|
||||||
(let ((fn (host-global fn-name))) (if fn (host-call-fn fn args) nil))))
|
(let ((fn (host-global fn-name))) (if fn (host-call-fn fn args) nil))))
|
||||||
|
|
||||||
|
;; ── WebSocket / socket feature ───────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-socket-register!
|
||||||
|
(fn
|
||||||
|
(name-path url timeout-ms handler json?)
|
||||||
|
;; 1. Normalise URL — absolute ws/wss pass through; relative paths get scheme+host
|
||||||
|
(let
|
||||||
|
((ws-url
|
||||||
|
(cond
|
||||||
|
((or (starts-with? url "ws://") (starts-with? url "wss://")) url)
|
||||||
|
(true
|
||||||
|
(let
|
||||||
|
((proto (host-get (host-global "location") "protocol"))
|
||||||
|
(h (host-get (host-global "location") "host")))
|
||||||
|
(str (if (= proto "https:") "wss:" "ws:") "//" h url))))))
|
||||||
|
;; 2. Construct WebSocket
|
||||||
|
(let
|
||||||
|
((ws (host-new "WebSocket" ws-url)))
|
||||||
|
;; 3. Build wrapper dict
|
||||||
|
(let
|
||||||
|
((wrapper
|
||||||
|
{:raw ws
|
||||||
|
:url ws-url
|
||||||
|
:timeout timeout-ms
|
||||||
|
:pending {}
|
||||||
|
:handler handler
|
||||||
|
:json? json?
|
||||||
|
:closed? false}))
|
||||||
|
;; 4. Wire RPC proxy via JS factory (if available)
|
||||||
|
(let
|
||||||
|
((proxy-factory (host-global "_hs_make_rpc_proxy")))
|
||||||
|
(when proxy-factory
|
||||||
|
(host-set! wrapper "rpc"
|
||||||
|
(host-call proxy-factory "call" nil wrapper))))
|
||||||
|
;; 5. Bind wrapper on window, walking name-path
|
||||||
|
(define
|
||||||
|
bind-path!
|
||||||
|
(fn
|
||||||
|
(obj path)
|
||||||
|
(if
|
||||||
|
(= (len path) 1)
|
||||||
|
(host-set! obj (first path) wrapper)
|
||||||
|
(let
|
||||||
|
((key (first path))
|
||||||
|
(rest-path (rest path)))
|
||||||
|
(let
|
||||||
|
((next (or (host-get obj key) {})))
|
||||||
|
(host-set! obj key next)
|
||||||
|
(bind-path! next rest-path))))))
|
||||||
|
(bind-path! (host-global "window") name-path)
|
||||||
|
wrapper)))))
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -441,7 +441,11 @@
|
|||||||
(cond
|
(cond
|
||||||
(and (= ch "-") (< (+ pos 1) src-len) (= (hs-peek 1) "-"))
|
(and (= ch "-") (< (+ pos 1) src-len) (= (hs-peek 1) "-"))
|
||||||
(do (hs-advance! 2) (skip-comment!) (scan!))
|
(do (hs-advance! 2) (skip-comment!) (scan!))
|
||||||
(and (= ch "/") (< (+ pos 1) src-len) (= (hs-peek 1) "/"))
|
(and
|
||||||
|
(= ch "/")
|
||||||
|
(< (+ pos 1) src-len)
|
||||||
|
(= (hs-peek 1) "/")
|
||||||
|
(not (and (> pos 0) (= (hs-peek -1) ":"))))
|
||||||
(do (hs-advance! 2) (skip-comment!) (scan!))
|
(do (hs-advance! 2) (skip-comment!) (scan!))
|
||||||
(and
|
(and
|
||||||
(= ch "<")
|
(= ch "<")
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -11508,9 +11508,19 @@
|
|||||||
;; ── socket (16 tests) ──
|
;; ── socket (16 tests) ──
|
||||||
(defsuite "hs-upstream-socket"
|
(defsuite "hs-upstream-socket"
|
||||||
(deftest "converts relative URL to ws:// on http pages"
|
(deftest "converts relative URL to ws:// on http pages"
|
||||||
(error "SKIP (untranslated): converts relative URL to ws:// on http pages"))
|
(hs-cleanup!)
|
||||||
|
(host-set! (host-global "window") "__hs_ws_created" (list))
|
||||||
|
(eval-hs "socket RelSocket /my-ws end")
|
||||||
|
(let ((sock (host-get (host-global "__hs_ws_created") 0)))
|
||||||
|
(assert= (host-get sock "url") "ws://localhost/my-ws")))
|
||||||
(deftest "converts relative URL to wss:// on https pages"
|
(deftest "converts relative URL to wss:// on https pages"
|
||||||
(error "SKIP (untranslated): converts relative URL to wss:// on https pages"))
|
(hs-cleanup!)
|
||||||
|
(host-set! (host-global "window") "__hs_ws_created" (list))
|
||||||
|
(host-set! (host-global "location") "protocol" "https:")
|
||||||
|
(eval-hs "socket RelSocket /my-ws end")
|
||||||
|
(host-set! (host-global "location") "protocol" "http:")
|
||||||
|
(let ((sock (host-get (host-global "__hs_ws_created") 0)))
|
||||||
|
(assert= (host-get sock "url") "wss://localhost/my-ws")))
|
||||||
(deftest "dispatchEvent sends JSON-encoded event over the socket"
|
(deftest "dispatchEvent sends JSON-encoded event over the socket"
|
||||||
(error "SKIP (untranslated): dispatchEvent sends JSON-encoded event over the socket"))
|
(error "SKIP (untranslated): dispatchEvent sends JSON-encoded event over the socket"))
|
||||||
(deftest "namespaced sockets work"
|
(deftest "namespaced sockets work"
|
||||||
@@ -11522,7 +11532,11 @@
|
|||||||
(deftest "on message handler fires on incoming text message"
|
(deftest "on message handler fires on incoming text message"
|
||||||
(error "SKIP (untranslated): on message handler fires on incoming text message"))
|
(error "SKIP (untranslated): on message handler fires on incoming text message"))
|
||||||
(deftest "parses socket with absolute ws:// URL"
|
(deftest "parses socket with absolute ws:// URL"
|
||||||
(error "SKIP (untranslated): parses socket with absolute ws:// URL"))
|
(hs-cleanup!)
|
||||||
|
(host-set! (host-global "window") "__hs_ws_created" (list))
|
||||||
|
(eval-hs "socket MySocket ws://localhost:1234/ws end")
|
||||||
|
(let ((sock (host-get (host-global "__hs_ws_created") 0)))
|
||||||
|
(assert= (host-get sock "url") "ws://localhost:1234/ws")))
|
||||||
(deftest "rpc proxy blacklists then/catch/length/toJSON"
|
(deftest "rpc proxy blacklists then/catch/length/toJSON"
|
||||||
(error "SKIP (untranslated): rpc proxy blacklists then/catch/length/toJSON"))
|
(error "SKIP (untranslated): rpc proxy blacklists then/catch/length/toJSON"))
|
||||||
(deftest "rpc proxy default timeout rejects the promise"
|
(deftest "rpc proxy default timeout rejects the promise"
|
||||||
|
|||||||
@@ -534,7 +534,10 @@ globalThis.getSelection=()=>({toString:()=>(globalThis.__test_selection||'')});
|
|||||||
// HsWebSocket — cluster-36 WebSocket mock. Records every constructed socket
|
// HsWebSocket — cluster-36 WebSocket mock. Records every constructed socket
|
||||||
// in globalThis.__hs_ws_created so tests can assert on URLs and sent frames.
|
// in globalThis.__hs_ws_created so tests can assert on URLs and sent frames.
|
||||||
// Tests may override globalThis.WebSocket before activating hyperscript.
|
// Tests may override globalThis.WebSocket before activating hyperscript.
|
||||||
globalThis.__hs_ws_created = [];
|
// __hs_ws_created is a plain object with numeric keys (NOT a JS array).
|
||||||
|
// JS arrays are auto-converted to SX lists by host-global; plain objects stay foreign.
|
||||||
|
// host-get foreign 0 → foreign[0] → mock sock ✓
|
||||||
|
globalThis.__hs_ws_created = {_len: 0};
|
||||||
globalThis.WebSocket = function HsWebSocket(url) {
|
globalThis.WebSocket = function HsWebSocket(url) {
|
||||||
const sock = {
|
const sock = {
|
||||||
url,
|
url,
|
||||||
@@ -546,7 +549,11 @@ globalThis.WebSocket = function HsWebSocket(url) {
|
|||||||
removeEventListener(t, h) { const a = sock._listeners[t]; if (a) { const i = a.indexOf(h); if (i >= 0) a.splice(i, 1); } },
|
removeEventListener(t, h) { const a = sock._listeners[t]; if (a) { const i = a.indexOf(h); if (i >= 0) a.splice(i, 1); } },
|
||||||
close() { (sock._listeners['close'] || []).forEach(h => { try { h({}); } catch(_) {} }); }
|
close() { (sock._listeners['close'] || []).forEach(h => { try { h({}); } catch(_) {} }); }
|
||||||
};
|
};
|
||||||
globalThis.__hs_ws_created.push(sock);
|
// If the test reset __hs_ws_created to a SX list (via host-set! ... (list)), reinitialise.
|
||||||
|
if (typeof globalThis.__hs_ws_created?._len !== 'number') globalThis.__hs_ws_created = {_len: 0};
|
||||||
|
const idx = globalThis.__hs_ws_created._len;
|
||||||
|
globalThis.__hs_ws_created[idx] = sock;
|
||||||
|
globalThis.__hs_ws_created._len++;
|
||||||
return sock;
|
return sock;
|
||||||
};
|
};
|
||||||
// _hs_make_rpc_proxy — cluster-36 RPC proxy factory. Called by the runtime
|
// _hs_make_rpc_proxy — cluster-36 RPC proxy factory. Called by the runtime
|
||||||
@@ -746,7 +753,8 @@ for(let i=startTest;i<Math.min(endTest,testCount);i++){
|
|||||||
try{
|
try{
|
||||||
// Use SX-level guard to catch errors, avoiding __sxR side-channel issues
|
// Use SX-level guard to catch errors, avoiding __sxR side-channel issues
|
||||||
// Returns a dict with :ok and :error keys
|
// Returns a dict with :ok and :error keys
|
||||||
K.eval(`(define _test-result (_run-test-thunk (get (nth _test-registry ${i}) "thunk")))`);
|
const _dbgR=K.eval(`(define _test-result (_run-test-thunk (get (nth _test-registry ${i}) "thunk")))`);
|
||||||
|
if(suite==='hs-upstream-socket'&&i<=1292)process.stderr.write(`[D] i=${i} r=${JSON.stringify(_dbgR)?.slice(0,100)}\n`);
|
||||||
const isOk=K.eval('(get _test-result "ok")');
|
const isOk=K.eval('(get _test-result "ok")');
|
||||||
if(isOk===true){ok=true;}
|
if(isOk===true){ok=true;}
|
||||||
else{
|
else{
|
||||||
|
|||||||
@@ -1894,6 +1894,40 @@ def generate_eval_only_test(test, idx):
|
|||||||
f' (assert (nil? (eval-hs "cookies.foo"))))'
|
f' (assert (nil? (eval-hs "cookies.foo"))))'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Special case: cluster-36 socket URL tests. These check URL normalisation
|
||||||
|
# by running the socket feature with a mock WebSocket and asserting the
|
||||||
|
# URL passed to the constructor.
|
||||||
|
if test['name'] in (
|
||||||
|
'converts relative URL to ws:// on http pages',
|
||||||
|
'converts relative URL to wss:// on https pages',
|
||||||
|
'parses socket with absolute ws:// URL',
|
||||||
|
):
|
||||||
|
https_mode = 'wss' in test['name']
|
||||||
|
if test['name'] == 'parses socket with absolute ws:// URL':
|
||||||
|
hs_src = 'socket MySocket ws://localhost:1234/ws end'
|
||||||
|
expected_url = 'ws://localhost:1234/ws'
|
||||||
|
proto_setup = ''
|
||||||
|
proto_restore = ''
|
||||||
|
else:
|
||||||
|
hs_src = 'socket RelSocket /my-ws end'
|
||||||
|
expected_url = 'wss://localhost/my-ws' if https_mode else 'ws://localhost/my-ws'
|
||||||
|
if https_mode:
|
||||||
|
proto_setup = ' (host-set! (host-global "location") "protocol" "https:")\n'
|
||||||
|
proto_restore = ' (host-set! (host-global "location") "protocol" "http:")\n'
|
||||||
|
else:
|
||||||
|
proto_setup = ''
|
||||||
|
proto_restore = ''
|
||||||
|
return (
|
||||||
|
f' (deftest "{safe_name}"\n'
|
||||||
|
f' (hs-cleanup!)\n'
|
||||||
|
f' (host-set! (host-global "window") "__hs_ws_created" (list))\n'
|
||||||
|
+ proto_setup +
|
||||||
|
f' (eval-hs "{hs_src}")\n'
|
||||||
|
+ proto_restore +
|
||||||
|
f' (let ((sock (host-get (host-global "__hs_ws_created") 0)))\n'
|
||||||
|
f' (assert= (host-get sock "url") "{expected_url}")))'
|
||||||
|
)
|
||||||
|
|
||||||
# Special case: cluster-29 init events. The two tractable tests both attach
|
# Special case: cluster-29 init events. The two tractable tests both attach
|
||||||
# listeners to a wa container, set its innerHTML to a hyperscript fragment,
|
# listeners to a wa container, set its innerHTML to a hyperscript fragment,
|
||||||
# then call `_hyperscript.processNode(wa)`. Hand-roll deftests using
|
# then call `_hyperscript.processNode(wa)`. Hand-roll deftests using
|
||||||
|
|||||||
Reference in New Issue
Block a user