HS: wait on event basics (+4 tests)
Five parts: (a) tests/hs-run-filtered.js `io-wait-event` mock now registers a one-shot listener on the target element and resumes with the event, instead of immediately resuming with nil. (b) Added hs-wait-for-or runtime form carrying a timeout-ms; mock resumes immediately when a timeout is present (0ms tests). (c) parser parse-wait-cmd recognises `wait for EV(v1, v2)` destructure syntax, emits :destructure list on wait-for AST. (d) compiler emit-wait-for updated for :from/:or combos; a new `__bind-from-detail__` form compiles to `(define v (host-get (host-get it "detail") v))`, and the `do`-sequence handler preprocesses wait-for to splice these synthetic bindings after the wait. (e) generator extracts `detail: ...` from `CustomEvent` options so dispatched events carry their payload. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -341,7 +341,23 @@ globalThis._driveAsync=function driveAsync(r,d){d=d||0;if(d>500||!r||!r.suspende
|
||||
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-html'){const frag=new El('fragment');frag.nodeType=11;doResume(frag);}
|
||||
else if(opName==='io-settle')doResume(null);
|
||||
else if(opName==='io-wait-event')doResume(null);
|
||||
else if(opName==='io-wait-event'){
|
||||
const target=items&&items[1];
|
||||
const evName=typeof items[2]==='string'?items[2]:'';
|
||||
const timeout=items&&items.length>3?items[3]:undefined;
|
||||
if(typeof timeout==='number'){
|
||||
// `wait for EV or Nms` — timeout wins immediately in the mock (tests use 0ms)
|
||||
doResume(null);
|
||||
} else if(target && target instanceof El && evName){
|
||||
const handler=function(ev){
|
||||
target.removeEventListener(evName,handler);
|
||||
doResume(ev);
|
||||
};
|
||||
target.addEventListener(evName,handler);
|
||||
} else {
|
||||
doResume(null);
|
||||
}
|
||||
}
|
||||
else if(opName==='io-transition')doResume(null);
|
||||
};
|
||||
|
||||
|
||||
@@ -783,6 +783,25 @@ def _window_setup_ops(assign_body):
|
||||
return out
|
||||
|
||||
|
||||
def _extract_detail_expr(opts_src):
|
||||
"""Extract `detail: ...` from an event options block like `, { detail: X }`.
|
||||
Returns an SX expression string, defaulting to `nil`."""
|
||||
if not opts_src:
|
||||
return 'nil'
|
||||
# Plain string detail
|
||||
dm = re.search(r'detail:\s*"([^"]*)"', opts_src)
|
||||
if dm:
|
||||
return f'"{dm.group(1)}"'
|
||||
# Simple object detail: { k: "v", k2: "v2", ... } (string values only)
|
||||
dm = re.search(r'detail:\s*\{([^{}]*)\}', opts_src)
|
||||
if dm:
|
||||
pairs = re.findall(r'(\w+):\s*"([^"]*)"', dm.group(1))
|
||||
if pairs:
|
||||
items = ' '.join(f':{k} "{v}"' for k, v in pairs)
|
||||
return '{' + items + '}'
|
||||
return 'nil'
|
||||
|
||||
|
||||
def parse_dev_body(body, elements, var_names):
|
||||
"""Parse Playwright test body into ordered SX ops.
|
||||
|
||||
@@ -950,13 +969,15 @@ def parse_dev_body(body, elements, var_names):
|
||||
m = re.match(
|
||||
r"evaluate\(\s*\(\)\s*=>\s*document\.querySelector\(\s*(['\"])([^'\"]+)\1\s*\)"
|
||||
r"\.dispatchEvent\(\s*new\s+(?:Custom)?Event\(\s*(['\"])([^'\"]+)\3"
|
||||
r"(?:\s*,\s*[^)]*)?\s*\)\s*\)\s*\)\s*$",
|
||||
r"(\s*,\s*\{.*\})?\s*\)\s*\)\s*\)\s*$",
|
||||
stmt_na, re.DOTALL,
|
||||
)
|
||||
if m and seen_html:
|
||||
sel = re.sub(r'^#work-area\s+', '', m.group(2))
|
||||
target = selector_to_sx(sel, elements, var_names)
|
||||
ops.append(f'(dom-dispatch {target} "{m.group(4)}" nil)')
|
||||
opts = m.group(5) or ''
|
||||
detail_expr = _extract_detail_expr(opts)
|
||||
ops.append(f'(dom-dispatch {target} "{m.group(4)}" {detail_expr})')
|
||||
continue
|
||||
|
||||
# evaluate(() => { const e = new Event(NAME, {...}); document.querySelector(SEL).dispatchEvent(e); })
|
||||
@@ -964,15 +985,17 @@ def parse_dev_body(body, elements, var_names):
|
||||
m = re.match(
|
||||
r"evaluate\(\s*\(\)\s*=>\s*\{\s*"
|
||||
r"const\s+(\w+)\s*=\s*new\s+(?:Custom)?Event\(\s*(['\"])([^'\"]+)\2"
|
||||
r"(?:\s*,\s*\{[^}]*\})?\s*\)\s*;\s*"
|
||||
r"document\.querySelector\(\s*(['\"])([^'\"]+)\4\s*\)"
|
||||
r"(\s*,\s*\{[^}]*\})?\s*\)\s*;\s*"
|
||||
r"document\.querySelector\(\s*(['\"])([^'\"]+)\5\s*\)"
|
||||
r"\.dispatchEvent\(\s*\1\s*\)\s*;?\s*\}\s*\)\s*$",
|
||||
stmt_na, re.DOTALL,
|
||||
)
|
||||
if m and seen_html:
|
||||
sel = re.sub(r'^#work-area\s+', '', m.group(5))
|
||||
sel = re.sub(r'^#work-area\s+', '', m.group(6))
|
||||
target = selector_to_sx(sel, elements, var_names)
|
||||
ops.append(f'(dom-dispatch {target} "{m.group(3)}" nil)')
|
||||
opts = m.group(4) or ''
|
||||
detail_expr = _extract_detail_expr(opts)
|
||||
ops.append(f'(dom-dispatch {target} "{m.group(3)}" {detail_expr})')
|
||||
continue
|
||||
|
||||
# evaluate(() => document.getElementById(ID).METHOD()) — generic
|
||||
|
||||
Reference in New Issue
Block a user