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:
@@ -81,7 +81,7 @@ class El {
|
||||
hasAttribute(n) { return n in this.attributes; }
|
||||
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); }
|
||||
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; }
|
||||
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; }
|
||||
@@ -336,6 +336,11 @@ const document = {
|
||||
createEvent(t){return new Ev(t);}, addEventListener(){}, removeEventListener(){},
|
||||
};
|
||||
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.
|
||||
globalThis.__hsCookieStore = new Map();
|
||||
Object.defineProperty(document, 'cookie', {
|
||||
@@ -584,9 +589,28 @@ const _fetchRoutes = {
|
||||
'/number': { status: 200, body: '1.2' },
|
||||
'/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) {
|
||||
const route = _fetchRoutes[url] || _fetchRoutes['/test'];
|
||||
return { ok: route.status < 400, status: route.status || 200, url: url || '/test',
|
||||
const scriptRoutes = _fetchScripts[globalThis.__currentHsTestName];
|
||||
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 || '' };
|
||||
}
|
||||
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);
|
||||
else if(opName==='io-fetch'){
|
||||
const url=typeof items[1]==='string'?items[1]:'/test';
|
||||
const fmt=typeof items[2]==='string'?items[2]:'text';
|
||||
const route=_fetchRoutes[url]||_fetchRoutes['/test'];
|
||||
if(fmt==='json'){try{doResume(JSON.parse(route.json||route.body||'{}'));}catch(e){doResume(null);}}
|
||||
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 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||'');
|
||||
const scriptRoutes=_fetchScripts[globalThis.__currentHsTestName];
|
||||
const route=(scriptRoutes&&scriptRoutes[url])||_fetchRoutes[url]||_fetchRoutes['/test'];
|
||||
if(route&&route.networkError){doResume({_type:'dict','_network-error':true,message:'aborted'});}
|
||||
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(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);}}
|
||||
@@ -697,6 +718,7 @@ for(let i=startTest;i<Math.min(endTest,testCount);i++){
|
||||
globalThis.__hsCookieStore.clear();
|
||||
globalThis.__hsMutationRegistry.length = 0;
|
||||
globalThis.__hsMutationActive = false;
|
||||
globalThis._windowListeners={};
|
||||
globalThis.__currentHsTestName = name;
|
||||
|
||||
// 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 handle an or after a from clause",
|
||||
# upstream 'fetch' category — depend on per-test sinon stubs for 404 / thrown errors,
|
||||
# or on real DocumentFragment semantics (`its childElementCount` after `as html`).
|
||||
# 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.
|
||||
# upstream 'fetch' category — real DocumentFragment semantics (`its childElementCount`
|
||||
# after `as html`) not exercisable with our DOM mock.
|
||||
"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:
|
||||
pre_setups.append(('__hs_config__', op_expr))
|
||||
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
|
||||
|
||||
# evaluate(() => _hyperscript.config.X = ...) single-line variant.
|
||||
|
||||
Reference in New Issue
Block a user