HS test generator: window/document binding + JS function-expr setups
Three related changes for the `evaluate(() => window.X = Y)` setup pattern:
1. extract_window_setups now also matches the single-expression form
`evaluate(() => window.X = Y)` (no braces), in addition to the
block form `evaluate(() => { window.X = Y; ... })`.
2. js_expr_to_sx now recognises `function(args) { return X; }` (and
`function(args) { X; }`) in addition to arrow functions, so e.g.
`window.select2 = function(){ return "select2"; }` translates to
`(fn () "select2")`.
3. generate_test_chai / generate_test_pw (HTML+click test generators)
inject `(host-set! (host-global "window") "X" <sx>)` for each window
setup found in the test body, so HS code that reads `window.X` sees
the right value at activation time.
4. Test-helper preamble now defines `window` and `document` as
`(host-global "window")` / `(host-global "document")`, so HS
expressions like `window.tmp` resolve through the host instead of
erroring on an unbound `window` symbol.
Net effect on suites smoke-tested: nominal, because most affected tests
hit a separate `if/then/else` parser bug — the `then` keyword inserter
in process_hs_val turns multi-line if blocks into ones the HS parser
collapses to "always run the body". Fixing that is the next iteration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -910,6 +910,11 @@ def generate_test_chai(test, elements, var_names, idx):
|
||||
lines.append(f' (deftest "{sx_name(test["name"])}"')
|
||||
lines.append(' (hs-cleanup!)')
|
||||
|
||||
# `evaluate(() => window.X = Y)` setups in the test body — inject as
|
||||
# globals before activation so HS code can read them.
|
||||
for name, sx_val in extract_window_setups(test.get('body', '') or ''):
|
||||
lines.append(f' (host-set! (host-global "window") "{name}" {sx_val})')
|
||||
|
||||
# Compile HS script blocks as setup (def functions etc.)
|
||||
for script in hs_scripts:
|
||||
clean = clean_hs_script(script)
|
||||
@@ -942,6 +947,10 @@ def generate_test_pw(test, elements, var_names, idx):
|
||||
lines.append(f' (deftest "{sx_name(test["name"])}"')
|
||||
lines.append(' (hs-cleanup!)')
|
||||
|
||||
# `evaluate(() => window.X = Y)` setups — see generate_test_chai.
|
||||
for name, sx_val in extract_window_setups(test.get('body', '') or ''):
|
||||
lines.append(f' (host-set! (host-global "window") "{name}" {sx_val})')
|
||||
|
||||
bindings = [f'({var_names[i]} (dom-create-element "{el["tag"]}"))' for i, el in enumerate(elements)]
|
||||
lines.append(f' (let ({" ".join(bindings)})')
|
||||
|
||||
@@ -1062,6 +1071,19 @@ def js_expr_to_sx(expr):
|
||||
return None
|
||||
return f'(fn ({" ".join(params)}) {body_sx})'
|
||||
|
||||
# function-expression form: `function(args) { return X; }` (or `{ X; }`).
|
||||
fm = re.match(
|
||||
r'^function\s*\(([^)]*)\)\s*\{\s*(?:return\s+)?(.+?)\s*;?\s*\}\s*$',
|
||||
expr, re.DOTALL,
|
||||
)
|
||||
if fm:
|
||||
args_str = fm.group(1).strip()
|
||||
params = [a.strip() for a in args_str.split(',') if a.strip()] if args_str else []
|
||||
body_sx = js_expr_to_sx(fm.group(2).strip())
|
||||
if body_sx is None:
|
||||
return None
|
||||
return f'(fn ({" ".join(params)}) {body_sx})'
|
||||
|
||||
# Balanced outer parens unwrap (after arrow check, so `(x)` alone works).
|
||||
if expr.startswith('(') and expr.endswith(')'):
|
||||
depth = 0
|
||||
@@ -1153,15 +1175,16 @@ def js_expr_to_sx(expr):
|
||||
|
||||
|
||||
def extract_window_setups(body):
|
||||
"""Find `evaluate(() => { window.NAME = VALUE; ... })` blocks and return
|
||||
a list of (name, sx_value) pairs. Skips assignments we can't translate.
|
||||
"""Find `evaluate(() => { window.NAME = VALUE; ... })` (block form) and
|
||||
`evaluate(() => window.NAME = VALUE)` (single-expression form) and
|
||||
return a list of (name, sx_value) pairs. Skips assignments we can't
|
||||
translate.
|
||||
"""
|
||||
setups = []
|
||||
# Each evaluate body may contain multiple `window.X = Y` (semicolon-separated).
|
||||
# Match the inner braces of evaluate(() => { ... }), with balanced braces.
|
||||
|
||||
# Block form: evaluate(() => { window.X = Y; ... })
|
||||
for em in re.finditer(r'evaluate\(\s*\(\)\s*=>\s*\{', body):
|
||||
start = em.end()
|
||||
# Find matching close brace.
|
||||
depth, i, in_str = 1, start, None
|
||||
while i < len(body) and depth > 0:
|
||||
ch = body[i]
|
||||
@@ -1177,16 +1200,23 @@ def extract_window_setups(body):
|
||||
i += 1
|
||||
if depth != 0:
|
||||
continue
|
||||
inner = body[start:i - 1]
|
||||
# Split on top-level semicolons.
|
||||
for stmt in split_top_level_chars(inner, ';'):
|
||||
for stmt in split_top_level_chars(body[start:i - 1], ';'):
|
||||
sm = re.match(r'\s*window\.(\w+)\s*=\s*(.+?)\s*$', stmt, re.DOTALL)
|
||||
if not sm:
|
||||
continue
|
||||
name = sm.group(1)
|
||||
sx_val = js_expr_to_sx(sm.group(2).strip())
|
||||
if sx_val is not None:
|
||||
setups.append((name, sx_val))
|
||||
setups.append((sm.group(1), sx_val))
|
||||
|
||||
# Single-expression form: evaluate(() => window.X = Y) — no braces.
|
||||
for em in re.finditer(
|
||||
r'evaluate\(\s*\(\)\s*=>\s*window\.(\w+)\s*=\s*([^)]+?)\)',
|
||||
body, re.DOTALL,
|
||||
):
|
||||
sx_val = js_expr_to_sx(em.group(2).strip())
|
||||
if sx_val is not None:
|
||||
setups.append((em.group(1), sx_val))
|
||||
|
||||
return setups
|
||||
|
||||
|
||||
@@ -1835,6 +1865,11 @@ output.append(';; DO NOT EDIT — regenerate with: python3 tests/playwright/gene
|
||||
output.append('')
|
||||
output.append(';; ── Test helpers ──────────────────────────────────────────────────')
|
||||
output.append('')
|
||||
output.append(';; Bind `window` and `document` as plain SX symbols so HS code that')
|
||||
output.append(';; references them (e.g. `window.tmp`) can resolve through the host.')
|
||||
output.append('(define window (host-global "window"))')
|
||||
output.append('(define document (host-global "document"))')
|
||||
output.append('')
|
||||
output.append('(define hs-test-el')
|
||||
output.append(' (fn (tag hs-src)')
|
||||
output.append(' (let ((el (dom-create-element tag)))')
|
||||
|
||||
Reference in New Issue
Block a user