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)
|
||||
return elements
|
||||
|
||||
def parse_action(action):
|
||||
def parse_action(action, ref):
|
||||
"""Convert upstream action to SX. Returns list of SX expressions."""
|
||||
if not action or action == '(see body)':
|
||||
return []
|
||||
|
||||
exprs = []
|
||||
# Split on ';' for multi-step actions
|
||||
for part in action.split(';'):
|
||||
part = part.strip()
|
||||
if not part:
|
||||
continue
|
||||
|
||||
# Pattern: var.click()
|
||||
m = re.match(r'(\w+)\.click\(\)', part)
|
||||
if m:
|
||||
name = m.group(1)
|
||||
exprs.append(f'(dom-dispatch {ref(name)} "click" nil)')
|
||||
exprs.append(f'(dom-dispatch {ref(m.group(1))} "click" nil)')
|
||||
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:
|
||||
exprs.append(f'(dom-dispatch {ref(m.group(1))} "{m.group(2)}" nil)')
|
||||
continue
|
||||
|
||||
# Pattern: var.dispatchEvent(new CustomEvent("name", {detail: {...}}))
|
||||
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)
|
||||
m = re.match(r'(\w+)\.setAttribute\("([\w-]+)",\s*"([^"]*)"\)', part)
|
||||
if m:
|
||||
exprs.append(f'(dom-set-attr {ref(m.group(1))} "{m.group(2)}" "{m.group(3)}")')
|
||||
continue
|
||||
|
||||
# Pattern: var.focus()
|
||||
m = re.match(r'(\w+)\.focus\(\)', part)
|
||||
if m:
|
||||
exprs.append(f'(dom-focus {ref(m.group(1))})')
|
||||
continue
|
||||
|
||||
# Pattern: var.appendChild(document.createElement("TAG"))
|
||||
m = re.match(r'(\w+)\.appendChild\(document\.createElement\("(\w+)"\)', part)
|
||||
if m:
|
||||
exprs.append(f'(dom-append {ref(m.group(1))} (dom-create-element "{m.group(2)}"))')
|
||||
continue
|
||||
|
||||
# Skip unrecognized
|
||||
# Sanitize comment — remove all chars that SX parser treats specially
|
||||
safe = re.sub(r'[\'\"$@`(),;\\#\[\]{}]', '_', part[:40])
|
||||
exprs.append(f';; SKIP action: {safe}')
|
||||
|
||||
@@ -203,17 +188,42 @@ def parse_checks(check):
|
||||
|
||||
return list(seen.values())
|
||||
|
||||
def ref(name):
|
||||
"""Convert a JS variable name to SX element reference.
|
||||
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 make_ref_fn(elements, var_names):
|
||||
"""Create a ref function that maps upstream JS variable names to SX let-bound variables.
|
||||
|
||||
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."""
|
||||
typ, name, key, val = check
|
||||
r = ref(name)
|
||||
@@ -250,8 +260,6 @@ def check_to_sx(check):
|
||||
def generate_test(test, idx):
|
||||
"""Generate SX deftest for an upstream test."""
|
||||
elements = parse_html(test['html'])
|
||||
actions = parse_action(test['action'])
|
||||
checks = parse_checks(test['check'])
|
||||
|
||||
if not elements and not test.get('html', '').strip():
|
||||
# eval-only test — no HTML at all
|
||||
@@ -277,6 +285,13 @@ def generate_test(test, idx):
|
||||
used_names.add(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
|
||||
bindings = []
|
||||
for i, el in enumerate(elements):
|
||||
@@ -305,7 +320,6 @@ def generate_test(test, idx):
|
||||
# Escape for SX double-quoted string
|
||||
hs_escaped = hs_val.replace('\\', '\\\\').replace('"', '\\"')
|
||||
lines.append(f' (dom-set-attr {var} "_" "{hs_escaped}")')
|
||||
all_hs_sources.add(hs_escaped)
|
||||
for aname, aval in el['attrs'].items():
|
||||
if '\\' in aval or '\n' in aval or aname.startswith('['):
|
||||
lines.append(f' ;; SKIP attr {aname} (contains special chars)')
|
||||
@@ -322,7 +336,7 @@ def generate_test(test, idx):
|
||||
|
||||
# Assertions
|
||||
for check in checks:
|
||||
sx = check_to_sx(check)
|
||||
sx = check_to_sx(check, ref)
|
||||
lines.append(f' {sx}')
|
||||
|
||||
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])) {
|
||||
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(passed).toBeGreaterThanOrEqual(420);
|
||||
|
||||
Reference in New Issue
Block a user