From 87fdb1db7157a28f11eedd2699285744e11ea085 Mon Sep 17 00:00:00 2001 From: giles Date: Thu, 16 Apr 2026 14:58:00 +0000 Subject: [PATCH] =?UTF-8?q?HS=20test=20generator:=20property=20access,=20l?= =?UTF-8?q?ocals,=20me-val,=20nested=20opts=20(515=E2=86=92517/831)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- spec/tests/test-hyperscript-behavioral.sx | 32 +++++++++------ tests/playwright/generate-sx-tests.py | 50 +++++++++++++++++++---- 2 files changed, 63 insertions(+), 19 deletions(-) diff --git a/spec/tests/test-hyperscript-behavioral.sx b/spec/tests/test-hyperscript-behavioral.sx index 7ba70023..5c129e2d 100644 --- a/spec/tests/test-hyperscript-behavioral.sx +++ b/spec/tests/test-hyperscript-behavioral.sx @@ -25,6 +25,14 @@ (list (quote fn) (list (quote me)) (list (quote let) (list (list (quote it) nil) (list (quote event) nil)) sx))))) (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) ── (defsuite "hs-upstream-add" (deftest "can add class ref on a single div" @@ -7492,7 +7500,7 @@ ;; ── collectionExpressions (22 tests) ── (defsuite "hs-upstream-collectionExpressions" (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" (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") )) (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" (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" - (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" (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" (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) (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" (assert= (eval-hs "{foo:'bar'} as JSONString") "{"foo":"bar"}") ) (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" - (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" (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) ) (deftest "I am between works" - (assert= (eval-hs "I am between 1 and 10") true) - (assert= (eval-hs "I am between 1 and 10") false) + (assert= (eval-hs-with-me "I am between 1 and 10" 5) true) + (assert= (eval-hs-with-me "I am between 1 and 10" 0) false) ) (deftest "I am not between works" - (assert= (eval-hs "I am not between 1 and 10") false) - (assert= (eval-hs "I am not between 1 and 10") true) + (assert= (eval-hs-with-me "I am not between 1 and 10" 5) false) + (assert= (eval-hs-with-me "I am not between 1 and 10" 0) true) ) (deftest "precedes works" (hs-cleanup!) @@ -8777,8 +8785,8 @@ (dom-append (dom-body) _el-b2) )) (deftest "is still does equality when rhs variable exists" - (assert= (eval-hs "x is y") true) - (assert= (eval-hs "x is y") false) + (let ((x 5) (y 5)) (assert= (eval-hs "x is y") true)) + (let ((x 5) (y 6)) (assert= (eval-hs "x is y") false)) ) (deftest "is boolean property works in where clause" (hs-cleanup!) diff --git a/tests/playwright/generate-sx-tests.py b/tests/playwright/generate-sx-tests.py index 3ad49ee9..0d097301 100644 --- a/tests/playwright/generate-sx-tests.py +++ b/tests/playwright/generate-sx-tests.py @@ -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) 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 ): hs_expr = extract_hs_expr(m.group(2)) - expected_sx = js_val_to_sx(m.group(3)) - assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})') + opts_str = m.group(3) or '' + 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([...]) for m in re.finditer( @@ -752,12 +767,25 @@ def generate_eval_only_test(test, idx): ) 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= (eval-hs "{hs_expr}") {expected_sx})') - for m in re.finditer(r'\.toEqual\((\[.*?\])\)', body, re.DOTALL): + var_name = re.search(r'(?:var|let|const)\s+(\w+)', body).group(1) + for m in re.finditer(r'expect\((' + re.escape(var_name) + r'(?:\["[^"]+"\]|\.\w+)?)\)\.toBe\(([^)]+)\)', body): + accessor = m.group(1) + 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)) 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 # 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(' (handler nil)))))') 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 categories = OrderedDict()