Generator: context-aware variable refs — 444/831 (53%, +20)
Fixed ref() to map upstream JS variable names to let-bound SX variables using element context (tag→var, id→var, make-return→last-var). Fixes if (0→14/19), put (14→18), on (20→23), and other categories where the upstream test uses make() return variables like d1, div, btn. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -56,57 +56,42 @@ def parse_html(html):
|
|||||||
Parser().feed(html)
|
Parser().feed(html)
|
||||||
return elements
|
return elements
|
||||||
|
|
||||||
def parse_action(action):
|
def parse_action(action, ref):
|
||||||
"""Convert upstream action to SX. Returns list of SX expressions."""
|
"""Convert upstream action to SX. Returns list of SX expressions."""
|
||||||
if not action or action == '(see body)':
|
if not action or action == '(see body)':
|
||||||
return []
|
return []
|
||||||
|
|
||||||
exprs = []
|
exprs = []
|
||||||
# Split on ';' for multi-step actions
|
|
||||||
for part in action.split(';'):
|
for part in action.split(';'):
|
||||||
part = part.strip()
|
part = part.strip()
|
||||||
if not part:
|
if not part:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Pattern: var.click()
|
|
||||||
m = re.match(r'(\w+)\.click\(\)', part)
|
m = re.match(r'(\w+)\.click\(\)', part)
|
||||||
if m:
|
if m:
|
||||||
name = m.group(1)
|
exprs.append(f'(dom-dispatch {ref(m.group(1))} "click" nil)')
|
||||||
exprs.append(f'(dom-dispatch {ref(name)} "click" nil)')
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Pattern: var.dispatchEvent(new CustomEvent("name"))
|
m = re.match(r'(\w+)\.dispatchEvent\(new CustomEvent\("([\w:.-]+)"', part)
|
||||||
m = re.match(r'(\w+)\.dispatchEvent\(new CustomEvent\("(\w+)"\)\)', part)
|
|
||||||
if m:
|
if m:
|
||||||
exprs.append(f'(dom-dispatch {ref(m.group(1))} "{m.group(2)}" nil)')
|
exprs.append(f'(dom-dispatch {ref(m.group(1))} "{m.group(2)}" nil)')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Pattern: var.dispatchEvent(new CustomEvent("name", {detail: {...}}))
|
m = re.match(r'(\w+)\.setAttribute\("([\w-]+)",\s*"([^"]*)"\)', part)
|
||||||
m = re.match(r'(\w+)\.dispatchEvent\(new CustomEvent\("(\w+)"', part)
|
|
||||||
if m:
|
|
||||||
exprs.append(f'(dom-dispatch {ref(m.group(1))} "{m.group(2)}" nil)')
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Pattern: var.setAttribute("name", "value")
|
|
||||||
m = re.match(r'(\w+)\.setAttribute\("(\w+)",\s*"([^"]*)"\)', part)
|
|
||||||
if m:
|
if m:
|
||||||
exprs.append(f'(dom-set-attr {ref(m.group(1))} "{m.group(2)}" "{m.group(3)}")')
|
exprs.append(f'(dom-set-attr {ref(m.group(1))} "{m.group(2)}" "{m.group(3)}")')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Pattern: var.focus()
|
|
||||||
m = re.match(r'(\w+)\.focus\(\)', part)
|
m = re.match(r'(\w+)\.focus\(\)', part)
|
||||||
if m:
|
if m:
|
||||||
exprs.append(f'(dom-focus {ref(m.group(1))})')
|
exprs.append(f'(dom-focus {ref(m.group(1))})')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Pattern: var.appendChild(document.createElement("TAG"))
|
|
||||||
m = re.match(r'(\w+)\.appendChild\(document\.createElement\("(\w+)"\)', part)
|
m = re.match(r'(\w+)\.appendChild\(document\.createElement\("(\w+)"\)', part)
|
||||||
if m:
|
if m:
|
||||||
exprs.append(f'(dom-append {ref(m.group(1))} (dom-create-element "{m.group(2)}"))')
|
exprs.append(f'(dom-append {ref(m.group(1))} (dom-create-element "{m.group(2)}"))')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Skip unrecognized
|
|
||||||
# Sanitize comment — remove all chars that SX parser treats specially
|
|
||||||
safe = re.sub(r'[\'\"$@`(),;\\#\[\]{}]', '_', part[:40])
|
safe = re.sub(r'[\'\"$@`(),;\\#\[\]{}]', '_', part[:40])
|
||||||
exprs.append(f';; SKIP action: {safe}')
|
exprs.append(f';; SKIP action: {safe}')
|
||||||
|
|
||||||
@@ -203,17 +188,42 @@ def parse_checks(check):
|
|||||||
|
|
||||||
return list(seen.values())
|
return list(seen.values())
|
||||||
|
|
||||||
def ref(name):
|
def make_ref_fn(elements, var_names):
|
||||||
"""Convert a JS variable name to SX element reference.
|
"""Create a ref function that maps upstream JS variable names to SX let-bound variables.
|
||||||
For IDs we use dom-query-by-id at runtime (safer than variable refs).
|
|
||||||
For tags we use the let-bound variable."""
|
|
||||||
tags = {'div', 'form', 'button', 'input', 'span', 'p', 'a', 'section', 'ul', 'li'}
|
|
||||||
if name in tags:
|
|
||||||
return f'_el-{name}'
|
|
||||||
# ID references — use dom-query-by-id for reliability
|
|
||||||
return f'(dom-query-by-id "{name}")'
|
|
||||||
|
|
||||||
def check_to_sx(check):
|
Upstream patterns:
|
||||||
|
- 'div', 'form' etc. → last element with that tag (the make() return value)
|
||||||
|
- 'd1', 'bar', 'p1' etc. → element with that ID, or last element if no ID match
|
||||||
|
"""
|
||||||
|
# Build mappings
|
||||||
|
tag_to_var = {} # tag -> last var with that tag
|
||||||
|
id_to_var = {} # id -> var
|
||||||
|
last_var = var_names[-1] if var_names else '_el-div'
|
||||||
|
|
||||||
|
for i, el in enumerate(elements):
|
||||||
|
tag_to_var[el['tag']] = var_names[i]
|
||||||
|
if el['id']:
|
||||||
|
id_to_var[el['id']] = var_names[i]
|
||||||
|
|
||||||
|
tags = {'div', 'form', 'button', 'input', 'span', 'p', 'a', 'section',
|
||||||
|
'ul', 'li', 'select', 'textarea', 'details', 'dialog', 'template'}
|
||||||
|
|
||||||
|
def ref(name):
|
||||||
|
if name in tags:
|
||||||
|
return tag_to_var.get(name, last_var)
|
||||||
|
if name in id_to_var:
|
||||||
|
return id_to_var[name]
|
||||||
|
# make() return variable pattern: d1, d2, div1, btn1, etc.
|
||||||
|
# These refer to the last element created by make()
|
||||||
|
if re.match(r'^[a-z]+\d*$', name) and len(elements) > 0:
|
||||||
|
# If there's an element with this as id, use dom-query-by-id
|
||||||
|
# Otherwise it's the make() return var — use last element
|
||||||
|
return last_var
|
||||||
|
return f'(dom-query-by-id "{name}")'
|
||||||
|
|
||||||
|
return ref
|
||||||
|
|
||||||
|
def check_to_sx(check, ref):
|
||||||
"""Convert a parsed check tuple to an SX assertion."""
|
"""Convert a parsed check tuple to an SX assertion."""
|
||||||
typ, name, key, val = check
|
typ, name, key, val = check
|
||||||
r = ref(name)
|
r = ref(name)
|
||||||
@@ -250,8 +260,6 @@ def check_to_sx(check):
|
|||||||
def generate_test(test, idx):
|
def generate_test(test, idx):
|
||||||
"""Generate SX deftest for an upstream test."""
|
"""Generate SX deftest for an upstream test."""
|
||||||
elements = parse_html(test['html'])
|
elements = parse_html(test['html'])
|
||||||
actions = parse_action(test['action'])
|
|
||||||
checks = parse_checks(test['check'])
|
|
||||||
|
|
||||||
if not elements and not test.get('html', '').strip():
|
if not elements and not test.get('html', '').strip():
|
||||||
# eval-only test — no HTML at all
|
# eval-only test — no HTML at all
|
||||||
@@ -277,6 +285,13 @@ def generate_test(test, idx):
|
|||||||
used_names.add(var)
|
used_names.add(var)
|
||||||
var_names.append(var)
|
var_names.append(var)
|
||||||
|
|
||||||
|
# Create ref function with element context
|
||||||
|
ref = make_ref_fn(elements, var_names)
|
||||||
|
|
||||||
|
# Parse actions and checks with context-aware ref
|
||||||
|
actions = parse_action(test['action'], ref)
|
||||||
|
checks = parse_checks(test['check'])
|
||||||
|
|
||||||
# Create elements
|
# Create elements
|
||||||
bindings = []
|
bindings = []
|
||||||
for i, el in enumerate(elements):
|
for i, el in enumerate(elements):
|
||||||
@@ -305,7 +320,6 @@ def generate_test(test, idx):
|
|||||||
# Escape for SX double-quoted string
|
# Escape for SX double-quoted string
|
||||||
hs_escaped = hs_val.replace('\\', '\\\\').replace('"', '\\"')
|
hs_escaped = hs_val.replace('\\', '\\\\').replace('"', '\\"')
|
||||||
lines.append(f' (dom-set-attr {var} "_" "{hs_escaped}")')
|
lines.append(f' (dom-set-attr {var} "_" "{hs_escaped}")')
|
||||||
all_hs_sources.add(hs_escaped)
|
|
||||||
for aname, aval in el['attrs'].items():
|
for aname, aval in el['attrs'].items():
|
||||||
if '\\' in aval or '\n' in aval or aname.startswith('['):
|
if '\\' in aval or '\n' in aval or aname.startswith('['):
|
||||||
lines.append(f' ;; SKIP attr {aname} (contains special chars)')
|
lines.append(f' ;; SKIP attr {aname} (contains special chars)')
|
||||||
@@ -322,7 +336,7 @@ def generate_test(test, idx):
|
|||||||
|
|
||||||
# Assertions
|
# Assertions
|
||||||
for check in checks:
|
for check in checks:
|
||||||
sx = check_to_sx(check)
|
sx = check_to_sx(check, ref)
|
||||||
lines.append(f' {sx}')
|
lines.append(f' {sx}')
|
||||||
|
|
||||||
lines.append(' ))') # close let + deftest
|
lines.append(' ))') # close let + deftest
|
||||||
|
|||||||
@@ -256,6 +256,17 @@ test.describe('Hyperscript behavioral tests', () => {
|
|||||||
for (const [t, n] of Object.entries(errTypes).sort((a,b) => b[1] - a[1])) {
|
for (const [t, n] of Object.entries(errTypes).sort((a,b) => b[1] - a[1])) {
|
||||||
console.log(` ${t}: ${n}`);
|
console.log(` ${t}: ${n}`);
|
||||||
}
|
}
|
||||||
|
// Show sample failures per type
|
||||||
|
for (const [t, n] of Object.entries(errTypes).sort((a,b) => b[1] - a[1])) {
|
||||||
|
const samples = results.filter(r => !r.p).filter(r => {
|
||||||
|
const e = r.e || '';
|
||||||
|
if (t === 'crash') return e.includes('callFn');
|
||||||
|
if (t === 'stub') return e.includes('NOT IMPLEMENTED');
|
||||||
|
if (t === 'timeout') return e === 'TIMEOUT';
|
||||||
|
return false;
|
||||||
|
}).slice(0, 3);
|
||||||
|
for (const s of samples) console.log(` ${s.s}/${s.n}: ${(s.e||'').slice(0, 120)}`);
|
||||||
|
}
|
||||||
|
|
||||||
expect(results.length).toBeGreaterThanOrEqual(830);
|
expect(results.length).toBeGreaterThanOrEqual(830);
|
||||||
expect(passed).toBeGreaterThanOrEqual(420);
|
expect(passed).toBeGreaterThanOrEqual(420);
|
||||||
|
|||||||
Reference in New Issue
Block a user