diff --git a/tests/hs-run-filtered.js b/tests/hs-run-filtered.js index 8a1406a0..4f32b557 100755 --- a/tests/hs-run-filtered.js +++ b/tests/hs-run-filtered.js @@ -528,9 +528,62 @@ class HsIntersectionObserver { } globalThis.IntersectionObserver = HsIntersectionObserver; globalThis.IntersectionObserverEntry = class {}; -globalThis.navigator={userAgent:'node'}; globalThis.location={href:'http://localhost/',pathname:'/',search:'',hash:''}; +globalThis.navigator={userAgent:'node'}; globalThis.location={href:'http://localhost/',pathname:'/',search:'',hash:'',protocol:'http:',host:'localhost',hostname:'localhost',port:''}; globalThis.history={pushState(){},replaceState(){},back(){},forward(){}}; globalThis.getSelection=()=>({toString:()=>(globalThis.__test_selection||'')}); +// HsWebSocket — cluster-36 WebSocket mock. Records every constructed socket +// in globalThis.__hs_ws_created so tests can assert on URLs and sent frames. +// Tests may override globalThis.WebSocket before activating hyperscript. +globalThis.__hs_ws_created = []; +globalThis.WebSocket = function HsWebSocket(url) { + const sock = { + url, + onmessage: null, + _listeners: {}, + _sent: [], + send(msg) { sock._sent.push(msg); }, + addEventListener(t, h) { (sock._listeners[t] = sock._listeners[t] || []).push(h); }, + 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(_) {} }); } + }; + globalThis.__hs_ws_created.push(sock); + return sock; +}; +// _hs_make_rpc_proxy — cluster-36 RPC proxy factory. Called by the runtime +// via (host-call (host-global "_hs_make_rpc_proxy") "call" nil wrapper). +// wrapper is the SX dict: {raw, url, timeout, pending, ...} +// Returns an ES6 Proxy whose property accesses dispatch RPC calls. +function _hsRpcCall(wrapper, fnName, args, timeoutMs) { + return new Promise((resolve, reject) => { + const iid = String(Math.random()).slice(2) + String(Date.now()); + if (!wrapper.pending) wrapper.pending = {}; + wrapper.pending[iid] = { resolve, reject }; + const raw = wrapper.raw; + const msg = JSON.stringify({ iid, function: fnName, args }); + raw.send(msg); + const ms = timeoutMs === undefined ? (typeof wrapper.timeout === 'number' ? wrapper.timeout : 0) : timeoutMs; + if (ms !== Infinity && typeof ms === 'number') { + setTimeout(() => { + if (wrapper.pending && wrapper.pending[iid]) { + delete wrapper.pending[iid]; + reject('Timed out'); + } + }, ms); + } + }); +} +function _hs_make_rpc_proxy(wrapper, overrides) { + overrides = overrides || {}; + return new Proxy({}, { + get(_, name) { + if (['then', 'catch', 'length', 'toJSON'].includes(name)) return null; + if (name === 'noTimeout') return _hs_make_rpc_proxy(wrapper, Object.assign({}, overrides, { timeout: Infinity })); + if (name === 'timeout') return function(n) { return _hs_make_rpc_proxy(wrapper, Object.assign({}, overrides, { timeout: n })); }; + return function(...args) { return _hsRpcCall(wrapper, name, args, overrides.timeout); }; + } + }); +} +globalThis._hs_make_rpc_proxy = { call: _hs_make_rpc_proxy }; const _origLog = console.log; globalThis.console = { log: () => {}, error: () => {}, warn: () => {}, info: () => {}, debug: () => {} }; // suppress ALL console noise const _log = _origLog; // keep reference for our own output