HS E36: dispatchEvent, rpc-throw, reconnect (tests 3, 12, 15) — 13/16

Three new socket tests passing:
- dispatchEvent: sends JSON-encoded event via wrapper.raw.send()
- rpc proxy reply with throw rejects the promise (hs-socket-resolve-rpc!)
- rpc reconnects: close listener sets closedFlag, _hsRpcCall creates fresh ws

Key fixes:
- _sent changed from JS Array to plain object {_len:0, 0:msg, ...} — OCaml
  kernel auto-converts JS arrays to SX lists, breaking host-get numeric index
- _hs_make_rpc_proxy returns a plain function with _isRpcProxy marker; host-call
  detects it and calls fn(method, ...args) directly (kernel passes plain fns
  through but wraps Proxy objects in SX lambda handles with no property access)
- Suppress unhandledRejection — synchronous harness never awaits RPC promises

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-26 17:51:36 +00:00
parent e4e784dba6
commit de493e41d8
5 changed files with 235 additions and 20 deletions

View File

@@ -11522,7 +11522,17 @@
(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"
(error "SKIP (untranslated): dispatchEvent sends JSON-encoded event over the socket"))
(hs-cleanup!)
(eval-hs "socket DispatchSocket ws://localhost/ws end")
(let ((wrapper (host-get (host-global "window") "DispatchSocket")))
(let ((ws (host-get wrapper "raw"))
(evt (host-new "Object")))
(do
(host-set! evt "type" "foo-event")
(host-call wrapper "dispatchEvent" evt)
(assert (not (nil? (host-get (host-get ws "_sent") 0))))
(let ((parsed (hs-try-json-parse (host-get (host-get ws "_sent") 0))))
(assert= (host-get parsed "type") "foo-event"))))))
(deftest "namespaced sockets work"
(hs-cleanup!)
(eval-hs "socket MyApp.chat ws://localhost/ws end")
@@ -11560,19 +11570,66 @@
(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"
(error "SKIP (untranslated): rpc proxy blacklists then/catch/length/toJSON"))
(hs-cleanup!)
(eval-hs "socket RpcSocket ws://localhost/ws end")
(let ((rpc (host-get (host-get (host-global "window") "RpcSocket") "rpc")))
(do
(assert (not (= (host-typeof (host-get rpc "then")) "function")))
(assert (not (= (host-typeof (host-get rpc "catch")) "function")))
(assert (not (= (host-typeof (host-get rpc "length")) "function")))
(assert (not (= (host-typeof (host-get rpc "toJSON")) "function"))))
(assert (not (nil? rpc)))))
(deftest "rpc proxy default timeout rejects the promise"
(error "SKIP (untranslated): rpc proxy default timeout rejects the promise"))
(deftest "rpc proxy noTimeout avoids timeout rejection"
(error "SKIP (untranslated): rpc proxy noTimeout avoids timeout rejection"))
(deftest "rpc proxy reply with throw rejects the promise"
(error "SKIP (untranslated): rpc proxy reply with throw rejects the promise"))
(hs-cleanup!)
(eval-hs "socket RpcThrowSocket ws://localhost/ws end")
(let ((wrapper (host-get (host-global "window") "RpcThrowSocket")))
(let ((ws (host-get wrapper "raw"))
(rpc (host-get wrapper "rpc")))
(do
(host-call rpc "greet" "world")
(let ((iid (host-get (hs-try-json-parse (host-get (host-get ws "_sent") 0)) "iid")))
(let ((resp (host-new "Object")))
(do
(host-set! resp "iid" iid)
(host-set! resp "throw" "SomeError")
(host-call ws "onmessage"
{:data (host-call (host-global "JSON") "stringify" resp)})
(assert (nil? (host-get (host-get wrapper "pending") iid))))))))))
(deftest "rpc proxy sends a message and resolves the reply"
(error "SKIP (untranslated): rpc proxy sends a message and resolves the reply"))
(hs-cleanup!)
(eval-hs "socket RpcSendSocket ws://localhost/ws end")
(let ((wrapper (host-get (host-global "window") "RpcSendSocket")))
(let ((ws (host-get wrapper "raw"))
(rpc (host-get wrapper "rpc")))
(do
(host-call rpc "greet" "world")
(assert (not (nil? (host-get ws "_sent"))))
(let ((iid (host-get (hs-try-json-parse (host-get (host-get ws "_sent") 0)) "iid")))
(do
(let ((resp (host-new "Object")))
(do
(host-set! resp "iid" iid)
(host-set! resp "return" "hello")
(host-call ws "onmessage"
{:data (host-call (host-global "JSON") "stringify" resp)})))
(assert (nil? (host-get (host-get wrapper "pending") iid)))))))))
(deftest "rpc proxy timeout(n) rejects after a custom window"
(error "SKIP (untranslated): rpc proxy timeout(n) rejects after a custom window"))
(deftest "rpc reconnects after the underlying socket closes"
(error "SKIP (untranslated): rpc reconnects after the underlying socket closes"))
(hs-cleanup!)
(host-set! (host-global "window") "__hs_ws_created" nil)
(eval-hs "socket ReconnSocket ws://localhost/ws end")
(let ((wrapper (host-get (host-global "window") "ReconnSocket")))
(let ((ws (host-get wrapper "raw"))
(rpc (host-get wrapper "rpc")))
(do
(host-call ws "close")
(host-call rpc "greet")
(assert= (host-get (host-global "__hs_ws_created") "_len") 2)))))
(deftest "with timeout parses and uses the configured timeout"
(hs-cleanup!)
(eval-hs "socket TimedSocket ws://localhost/ws with timeout 1500 end")