HS test generator: property access, locals, me-val, nested opts (515→517/831)

- Handle result["foo"] and result.foo property access after eval-hs
- Handle { locals: { x: 5, y: 5 } } opts with nested braces
- Handle { me: N } opts via eval-hs-with-me helper
- Add eval-hs-with-me to test framework for "I am between" tests
- Use host-get for property access on host handles (JSON.parse results)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-16 14:58:00 +00:00
parent fc76a42403
commit 87fdb1db71
2 changed files with 63 additions and 19 deletions

View File

@@ -25,6 +25,14 @@
(list (quote fn) (list (quote me)) (list (quote let) (list (list (quote it) nil) (list (quote event) nil)) sx))))) (list (quote fn) (list (quote me)) (list (quote let) (list (list (quote it) nil) (list (quote event) nil)) sx)))))
(handler nil))))) (handler nil)))))
;; Evaluate with a specific me value (for "I am between" etc.)
(define eval-hs-with-me
(fn (src me-val)
(let ((sx (hs-to-sx (hs-compile src))))
(let ((handler (eval-expr-cek
(list (quote fn) (list (quote me)) (list (quote let) (list (list (quote it) nil) (list (quote event) nil)) sx)))))
(handler me-val)))))
;; ── add (19 tests) ── ;; ── add (19 tests) ──
(defsuite "hs-upstream-add" (defsuite "hs-upstream-add"
(deftest "can add class ref on a single div" (deftest "can add class ref on a single div"
@@ -7492,7 +7500,7 @@
;; ── collectionExpressions (22 tests) ── ;; ── collectionExpressions (22 tests) ──
(defsuite "hs-upstream-collectionExpressions" (defsuite "hs-upstream-collectionExpressions"
(deftest "filters an array by condition" (deftest "filters an array by condition"
(assert= (eval-hs "set arr to [{name: \"a\", active: true}, {name: \"b\", active: false}, {name: \"c\", active: true}] then return arr where its active") (list "a" "c")) (assert= (map (fn (x) (get x "name")) (eval-hs "set arr to [{name: \"a\", active: true}, {name: \"b\", active: false}, {name: \"c\", active: true}] then return arr where its active")) (list "a" "c"))
) )
(deftest "filters with comparison" (deftest "filters with comparison"
(assert= (eval-hs "set arr to [1, 2, 3, 4, 5] then return arr where it > 3") (list 4 5)) (assert= (eval-hs "set arr to [1, 2, 3, 4, 5] then return arr where it > 3") (list 4 5))
@@ -7520,13 +7528,13 @@
(assert= (dom-text-content (dom-query-by-id "out")) "AC") (assert= (dom-text-content (dom-query-by-id "out")) "AC")
)) ))
(deftest "sorts by a property" (deftest "sorts by a property"
(assert= (eval-hs "set arr to [{name: \"Charlie\"}, {name: \"Alice\"}, {name: \"Bob\"}] then return arr sorted by its name") (list "Alice" "Bob" "Charlie")) (assert= (map (fn (x) (get x "name")) (eval-hs "set arr to [{name: \"Charlie\"}, {name: \"Alice\"}, {name: \"Bob\"}] then return arr sorted by its name")) (list "Alice" "Bob" "Charlie"))
) )
(deftest "sorts descending" (deftest "sorts descending"
(assert= (eval-hs "set arr to [3, 1, 2] then return arr sorted by it descending") (list 3 2 1)) (assert= (eval-hs "set arr to [3, 1, 2] then return arr sorted by it descending") (list 3 2 1))
) )
(deftest "sorts numbers by a computed key" (deftest "sorts numbers by a computed key"
(assert= (eval-hs "set arr to [{name: \"b\", age: 30}, {name: \"a\", age: 20}, {name: \"c\", age: 25}] then return arr sorted by its age") (list "a" "c" "b")) (assert= (map (fn (x) (get x "name")) (eval-hs "set arr to [{name: \"b\", age: 30}, {name: \"a\", age: 20}, {name: \"c\", age: 25}] then return arr sorted by its age")) (list "a" "c" "b"))
) )
(deftest "maps to a property" (deftest "maps to a property"
(assert= (eval-hs "set arr to [{name: \"Alice\"}, {name: \"Bob\"}] then return arr mapped to its name") (list "Alice" "Bob")) (assert= (eval-hs "set arr to [{name: \"Alice\"}, {name: \"Bob\"}] then return arr mapped to its name") (list "Alice" "Bob"))
@@ -8441,16 +8449,16 @@
(deftest "can use the a modifier if you like" (deftest "can use the a modifier if you like"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) (error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
(deftest "parses string as JSON to object" (deftest "parses string as JSON to object"
(assert= (eval-hs "'{\"foo\":\"bar\"}' as JSON") "bar") (assert= (host-get (eval-hs "'{\"foo\":\"bar\"}' as JSON") "foo") "bar")
) )
(deftest "converts value as JSONString" (deftest "converts value as JSONString"
(assert= (eval-hs "{foo:'bar'} as JSONString") "{"foo":"bar"}") (assert= (eval-hs "{foo:'bar'} as JSONString") "{"foo":"bar"}")
) )
(deftest "pipe operator chains conversions" (deftest "pipe operator chains conversions"
(assert= (eval-hs "{foo:'bar'} as JSONString | JSON") "bar") (assert= (host-get (eval-hs "{foo:'bar'} as JSONString | JSON") "foo") "bar")
) )
(deftest "can use the an modifier if you'd like" (deftest "can use the an modifier if you'd like"
(assert= (eval-hs "'{\"foo\":\"bar\"}' as an Object") "bar") (assert= (host-get (eval-hs "'{\"foo\":\"bar\"}' as an Object") "foo") "bar")
) )
(deftest "collects duplicate text inputs into an array" (deftest "collects duplicate text inputs into an array"
(error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) (error "NOT IMPLEMENTED: test HTML could not be parsed into SX"))
@@ -8658,12 +8666,12 @@
(assert= (eval-hs "'d' is between 'a' and 'c'") false) (assert= (eval-hs "'d' is between 'a' and 'c'") false)
) )
(deftest "I am between works" (deftest "I am between works"
(assert= (eval-hs "I am between 1 and 10") true) (assert= (eval-hs-with-me "I am between 1 and 10" 5) true)
(assert= (eval-hs "I am between 1 and 10") false) (assert= (eval-hs-with-me "I am between 1 and 10" 0) false)
) )
(deftest "I am not between works" (deftest "I am not between works"
(assert= (eval-hs "I am not between 1 and 10") false) (assert= (eval-hs-with-me "I am not between 1 and 10" 5) false)
(assert= (eval-hs "I am not between 1 and 10") true) (assert= (eval-hs-with-me "I am not between 1 and 10" 0) true)
) )
(deftest "precedes works" (deftest "precedes works"
(hs-cleanup!) (hs-cleanup!)
@@ -8777,8 +8785,8 @@
(dom-append (dom-body) _el-b2) (dom-append (dom-body) _el-b2)
)) ))
(deftest "is still does equality when rhs variable exists" (deftest "is still does equality when rhs variable exists"
(assert= (eval-hs "x is y") true) (let ((x 5) (y 5)) (assert= (eval-hs "x is y") true))
(assert= (eval-hs "x is y") false) (let ((x 5) (y 6)) (assert= (eval-hs "x is y") false))
) )
(deftest "is boolean property works in where clause" (deftest "is boolean property works in where clause"
(hs-cleanup!) (hs-cleanup!)

View File

@@ -717,12 +717,27 @@ def generate_eval_only_test(test, idx):
# Pattern 1: Inline — expect(run("expr", opts)).toBe(val) or run("expr", opts).toBe(val) # Pattern 1: Inline — expect(run("expr", opts)).toBe(val) or run("expr", opts).toBe(val)
for m in re.finditer( for m in re.finditer(
r'(?:expect\()?' + _RUN_OPEN + _RUN_ARGS + r'\)\)?\.toBe\(([^)]+)\)', r'(?:expect\()?' + _RUN_OPEN + r'(\s*,\s*\{[^}]*(?:\{[^}]*\}[^}]*)?\})?' + r'\)\)?\.toBe\(([^)]+)\)',
body, re.DOTALL body, re.DOTALL
): ):
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)) opts_str = m.group(3) or ''
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})') expected_sx = js_val_to_sx(m.group(4))
# Check for { me: X } or { locals: { x: X, y: Y } } in opts
me_match = re.search(r'\bme:\s*(\d+)', opts_str)
locals_match = re.search(r'locals:\s*\{([^}]+)\}', opts_str)
if locals_match:
local_bindings = []
for lm in re.finditer(r'(\w+)\s*:\s*([^,}]+)', locals_match.group(1)):
lname = lm.group(1)
lval = js_val_to_sx(lm.group(2).strip())
local_bindings.append(f'({lname} {lval})')
assertions.append(f' (let ({" ".join(local_bindings)}) (assert= (eval-hs "{hs_expr}") {expected_sx}))')
elif me_match:
me_val = me_match.group(1)
assertions.append(f' (assert= (eval-hs-with-me "{hs_expr}" {me_val}) {expected_sx})')
else:
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
# Pattern 1b: Inline — run("expr", opts).toEqual([...]) # Pattern 1b: Inline — run("expr", opts).toEqual([...])
for m in re.finditer( for m in re.finditer(
@@ -752,12 +767,25 @@ def generate_eval_only_test(test, idx):
) )
if run_match: if run_match:
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): var_name = re.search(r'(?:var|let|const)\s+(\w+)', body).group(1)
expected_sx = js_val_to_sx(m.group(1)) for m in re.finditer(r'expect\((' + re.escape(var_name) + r'(?:\["[^"]+"\]|\.\w+)?)\)\.toBe\(([^)]+)\)', body):
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})') accessor = m.group(1)
for m in re.finditer(r'\.toEqual\((\[.*?\])\)', body, re.DOTALL): expected_sx = js_val_to_sx(m.group(2))
# Check for property access: result["foo"] or result.foo
prop_m = re.search(r'\["([^"]+)"\]|\.(\w+)', accessor[len(var_name):])
if prop_m:
prop = prop_m.group(1) or prop_m.group(2)
assertions.append(f' (assert= (host-get (eval-hs "{hs_expr}") "{prop}") {expected_sx})')
else:
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
for m in re.finditer(r'expect\(' + re.escape(var_name) + r'(?:\.\w+)?\)\.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= (eval-hs "{hs_expr}") {expected_sx})') assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
# Handle .map(x => x.prop) before toEqual
for m in re.finditer(r'expect\(' + re.escape(var_name) + r'\.map\(\w+\s*=>\s*\w+\.(\w+)\)\)\.toEqual\((\[.*?\])\)', body, re.DOTALL):
prop = m.group(1)
expected_sx = js_val_to_sx(m.group(2))
assertions.append(f' (assert= (map (fn (x) (get x "{prop}")) (eval-hs "{hs_expr}")) {expected_sx})')
# Pattern 2b: run() with locals + evaluate(window.X) + expect().toBe/toEqual # Pattern 2b: run() with locals + evaluate(window.X) + expect().toBe/toEqual
# e.g.: await run(`expr`, {locals: {arr: [1,2,3]}}); # e.g.: await run(`expr`, {locals: {arr: [1,2,3]}});
@@ -883,6 +911,14 @@ output.append(' (let ((handler (eval-expr-cek')
output.append(' (list (quote fn) (list (quote me)) (list (quote let) (list (list (quote it) nil) (list (quote event) nil)) sx)))))') output.append(' (list (quote fn) (list (quote me)) (list (quote let) (list (list (quote it) nil) (list (quote event) nil)) sx)))))')
output.append(' (handler nil)))))') output.append(' (handler nil)))))')
output.append('') output.append('')
output.append(';; Evaluate with a specific me value (for "I am between" etc.)')
output.append('(define eval-hs-with-me')
output.append(' (fn (src me-val)')
output.append(' (let ((sx (hs-to-sx (hs-compile src))))')
output.append(' (let ((handler (eval-expr-cek')
output.append(' (list (quote fn) (list (quote me)) (list (quote let) (list (list (quote it) nil) (list (quote event) nil)) sx)))))')
output.append(' (handler me-val)))))')
output.append('')
# Group by category # Group by category
categories = OrderedDict() categories = OrderedDict()