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
|
||||
(fn
|
||||
(acc)
|
||||
(do
|
||||
(match-kw "then")
|
||||
(let
|
||||
((cmd (parse-cmd)))
|
||||
(if
|
||||
(nil? cmd)
|
||||
acc
|
||||
(let
|
||||
((acc2 (append acc (list cmd))))
|
||||
(cond
|
||||
((match-kw "then") (cl-collect acc2))
|
||||
((and (not (at-end?)) (= (tp-type) "keyword") (cmd-kw? (tp-val)))
|
||||
(cl-collect acc2))
|
||||
(true acc2))))))))
|
||||
(let
|
||||
((cmd (parse-cmd)))
|
||||
(if
|
||||
(nil? cmd)
|
||||
acc
|
||||
(let
|
||||
((acc2 (append acc (list cmd))))
|
||||
(cond
|
||||
((match-kw "then") (cl-collect acc2))
|
||||
((and (not (at-end?)) (= (tp-type) "keyword") (cmd-kw? (tp-val)))
|
||||
(cl-collect acc2))
|
||||
(true acc2)))))))
|
||||
(let
|
||||
((cmds (cl-collect (list))))
|
||||
(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}")))'
|
||||
elif typ == 'innerHTML':
|
||||
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':
|
||||
escaped = key.replace('"', '\\"')
|
||||
return f'(assert= "{escaped}" (dom-text-content {r}))'
|
||||
return f'(assert= (dom-text-content {r}) "{escaped}")'
|
||||
elif typ == 'style':
|
||||
return f'(assert= "{val}" (dom-get-style {r} "{key}"))'
|
||||
return f'(assert= (dom-get-style {r} "{key}") "{val}")'
|
||||
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:
|
||||
return f'(assert (dom-has-attr? {r} "{key}"))'
|
||||
elif typ == 'hasAttr' and not val:
|
||||
@@ -324,7 +324,7 @@ def check_to_sx(check, ref):
|
||||
elif typ == 'hasParent':
|
||||
return f'(assert (not (nil? (dom-parent {r}))))'
|
||||
elif typ == 'value':
|
||||
return f'(assert= "{key}" (dom-get-prop {r} "value"))'
|
||||
return f'(assert= (dom-get-prop {r} "value") "{key}")'
|
||||
else:
|
||||
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 ''
|
||||
escaped = val.replace('\\', '\\\\').replace('"', '\\"')
|
||||
if negated:
|
||||
return f'(assert (!= "{escaped}" (dom-text-content {target})))'
|
||||
return f'(assert= "{escaped}" (dom-text-content {target}))'
|
||||
return f'(assert (!= (dom-text-content {target}) "{escaped}"))'
|
||||
return f'(assert= (dom-text-content {target}) "{escaped}")'
|
||||
|
||||
elif assert_type == 'toHaveAttribute':
|
||||
attr_name = args[0] if args else ''
|
||||
if len(args) >= 2:
|
||||
attr_val = args[1].replace('\\', '\\\\').replace('"', '\\"')
|
||||
if negated:
|
||||
return f'(assert (!= "{attr_val}" (dom-get-attr {target} "{attr_name}")))'
|
||||
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= (dom-get-attr {target} "{attr_name}") "{attr_val}")'
|
||||
else:
|
||||
if negated:
|
||||
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 ''
|
||||
escaped = val.replace('\\', '\\\\').replace('"', '\\"')
|
||||
if negated:
|
||||
return f'(assert (!= "{escaped}" (dom-get-style {target} "{prop}")))'
|
||||
return f'(assert= "{escaped}" (dom-get-style {target} "{prop}"))'
|
||||
return f'(assert (!= (dom-get-style {target} "{prop}") "{escaped}"))'
|
||||
return f'(assert= (dom-get-style {target} "{prop}") "{escaped}")'
|
||||
|
||||
elif assert_type == 'toHaveValue':
|
||||
val = args[0] if args else ''
|
||||
escaped = val.replace('\\', '\\\\').replace('"', '\\"')
|
||||
if negated:
|
||||
return f'(assert (!= "{escaped}" (dom-get-prop {target} "value")))'
|
||||
return f'(assert= "{escaped}" (dom-get-prop {target} "value"))'
|
||||
return f'(assert (!= (dom-get-prop {target} "value") "{escaped}"))'
|
||||
return f'(assert= (dom-get-prop {target} "value") "{escaped}")'
|
||||
|
||||
elif assert_type == 'toBeVisible':
|
||||
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)
|
||||
# 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)
|
||||
# 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()
|
||||
if not hs_val:
|
||||
lines.append(f' (dom-append (dom-body) {var})')
|
||||
continue
|
||||
if hs_val.startswith('"') or hs_val.endswith('"'):
|
||||
lines.append(f' ;; HS source has bare quotes — HTML parse artifact')
|
||||
if hs_val.startswith('"') or (hs_val.endswith('"') and '<' in hs_val):
|
||||
lines.append(f' ;; HS source has bare quotes or embedded HTML')
|
||||
lines.append(f' (dom-append (dom-body) {var})')
|
||||
continue
|
||||
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))
|
||||
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([...])
|
||||
for m in re.finditer(
|
||||
@@ -666,7 +670,7 @@ def generate_eval_only_test(test, idx):
|
||||
):
|
||||
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}"))')
|
||||
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
|
||||
|
||||
# Pattern 2: Two-line — var result = await run(`expr`); expect(result).toBe(val)
|
||||
if not assertions:
|
||||
@@ -678,10 +682,10 @@ def generate_eval_only_test(test, idx):
|
||||
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}"))')
|
||||
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
|
||||
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}"))')
|
||||
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
|
||||
|
||||
# Pattern 3: toThrow — expect(() => run("expr")).toThrow()
|
||||
for m in re.finditer(
|
||||
|
||||
Reference in New Issue
Block a user