Hyperscript test generator: repeat loop fix, assert= arg order, quote handling
- Don't insert 'then' inside for-in loop bodies or after 'repeat N times' (fixes repeat from 1/30 → 5/30) - Allow HS sources ending with " when they don't contain embedded HTML (fixes set from 6/25 → 10/25, enables 18 previously-skipped tests) - Fix assert= argument order: (actual expected), not (expected actual) (error messages now correctly report Expected/Got) 395 → 402/831 (+7) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1553,20 +1553,18 @@
|
|||||||
cl-collect
|
cl-collect
|
||||||
(fn
|
(fn
|
||||||
(acc)
|
(acc)
|
||||||
(do
|
(let
|
||||||
(match-kw "then")
|
((cmd (parse-cmd)))
|
||||||
(let
|
(if
|
||||||
((cmd (parse-cmd)))
|
(nil? cmd)
|
||||||
(if
|
acc
|
||||||
(nil? cmd)
|
(let
|
||||||
acc
|
((acc2 (append acc (list cmd))))
|
||||||
(let
|
(cond
|
||||||
((acc2 (append acc (list cmd))))
|
((match-kw "then") (cl-collect acc2))
|
||||||
(cond
|
((and (not (at-end?)) (= (tp-type) "keyword") (cmd-kw? (tp-val)))
|
||||||
((match-kw "then") (cl-collect acc2))
|
(cl-collect acc2))
|
||||||
((and (not (at-end?)) (= (tp-type) "keyword") (cmd-kw? (tp-val)))
|
(true acc2)))))))
|
||||||
(cl-collect acc2))
|
|
||||||
(true acc2))))))))
|
|
||||||
(let
|
(let
|
||||||
((cmds (cl-collect (list))))
|
((cmds (cl-collect (list))))
|
||||||
(cond
|
(cond
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -305,14 +305,14 @@ def check_to_sx(check, ref):
|
|||||||
return f'(assert (not (dom-has-class? {r} "{key}")))'
|
return f'(assert (not (dom-has-class? {r} "{key}")))'
|
||||||
elif typ == 'innerHTML':
|
elif typ == 'innerHTML':
|
||||||
escaped = key.replace('"', '\\"') if isinstance(key, str) else key
|
escaped = key.replace('"', '\\"') if isinstance(key, str) else key
|
||||||
return f'(assert= "{escaped}" (dom-inner-html {r}))'
|
return f'(assert= (dom-inner-html {r}) "{escaped}")'
|
||||||
elif typ == 'textContent':
|
elif typ == 'textContent':
|
||||||
escaped = key.replace('"', '\\"')
|
escaped = key.replace('"', '\\"')
|
||||||
return f'(assert= "{escaped}" (dom-text-content {r}))'
|
return f'(assert= (dom-text-content {r}) "{escaped}")'
|
||||||
elif typ == 'style':
|
elif typ == 'style':
|
||||||
return f'(assert= "{val}" (dom-get-style {r} "{key}"))'
|
return f'(assert= (dom-get-style {r} "{key}") "{val}")'
|
||||||
elif typ == 'attr':
|
elif typ == 'attr':
|
||||||
return f'(assert= "{val}" (dom-get-attr {r} "{key}"))'
|
return f'(assert= (dom-get-attr {r} "{key}") "{val}")'
|
||||||
elif typ == 'hasAttr' and val:
|
elif typ == 'hasAttr' and val:
|
||||||
return f'(assert (dom-has-attr? {r} "{key}"))'
|
return f'(assert (dom-has-attr? {r} "{key}"))'
|
||||||
elif typ == 'hasAttr' and not val:
|
elif typ == 'hasAttr' and not val:
|
||||||
@@ -324,7 +324,7 @@ def check_to_sx(check, ref):
|
|||||||
elif typ == 'hasParent':
|
elif typ == 'hasParent':
|
||||||
return f'(assert (not (nil? (dom-parent {r}))))'
|
return f'(assert (not (nil? (dom-parent {r}))))'
|
||||||
elif typ == 'value':
|
elif typ == 'value':
|
||||||
return f'(assert= "{key}" (dom-get-prop {r} "value"))'
|
return f'(assert= (dom-get-prop {r} "value") "{key}")'
|
||||||
else:
|
else:
|
||||||
return f';; SKIP check: {typ} {name}'
|
return f';; SKIP check: {typ} {name}'
|
||||||
|
|
||||||
@@ -365,16 +365,16 @@ def pw_assertion_to_sx(target, negated, assert_type, args_str):
|
|||||||
val = args[0] if args else ''
|
val = args[0] if args else ''
|
||||||
escaped = val.replace('\\', '\\\\').replace('"', '\\"')
|
escaped = val.replace('\\', '\\\\').replace('"', '\\"')
|
||||||
if negated:
|
if negated:
|
||||||
return f'(assert (!= "{escaped}" (dom-text-content {target})))'
|
return f'(assert (!= (dom-text-content {target}) "{escaped}"))'
|
||||||
return f'(assert= "{escaped}" (dom-text-content {target}))'
|
return f'(assert= (dom-text-content {target}) "{escaped}")'
|
||||||
|
|
||||||
elif assert_type == 'toHaveAttribute':
|
elif assert_type == 'toHaveAttribute':
|
||||||
attr_name = args[0] if args else ''
|
attr_name = args[0] if args else ''
|
||||||
if len(args) >= 2:
|
if len(args) >= 2:
|
||||||
attr_val = args[1].replace('\\', '\\\\').replace('"', '\\"')
|
attr_val = args[1].replace('\\', '\\\\').replace('"', '\\"')
|
||||||
if negated:
|
if negated:
|
||||||
return f'(assert (!= "{attr_val}" (dom-get-attr {target} "{attr_name}")))'
|
return f'(assert (!= (dom-get-attr {target} "{attr_name}") "{attr_val}"))'
|
||||||
return f'(assert= "{attr_val}" (dom-get-attr {target} "{attr_name}"))'
|
return f'(assert= (dom-get-attr {target} "{attr_name}") "{attr_val}")'
|
||||||
else:
|
else:
|
||||||
if negated:
|
if negated:
|
||||||
return f'(assert (not (dom-has-attr? {target} "{attr_name}")))'
|
return f'(assert (not (dom-has-attr? {target} "{attr_name}")))'
|
||||||
@@ -396,15 +396,15 @@ def pw_assertion_to_sx(target, negated, assert_type, args_str):
|
|||||||
val = args[1] if len(args) >= 2 else ''
|
val = args[1] if len(args) >= 2 else ''
|
||||||
escaped = val.replace('\\', '\\\\').replace('"', '\\"')
|
escaped = val.replace('\\', '\\\\').replace('"', '\\"')
|
||||||
if negated:
|
if negated:
|
||||||
return f'(assert (!= "{escaped}" (dom-get-style {target} "{prop}")))'
|
return f'(assert (!= (dom-get-style {target} "{prop}") "{escaped}"))'
|
||||||
return f'(assert= "{escaped}" (dom-get-style {target} "{prop}"))'
|
return f'(assert= (dom-get-style {target} "{prop}") "{escaped}")'
|
||||||
|
|
||||||
elif assert_type == 'toHaveValue':
|
elif assert_type == 'toHaveValue':
|
||||||
val = args[0] if args else ''
|
val = args[0] if args else ''
|
||||||
escaped = val.replace('\\', '\\\\').replace('"', '\\"')
|
escaped = val.replace('\\', '\\\\').replace('"', '\\"')
|
||||||
if negated:
|
if negated:
|
||||||
return f'(assert (!= "{escaped}" (dom-get-prop {target} "value")))'
|
return f'(assert (!= (dom-get-prop {target} "value") "{escaped}"))'
|
||||||
return f'(assert= "{escaped}" (dom-get-prop {target} "value"))'
|
return f'(assert= (dom-get-prop {target} "value") "{escaped}")'
|
||||||
|
|
||||||
elif assert_type == 'toBeVisible':
|
elif assert_type == 'toBeVisible':
|
||||||
if negated:
|
if negated:
|
||||||
@@ -505,12 +505,16 @@ def emit_element_setup(lines, elements, var_names):
|
|||||||
hs_val = re.sub(r'(then\s*)+then', 'then', hs_val)
|
hs_val = re.sub(r'(then\s*)+then', 'then', hs_val)
|
||||||
# Don't insert 'then' between event name and first command in 'on' handlers
|
# Don't insert 'then' between event name and first command in 'on' handlers
|
||||||
hs_val = re.sub(r'\bon (\w[\w.:+-]*) then\b', r'on \1 ', hs_val)
|
hs_val = re.sub(r'\bon (\w[\w.:+-]*) then\b', r'on \1 ', hs_val)
|
||||||
|
# Don't insert 'then' inside for-in loop bodies (between collection and body)
|
||||||
|
hs_val = re.sub(r'(\bin \[.*?\]) then\b', r'\1 ', hs_val)
|
||||||
|
# Don't insert 'then' after 'times' in repeat N times loops
|
||||||
|
hs_val = re.sub(r'\btimes then\b', 'times ', hs_val)
|
||||||
hs_val = hs_val.strip()
|
hs_val = hs_val.strip()
|
||||||
if not hs_val:
|
if not hs_val:
|
||||||
lines.append(f' (dom-append (dom-body) {var})')
|
lines.append(f' (dom-append (dom-body) {var})')
|
||||||
continue
|
continue
|
||||||
if hs_val.startswith('"') or hs_val.endswith('"'):
|
if hs_val.startswith('"') or (hs_val.endswith('"') and '<' in hs_val):
|
||||||
lines.append(f' ;; HS source has bare quotes — HTML parse artifact')
|
lines.append(f' ;; HS source has bare quotes or embedded HTML')
|
||||||
lines.append(f' (dom-append (dom-body) {var})')
|
lines.append(f' (dom-append (dom-body) {var})')
|
||||||
continue
|
continue
|
||||||
hs_escaped = hs_val.replace('\\', '\\\\').replace('"', '\\"')
|
hs_escaped = hs_val.replace('\\', '\\\\').replace('"', '\\"')
|
||||||
@@ -657,7 +661,7 @@ def generate_eval_only_test(test, idx):
|
|||||||
):
|
):
|
||||||
hs_expr = extract_hs_expr(m.group(2))
|
hs_expr = extract_hs_expr(m.group(2))
|
||||||
expected_sx = js_val_to_sx(m.group(3))
|
expected_sx = js_val_to_sx(m.group(3))
|
||||||
assertions.append(f' (assert= {expected_sx} (eval-hs "{hs_expr}"))')
|
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
|
||||||
|
|
||||||
# Pattern 1b: Inline — run("expr").toEqual([...])
|
# Pattern 1b: Inline — run("expr").toEqual([...])
|
||||||
for m in re.finditer(
|
for m in re.finditer(
|
||||||
@@ -666,7 +670,7 @@ def generate_eval_only_test(test, idx):
|
|||||||
):
|
):
|
||||||
hs_expr = extract_hs_expr(m.group(2))
|
hs_expr = extract_hs_expr(m.group(2))
|
||||||
expected_sx = js_val_to_sx(m.group(3))
|
expected_sx = js_val_to_sx(m.group(3))
|
||||||
assertions.append(f' (assert= {expected_sx} (eval-hs "{hs_expr}"))')
|
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
|
||||||
|
|
||||||
# Pattern 2: Two-line — var result = await run(`expr`); expect(result).toBe(val)
|
# Pattern 2: Two-line — var result = await run(`expr`); expect(result).toBe(val)
|
||||||
if not assertions:
|
if not assertions:
|
||||||
@@ -678,10 +682,10 @@ def generate_eval_only_test(test, idx):
|
|||||||
hs_expr = extract_hs_expr(run_match.group(2))
|
hs_expr = extract_hs_expr(run_match.group(2))
|
||||||
for m in re.finditer(r'\.toBe\(([^)]+)\)', body):
|
for m in re.finditer(r'\.toBe\(([^)]+)\)', body):
|
||||||
expected_sx = js_val_to_sx(m.group(1))
|
expected_sx = js_val_to_sx(m.group(1))
|
||||||
assertions.append(f' (assert= {expected_sx} (eval-hs "{hs_expr}"))')
|
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
|
||||||
for m in re.finditer(r'\.toEqual\((\[.*?\])\)', body, re.DOTALL):
|
for m in re.finditer(r'\.toEqual\((\[.*?\])\)', body, re.DOTALL):
|
||||||
expected_sx = js_val_to_sx(m.group(1))
|
expected_sx = js_val_to_sx(m.group(1))
|
||||||
assertions.append(f' (assert= {expected_sx} (eval-hs "{hs_expr}"))')
|
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
|
||||||
|
|
||||||
# Pattern 3: toThrow — expect(() => run("expr")).toThrow()
|
# Pattern 3: toThrow — expect(() => run("expr")).toThrow()
|
||||||
for m in re.finditer(
|
for m in re.finditer(
|
||||||
|
|||||||
Reference in New Issue
Block a user