Hyperscript conformance: 341→372 (45%) — parser, compiler, runtime, generator
Parser: increment/decrement "by N", then-less command chaining, scroll/select/ reset/default/halt commands, toggle style/attr/between, repeat for-loop delegation, number fix for repeat N times, take with from/for scope. Compiler: emit-inc/emit-dec with amount + property/style targets, 12 new dispatch entries (scroll, select, reset, default, halt, toggle-style, toggle-style-between, toggle-attr, toggle-attr-between, take rewrite). Runtime: hs-scroll!, hs-halt!, hs-select!, hs-reset!, hs-query-all, hs-toggle-style!, hs-toggle-style-between!, hs-toggle-attr!, hs-toggle-attr-between!, hs-take! rewrite with kind/name/scope. Generator: handle backtick strings, two-line run()/expect() patterns, toEqual with arrays, toThrow — unlocks 34 more eval-only tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -508,37 +508,116 @@ def generate_test_pw(test, elements, var_names, idx):
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def js_val_to_sx(val):
|
||||
"""Convert a JS literal value to SX."""
|
||||
val = val.strip()
|
||||
if val == 'true': return 'true'
|
||||
if val == 'false': return 'false'
|
||||
if val in ('null', 'undefined'): return 'nil'
|
||||
if val.startswith('"') or val.startswith("'"):
|
||||
return '"' + val.strip("\"'") + '"'
|
||||
# Arrays: [1, 2, 3] → (list 1 2 3)
|
||||
if val.startswith('[') and val.endswith(']'):
|
||||
inner = val[1:-1].strip()
|
||||
if not inner:
|
||||
return '(list)'
|
||||
items = [js_val_to_sx(x.strip()) for x in split_top_level(inner)]
|
||||
return '(list ' + ' '.join(items) + ')'
|
||||
try:
|
||||
float(val)
|
||||
return val
|
||||
except ValueError:
|
||||
return f'"{val}"'
|
||||
|
||||
|
||||
def split_top_level(s):
|
||||
"""Split a string by commas, respecting brackets/quotes."""
|
||||
parts = []
|
||||
depth = 0
|
||||
current = []
|
||||
in_str = None
|
||||
for ch in s:
|
||||
if in_str:
|
||||
current.append(ch)
|
||||
if ch == in_str:
|
||||
in_str = None
|
||||
elif ch in ('"', "'"):
|
||||
in_str = ch
|
||||
current.append(ch)
|
||||
elif ch in ('(', '[', '{'):
|
||||
depth += 1
|
||||
current.append(ch)
|
||||
elif ch in (')', ']', '}'):
|
||||
depth -= 1
|
||||
current.append(ch)
|
||||
elif ch == ',' and depth == 0:
|
||||
parts.append(''.join(current))
|
||||
current = []
|
||||
else:
|
||||
current.append(ch)
|
||||
if current:
|
||||
parts.append(''.join(current))
|
||||
return parts
|
||||
|
||||
|
||||
def extract_hs_expr(raw):
|
||||
"""Clean a HS expression extracted from run() call."""
|
||||
# Remove surrounding whitespace and newlines
|
||||
expr = raw.strip().replace('\n', ' ').replace('\t', ' ')
|
||||
# Collapse multiple spaces
|
||||
expr = re.sub(r'\s+', ' ', expr)
|
||||
# Escape quotes for SX string
|
||||
expr = expr.replace('\\', '').replace('"', '\\"')
|
||||
return expr
|
||||
|
||||
|
||||
def generate_eval_only_test(test, idx):
|
||||
"""Generate SX deftest for no-HTML tests using eval-hs.
|
||||
Parses body field for run("expr").toBe(val) / expect(run("expr")).toBe(val) patterns."""
|
||||
Handles patterns:
|
||||
- run("expr").toBe(val)
|
||||
- expect(run("expr")).toBe(val)
|
||||
- var result = await run(`expr`); expect(result).toBe(val)
|
||||
- run("expr").toEqual([...])
|
||||
- run("expr").toThrow()
|
||||
"""
|
||||
body = test.get('body', '')
|
||||
lines = []
|
||||
lines.append(f' (deftest "{test["name"]}"')
|
||||
safe_name = test["name"].replace('"', "'")
|
||||
lines.append(f' (deftest "{safe_name}"')
|
||||
|
||||
# Extract run("expr").toBe(val) or expect(await run("expr")).toBe(val) patterns
|
||||
assertions = []
|
||||
for m in re.finditer(r'(?:expect\()?(?:await\s+)?run\(["\x27]([^"\x27]+)["\x27]\)\)?\.toBe\(([^)]+)\)', body):
|
||||
hs_expr = m.group(1).replace('\\', '').replace('"', '\\"')
|
||||
expected = m.group(2).strip()
|
||||
# Convert JS values to SX
|
||||
if expected == 'true': expected_sx = 'true'
|
||||
elif expected == 'false': expected_sx = 'false'
|
||||
elif expected == 'null' or expected == 'undefined': expected_sx = 'nil'
|
||||
elif expected.startswith('"') or expected.startswith("'"):
|
||||
expected_sx = '"' + expected.strip("\"'") + '"'
|
||||
else:
|
||||
try:
|
||||
float(expected)
|
||||
expected_sx = expected
|
||||
except ValueError:
|
||||
expected_sx = f'"{expected}"'
|
||||
|
||||
# Pattern 1: Inline — run("expr").toBe(val) or expect(run("expr")).toBe(val)
|
||||
for m in re.finditer(
|
||||
r'(?:expect\()?(?:await\s+)?run\((["\x27`])(.+?)\1\)\)?\.toBe\(([^)]+)\)',
|
||||
body, re.DOTALL
|
||||
):
|
||||
hs_expr = extract_hs_expr(m.group(2))
|
||||
expected_sx = js_val_to_sx(m.group(3))
|
||||
assertions.append(f' (assert= {expected_sx} (eval-hs "{hs_expr}"))')
|
||||
|
||||
# Also handle toEqual patterns
|
||||
for m in re.finditer(r'(?:expect\()?(?:await\s+)?run\(["\x27]([^"\x27]+)["\x27]\)\)?\.toEqual\(([^)]+)\)', body):
|
||||
hs_expr = m.group(1).replace('\\', '').replace('"', '\\"')
|
||||
expected = m.group(2).strip()
|
||||
assertions.append(f' ;; toEqual: {expected[:40]}')
|
||||
# Pattern 2: Two-line — var result = await run(`expr`); expect(result).toBe(val)
|
||||
if not assertions:
|
||||
run_match = re.search(
|
||||
r'(?:var|let|const)\s+\w+\s*=\s*(?:await\s+)?run\((["\x27`])(.+?)\1\)',
|
||||
body, re.DOTALL
|
||||
)
|
||||
if run_match:
|
||||
hs_expr = extract_hs_expr(run_match.group(2))
|
||||
for m in re.finditer(r'\.toBe\(([^)]+)\)', body):
|
||||
expected_sx = js_val_to_sx(m.group(1))
|
||||
assertions.append(f' (assert= {expected_sx} (eval-hs "{hs_expr}"))')
|
||||
for m in re.finditer(r'\.toEqual\((\[.*?\])\)', body, re.DOTALL):
|
||||
expected_sx = js_val_to_sx(m.group(1))
|
||||
assertions.append(f' (assert= {expected_sx} (eval-hs "{hs_expr}"))')
|
||||
|
||||
# Pattern 3: toThrow — expect(() => run("expr")).toThrow()
|
||||
for m in re.finditer(
|
||||
r'run\((["\x27`])(.+?)\1\).*?\.toThrow\(\)',
|
||||
body, re.DOTALL
|
||||
):
|
||||
hs_expr = extract_hs_expr(m.group(2))
|
||||
assertions.append(f' (assert-throws (eval-hs "{hs_expr}"))')
|
||||
|
||||
if not assertions:
|
||||
return None # Can't convert this body pattern
|
||||
|
||||
Reference in New Issue
Block a user