Hyperscript conformance: 341→372 (45%) — parser, compiler, runtime, generator

Parser: increment/decrement "by N", then-less command chaining, scroll/select/
reset/default/halt commands, toggle style/attr/between, repeat for-loop
delegation, number fix for repeat N times, take with from/for scope.

Compiler: emit-inc/emit-dec with amount + property/style targets, 12 new
dispatch entries (scroll, select, reset, default, halt, toggle-style,
toggle-style-between, toggle-attr, toggle-attr-between, take rewrite).

Runtime: hs-scroll!, hs-halt!, hs-select!, hs-reset!, hs-query-all,
hs-toggle-style!, hs-toggle-style-between!, hs-toggle-attr!,
hs-toggle-attr-between!, hs-take! rewrite with kind/name/scope.

Generator: handle backtick strings, two-line run()/expect() patterns,
toEqual with arrays, toThrow — unlocks 34 more eval-only tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-12 10:00:51 +00:00
parent 56855eee7f
commit 3dbbe7e1d1
3 changed files with 220 additions and 78 deletions

View File

@@ -6696,9 +6696,11 @@
;; ── collectionExpressions (22 tests) ──
(defsuite "hs-upstream-collectionExpressions"
(deftest "filters an array by condition"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= (list "a" "c") (eval-hs "set arr to [{name: \"a\", active: true}, {name: \"b\", active: false}, {name: \"c\", active: true}] then return arr where its active"))
)
(deftest "filters with comparison"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= (list 4 5) (eval-hs "set arr to [1, 2, 3, 4, 5] then return arr where it > 3"))
)
(deftest "works with DOM elements"
(hs-cleanup!)
(let ((_el-list (dom-create-element "ul")) (_el-button (dom-create-element "button")) (_el-out (dom-create-element "div")))
@@ -6713,23 +6715,32 @@
(assert= "AC" (dom-text-content (dom-query-by-id "out")))
))
(deftest "sorts by a property"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= (list "Alice" "Bob" "Charlie") (eval-hs "set arr to [{name: \"Charlie\"}, {name: \"Alice\"}, {name: \"Bob\"}] then return arr sorted by its name"))
)
(deftest "sorts descending"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= (list 3 2 1) (eval-hs "set arr to [3, 1, 2] then return arr sorted by it descending"))
)
(deftest "sorts numbers by a computed key"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= (list "a" "c" "b") (eval-hs "set arr to [{name: \"b\", age: 30}, {name: \"a\", age: 20}, {name: \"c\", age: 25}] then return arr sorted by its age"))
)
(deftest "maps to a property"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= (list "Alice" "Bob") (eval-hs "set arr to [{name: \"Alice\"}, {name: \"Bob\"}] then return arr mapped to its name"))
)
(deftest "maps with an expression"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= (list 2 4 6) (eval-hs "set arr to [1, 2, 3] then return arr mapped to (it * 2)"))
)
(deftest "where then mapped to"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= (list "Alice" "Charlie") (eval-hs "set arr to [{name: \"Alice\", active: true}, {name: \"Bob\", active: false}, {name: \"Charlie\", active: true}] then return arr where its active mapped to its name"))
)
(deftest "sorted by then mapped to"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= (list "Alice" "Charlie") (eval-hs "set arr to [{name: \"Charlie\", age: 30}, {name: \"Alice\", age: 20}] then return arr sorted by its age mapped to its name"))
)
(deftest "where then sorted by then mapped to"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= (list "Bob" "Charlie") (eval-hs "set arr to [{name: \"Charlie\", active: true, age: 30}, {name: \"Alice\", active: false, age: 20}, {name: \"Bob\", active: true, age: 25}] then return arr where its active sorted by its age mapped to its name"))
)
(deftest "the result inside where refers to previous command result, not current element"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= (list 4 5) (eval-hs "get 3 then set arr to [1, 2, 3, 4, 5] then return arr where it > the result"))
)
(deftest "where binds after in without parens"
(hs-cleanup!)
(let ((_el-container (dom-create-element "div")))
@@ -6810,19 +6821,26 @@
;; ── splitJoin (7 tests) ──
(defsuite "hs-upstream-splitJoin"
(deftest "splits a string by delimiter"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= (list "a" "b" "c") (eval-hs "return \"a,b,c\" split by \",\""))
)
(deftest "splits by whitespace"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= (list "hello" "world") (eval-hs "return \"hello world\" split by \" \""))
)
(deftest "joins an array with delimiter"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= "a, b, c" (eval-hs "return [\"a\", \"b\", \"c\"] joined by \", \""))
)
(deftest "joins with empty string"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= "xyz" (eval-hs "return [\"x\", \"y\", \"z\"] joined by \"\""))
)
(deftest "split then where then joined"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= "a-b-c" (eval-hs "return \"a,,b,,c\" split by \",\" where it is not \"\" joined by \"-\""))
)
(deftest "split then sorted then joined"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= "apple, banana, cherry" (eval-hs "return \"banana,apple,cherry\" split by \",\" sorted by it joined by \", \""))
)
(deftest "split then mapped then joined"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= "5,5" (eval-hs "return \"hello world\" split by \" \" mapped to its length joined by \",\""))
)
)
;; ── component (19 tests) ──
@@ -7380,17 +7398,23 @@
(deftest "converts value as Boolean"
(assert= true (eval-hs "1 as Boolean"))
(assert= false (eval-hs "0 as Boolean"))
(assert= false (eval-hs "'' as Boolean"))
(assert= true (eval-hs "'hello' as Boolean"))
)
(deftest "can use the a modifier if you like"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(deftest "parses string as JSON to object"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= "bar" (eval-hs "'{\"foo\":\"bar\"}' as JSON"))
)
(deftest "converts value as JSONString"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= "{"foo":"bar"}" (eval-hs "{foo:'bar'} as JSONString"))
)
(deftest "pipe operator chains conversions"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= "bar" (eval-hs "{foo:'bar'} as JSONString | JSON"))
)
(deftest "can use the an modifier if you'd like"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= "bar" (eval-hs "'{\"foo\":\"bar\"}' as an Object"))
)
(deftest "collects duplicate text inputs into an array"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(deftest "converts multiple selects with programmatically changed selections"
@@ -7485,33 +7509,69 @@
(assert= "yes" (dom-text-content (dom-query-by-id "d1")))
))
(deftest "is ignoring case works"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= true (eval-hs "'Hello' is 'hello' ignoring case"))
(assert= true (eval-hs "'Hello' is 'HELLO' ignoring case"))
(assert= false (eval-hs "'Hello' is 'world' ignoring case"))
)
(deftest "is not ignoring case works"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= true (eval-hs "'Hello' is not 'world' ignoring case"))
(assert= false (eval-hs "'Hello' is not 'hello' ignoring case"))
)
(deftest "contains ignoring case works"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= true (eval-hs "'Hello World' contains 'hello' ignoring case"))
(assert= true (eval-hs "'Hello World' contains 'WORLD' ignoring case"))
(assert= false (eval-hs "'Hello World' contains 'missing' ignoring case"))
)
(deftest "matches ignoring case works"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= true (eval-hs "'Hello' matches 'hello' ignoring case"))
(assert= true (eval-hs "'Hello' matches 'HELLO' ignoring case"))
)
(deftest "starts with works"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= true (eval-hs "'hello world' starts with 'hello'"))
(assert= false (eval-hs "'hello world' starts with 'world'"))
(assert= true (eval-hs "'hello' starts with 'hello'"))
(assert= false (eval-hs "'' starts with 'x'"))
)
(deftest "ends with works"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= true (eval-hs "'hello world' ends with 'world'"))
(assert= false (eval-hs "'hello world' ends with 'hello'"))
(assert= true (eval-hs "'hello' ends with 'hello'"))
(assert= false (eval-hs "'' ends with 'x'"))
)
(deftest "does not start with works"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= false (eval-hs "'hello world' does not start with 'hello'"))
(assert= true (eval-hs "'hello world' does not start with 'world'"))
)
(deftest "does not end with works"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= false (eval-hs "'hello world' does not end with 'world'"))
(assert= true (eval-hs "'hello world' does not end with 'hello'"))
)
(deftest "starts with null is false"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= false (eval-hs "null starts with 'x'"))
(assert= true (eval-hs "null does not start with 'x'"))
)
(deftest "ends with null is false"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= false (eval-hs "null ends with 'x'"))
(assert= true (eval-hs "null does not end with 'x'"))
)
(deftest "starts with ignoring case works"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= true (eval-hs "'Hello World' starts with 'hello' ignoring case"))
(assert= true (eval-hs "'Hello World' starts with 'HELLO' ignoring case"))
(assert= false (eval-hs "'Hello World' starts with 'world' ignoring case"))
)
(deftest "ends with ignoring case works"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= true (eval-hs "'Hello World' ends with 'world' ignoring case"))
(assert= true (eval-hs "'Hello World' ends with 'WORLD' ignoring case"))
(assert= false (eval-hs "'Hello World' ends with 'hello' ignoring case"))
)
(deftest "starts with coerces to string"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= true (eval-hs "123 starts with '12'"))
(assert= false (eval-hs "123 starts with '23'"))
)
(deftest "ends with coerces to string"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= true (eval-hs "123 ends with '23'"))
(assert= false (eval-hs "123 ends with '12'"))
)
(deftest "is between works"
(assert= true (eval-hs "5 is between 1 and 10"))
(assert= true (eval-hs "1 is between 1 and 10"))
@@ -7527,7 +7587,9 @@
(assert= false (eval-hs "10 is not between 1 and 10"))
)
(deftest "between works with strings"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= true (eval-hs "'b' is between 'a' and 'c'"))
(assert= false (eval-hs "'d' is between 'a' and 'c'"))
)
(deftest "I am between works"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(deftest "I am not between works"
@@ -7585,8 +7647,10 @@
))
(deftest "is really works without equal to"
(assert= true (eval-hs "2 is really 2"))
(assert= false (eval-hs "2 is really '2'"))
)
(deftest "is not really works without equal to"
(assert= true (eval-hs "2 is not really '2'"))
(assert= false (eval-hs "2 is not really 2"))
)
(deftest "is equal works without to"
@@ -7667,8 +7731,7 @@
;; ── in (1 tests) ──
(defsuite "hs-upstream-in"
(deftest "null value in array returns empty"
;; toEqual: []
)
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
)
;; ── logicalOperator (3 tests) ──
@@ -7684,25 +7747,23 @@
;; ── mathOperator (5 tests) ──
(defsuite "hs-upstream-mathOperator"
(deftest "array + array concats"
;; toEqual: [1, 2, 3, 4]
)
(deftest "array + single value appends"
;; toEqual: [1, 2, 3]
)
(deftest "array + array does not mutate original"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(deftest "array + single value appends"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(deftest "array + array does not mutate original"
(assert= (list 1 2) (eval-hs "set a to [1, 2] then set b to a + [3] then return a"))
)
(deftest "array concat chains"
;; toEqual: [1, 2, 3]
)
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(deftest "empty array + array works"
;; toEqual: [1, 2]
)
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
)
;; ── no (5 tests) ──
(defsuite "hs-upstream-no"
(deftest "no returns false for non-empty array"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= false (eval-hs "no ['thing']"))
)
(deftest "no with where filters then checks emptiness"
(assert= true (eval-hs "no [1, 2, 3] where it > 5"))
)
@@ -7710,7 +7771,8 @@
(assert= false (eval-hs "no [1, 2, 3] where it > 1"))
)
(deftest "no with where and is not"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(assert= false (eval-hs "no [1, 2, 3] where it is not 2"))
)
(deftest "no with where on DOM elements"
(hs-cleanup!)
(let ((_el-box (dom-create-element "div")) (_el-button (dom-create-element "button")) (_el-out (dom-create-element "div")))
@@ -7728,8 +7790,7 @@
;; ── objectLiteral (1 tests) ──
(defsuite "hs-upstream-objectLiteral"
(deftest "allows trailing commas"
;; toEqual: { "foo": true, "bar-baz": false }
)
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
)
;; ── queryRef (1 tests) ──

View File

@@ -1,4 +1,6 @@
{
"status": "passed",
"failedTests": []
"status": "failed",
"failedTests": [
"1c2c0c67218972a8b3e8-5ab09db25e2a06899363"
]
}

View File

@@ -508,37 +508,116 @@ def generate_test_pw(test, elements, var_names, idx):
return '\n'.join(lines)
def js_val_to_sx(val):
"""Convert a JS literal value to SX."""
val = val.strip()
if val == 'true': return 'true'
if val == 'false': return 'false'
if val in ('null', 'undefined'): return 'nil'
if val.startswith('"') or val.startswith("'"):
return '"' + val.strip("\"'") + '"'
# Arrays: [1, 2, 3] → (list 1 2 3)
if val.startswith('[') and val.endswith(']'):
inner = val[1:-1].strip()
if not inner:
return '(list)'
items = [js_val_to_sx(x.strip()) for x in split_top_level(inner)]
return '(list ' + ' '.join(items) + ')'
try:
float(val)
return val
except ValueError:
return f'"{val}"'
def split_top_level(s):
"""Split a string by commas, respecting brackets/quotes."""
parts = []
depth = 0
current = []
in_str = None
for ch in s:
if in_str:
current.append(ch)
if ch == in_str:
in_str = None
elif ch in ('"', "'"):
in_str = ch
current.append(ch)
elif ch in ('(', '[', '{'):
depth += 1
current.append(ch)
elif ch in (')', ']', '}'):
depth -= 1
current.append(ch)
elif ch == ',' and depth == 0:
parts.append(''.join(current))
current = []
else:
current.append(ch)
if current:
parts.append(''.join(current))
return parts
def extract_hs_expr(raw):
"""Clean a HS expression extracted from run() call."""
# Remove surrounding whitespace and newlines
expr = raw.strip().replace('\n', ' ').replace('\t', ' ')
# Collapse multiple spaces
expr = re.sub(r'\s+', ' ', expr)
# Escape quotes for SX string
expr = expr.replace('\\', '').replace('"', '\\"')
return expr
def generate_eval_only_test(test, idx):
"""Generate SX deftest for no-HTML tests using eval-hs.
Parses body field for run("expr").toBe(val) / expect(run("expr")).toBe(val) patterns."""
Handles patterns:
- run("expr").toBe(val)
- expect(run("expr")).toBe(val)
- var result = await run(`expr`); expect(result).toBe(val)
- run("expr").toEqual([...])
- run("expr").toThrow()
"""
body = test.get('body', '')
lines = []
lines.append(f' (deftest "{test["name"]}"')
safe_name = test["name"].replace('"', "'")
lines.append(f' (deftest "{safe_name}"')
# Extract run("expr").toBe(val) or expect(await run("expr")).toBe(val) patterns
assertions = []
for m in re.finditer(r'(?:expect\()?(?:await\s+)?run\(["\x27]([^"\x27]+)["\x27]\)\)?\.toBe\(([^)]+)\)', body):
hs_expr = m.group(1).replace('\\', '').replace('"', '\\"')
expected = m.group(2).strip()
# Convert JS values to SX
if expected == 'true': expected_sx = 'true'
elif expected == 'false': expected_sx = 'false'
elif expected == 'null' or expected == 'undefined': expected_sx = 'nil'
elif expected.startswith('"') or expected.startswith("'"):
expected_sx = '"' + expected.strip("\"'") + '"'
else:
try:
float(expected)
expected_sx = expected
except ValueError:
expected_sx = f'"{expected}"'
# Pattern 1: Inline — run("expr").toBe(val) or expect(run("expr")).toBe(val)
for m in re.finditer(
r'(?:expect\()?(?:await\s+)?run\((["\x27`])(.+?)\1\)\)?\.toBe\(([^)]+)\)',
body, re.DOTALL
):
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}"))')
# Also handle toEqual patterns
for m in re.finditer(r'(?:expect\()?(?:await\s+)?run\(["\x27]([^"\x27]+)["\x27]\)\)?\.toEqual\(([^)]+)\)', body):
hs_expr = m.group(1).replace('\\', '').replace('"', '\\"')
expected = m.group(2).strip()
assertions.append(f' ;; toEqual: {expected[:40]}')
# Pattern 2: Two-line — var result = await run(`expr`); expect(result).toBe(val)
if not assertions:
run_match = re.search(
r'(?:var|let|const)\s+\w+\s*=\s*(?:await\s+)?run\((["\x27`])(.+?)\1\)',
body, re.DOTALL
)
if run_match:
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}"))')
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}"))')
# Pattern 3: toThrow — expect(() => run("expr")).toThrow()
for m in re.finditer(
r'run\((["\x27`])(.+?)\1\).*?\.toThrow\(\)',
body, re.DOTALL
):
hs_expr = extract_hs_expr(m.group(2))
assertions.append(f' (assert-throws (eval-hs "{hs_expr}"))')
if not assertions:
return None # Can't convert this body pattern