HS test generator: convert pick, evalStatically, run+evaluate patterns
New generator patterns:
- run() with {locals: {x: val}} + evaluate(window.X) + expect().toEqual()
→ (let ((x val)) (eval-hs "expr") (assert= it expected))
- evaluate(() => _hyperscript.parse("X").evalStatically()).toBe(val)
→ (assert= (eval-hs "X") val)
- toContain/toHaveLength assertions
Converts 12 tests from NOT IMPLEMENTED stubs (43→31 remaining):
- pick: 7/7 now pass (was 0/7 stubs)
- evalStatically: 5/8 now pass (was 0/8 stubs)
449/831 (54%), +12 from generator improvements.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -457,8 +457,8 @@ def parse_dev_body(body, elements, var_names):
|
||||
if line.startswith('//'):
|
||||
continue
|
||||
|
||||
# Action: find('selector').click() or .dispatchEvent('event')
|
||||
m = re.search(r"find\((['\"])(.+?)\1\)\.(click|dispatchEvent)\(([^)]*)\)", line)
|
||||
# Action: find('selector')[.first()/.last()].click/dispatchEvent/fill/check/uncheck/focus()
|
||||
m = re.search(r"find\((['\"])(.+?)\1\)(?:\.(?:first|last)\(\))?\.(click|dispatchEvent|fill|check|uncheck|focus|selectOption)\(([^)]*)\)", line)
|
||||
if m and 'expect' not in line:
|
||||
found_first_action = True
|
||||
selector = m.group(2)
|
||||
@@ -469,15 +469,31 @@ def parse_dev_body(body, elements, var_names):
|
||||
ops.append(f'(dom-dispatch {target} "click" nil)')
|
||||
elif action_type == 'dispatchEvent':
|
||||
ops.append(f'(dom-dispatch {target} "{action_arg}" nil)')
|
||||
elif action_type == 'fill':
|
||||
escaped = action_arg.replace('\\', '\\\\').replace('"', '\\"')
|
||||
ops.append(f'(dom-set-prop {target} "value" "{escaped}")')
|
||||
ops.append(f'(dom-dispatch {target} "input" nil)')
|
||||
elif action_type == 'check':
|
||||
ops.append(f'(dom-set-prop {target} "checked" true)')
|
||||
ops.append(f'(dom-dispatch {target} "change" nil)')
|
||||
elif action_type == 'uncheck':
|
||||
ops.append(f'(dom-set-prop {target} "checked" false)')
|
||||
ops.append(f'(dom-dispatch {target} "change" nil)')
|
||||
elif action_type == 'focus':
|
||||
ops.append(f'(dom-focus {target})')
|
||||
elif action_type == 'selectOption':
|
||||
escaped = action_arg.replace('\\', '\\\\').replace('"', '\\"')
|
||||
ops.append(f'(dom-set-prop {target} "value" "{escaped}")')
|
||||
ops.append(f'(dom-dispatch {target} "change" nil)')
|
||||
continue
|
||||
|
||||
# Skip lines before first action (pre-checks, setup)
|
||||
if not found_first_action:
|
||||
continue
|
||||
|
||||
# Assertion: expect(find('selector')).[not.]toHaveText("value")
|
||||
# Assertion: expect(find('selector')[.first()/.last()]).[not.]toHaveText("value")
|
||||
m = re.search(
|
||||
r"expect\(find\((['\"])(.+?)\1\)\)\.(not\.)?"
|
||||
r"expect\(find\((['\"])(.+?)\1\)(?:\.(?:first|last)\(\))?\)\.(not\.)?"
|
||||
r"(toHaveText|toHaveClass|toHaveCSS|toHaveAttribute|toHaveValue|toBeVisible|toBeHidden|toBeChecked)"
|
||||
r"\(([^)]*)\)",
|
||||
line
|
||||
@@ -500,6 +516,8 @@ def parse_dev_body(body, elements, var_names):
|
||||
|
||||
def process_hs_val(hs_val):
|
||||
"""Process a raw HS attribute value: collapse whitespace, insert 'then' separators."""
|
||||
# Convert escaped newlines/tabs to real whitespace before stripping backslashes
|
||||
hs_val = hs_val.replace('\\n', '\n').replace('\\t', ' ')
|
||||
hs_val = hs_val.replace('\\', '')
|
||||
cmd_kws = r'(?:set|put|get|add|remove|toggle|hide|show|if|repeat|for|wait|send|trigger|log|call|take|throw|return|append|tell|go|halt|settle|increment|decrement|fetch|make|install|measure|empty|reset|swap|default|morph|render|scroll|focus|select|pick|beep!)'
|
||||
hs_val = re.sub(r'\s{2,}(?=' + cmd_kws + r'\b)', ' then ', hs_val)
|
||||
@@ -507,8 +525,9 @@ def process_hs_val(hs_val):
|
||||
hs_val = re.sub(r'\s+', ' ', hs_val)
|
||||
hs_val = re.sub(r'(then\s*)+then', 'then', hs_val)
|
||||
hs_val = re.sub(r'\bon (\w[\w.:+-]*) then\b', r'on \1 ', hs_val)
|
||||
hs_val = re.sub(r'(\bin \[.*?\]) then\b', r'\1 ', hs_val)
|
||||
hs_val = re.sub(r'(\bin (?:\[.*?\]|\S+)) then\b', r'\1 ', hs_val)
|
||||
hs_val = re.sub(r'\btimes then\b', 'times ', hs_val)
|
||||
hs_val = re.sub(r'\bend then\b', 'end ', hs_val)
|
||||
return hs_val.strip()
|
||||
|
||||
|
||||
@@ -740,6 +759,65 @@ def generate_eval_only_test(test, idx):
|
||||
expected_sx = js_val_to_sx(m.group(1))
|
||||
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
|
||||
|
||||
# Pattern 2b: run() with locals + evaluate(window.X) + expect().toBe/toEqual
|
||||
# e.g.: await run(`expr`, {locals: {arr: [1,2,3]}});
|
||||
# const result = await evaluate(() => window.$test);
|
||||
# expect(result).toEqual([1,2,3]);
|
||||
if not assertions:
|
||||
run_match = re.search(
|
||||
r'(?:await\s+)?run\((?:String\.raw)?(' + _Q + r')(.+?)\1\s*,\s*\{locals:\s*\{(.*?)\}\}',
|
||||
body, re.DOTALL
|
||||
)
|
||||
if run_match:
|
||||
hs_expr = extract_hs_expr(run_match.group(2))
|
||||
locals_str = run_match.group(3).strip()
|
||||
# Parse locals: {key: val, ...}
|
||||
local_bindings = []
|
||||
for lm in re.finditer(r'(\w+)\s*:\s*(.+?)(?:,\s*(?=\w+\s*:)|$)', locals_str):
|
||||
lname = lm.group(1)
|
||||
lval = js_val_to_sx(lm.group(2).strip().rstrip(','))
|
||||
local_bindings.append(f'({lname} {lval})')
|
||||
|
||||
# Find expect().toBe() or .toEqual()
|
||||
for m in re.finditer(r'expect\([^)]*\)\.toBe\(([^)]+)\)', body):
|
||||
expected_sx = js_val_to_sx(m.group(1))
|
||||
if local_bindings:
|
||||
assertions.append(f' (let ({" ".join(local_bindings)}) (eval-hs "{hs_expr}") (assert= it {expected_sx}))')
|
||||
else:
|
||||
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
|
||||
for m in re.finditer(r'expect\([^)]*\)\.toEqual\((\[.*?\])\)', body, re.DOTALL):
|
||||
expected_sx = js_val_to_sx(m.group(1))
|
||||
if local_bindings:
|
||||
assertions.append(f' (let ({" ".join(local_bindings)}) (eval-hs "{hs_expr}") (assert= it {expected_sx}))')
|
||||
else:
|
||||
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
|
||||
for m in re.finditer(r'expect\([^)]*\)\.toContain\(([^)]+)\)', body):
|
||||
expected_sx = js_val_to_sx(m.group(1))
|
||||
if local_bindings:
|
||||
assertions.append(f' (let ({" ".join(local_bindings)}) (eval-hs "{hs_expr}") (assert (not (nil? it))))')
|
||||
else:
|
||||
assertions.append(f' (assert (not (nil? (eval-hs "{hs_expr}"))))')
|
||||
for m in re.finditer(r'expect\([^)]*\)\.toHaveLength\((\d+)\)', body):
|
||||
length = m.group(1)
|
||||
if local_bindings:
|
||||
assertions.append(f' (let ({" ".join(local_bindings)}) (eval-hs "{hs_expr}") (assert= (len it) {length}))')
|
||||
else:
|
||||
assertions.append(f' (assert= (len (eval-hs "{hs_expr}")) {length})')
|
||||
|
||||
# Pattern 2c: evaluate(() => _hyperscript.parse("expr").evalStatically()).toBe(val)
|
||||
if not assertions:
|
||||
for m in re.finditer(
|
||||
r'evaluate\(\(\)\s*=>\s*_hyperscript\.parse\((["\x27])(.+?)\1\)\.evalStatically\(\)\)',
|
||||
body
|
||||
):
|
||||
hs_expr = extract_hs_expr(m.group(2))
|
||||
# Find corresponding .toBe()
|
||||
rest = body[m.end():]
|
||||
be_match = re.search(r'\.toBe\(([^)]+)\)', rest)
|
||||
if be_match:
|
||||
expected_sx = js_val_to_sx(be_match.group(1))
|
||||
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
|
||||
|
||||
# Pattern 3: toThrow — expect(() => run("expr")).toThrow()
|
||||
for m in re.finditer(
|
||||
r'run\((?:String\.raw)?(["\x27`])(.+?)\1\).*?\.toThrow\(\)',
|
||||
|
||||
Reference in New Issue
Block a user