From d6ae303db35808b6b1c6188f298df078bad86814 Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 15 Apr 2026 07:12:24 +0000 Subject: [PATCH] HS test generator: convert pick, evalStatically, run+evaluate patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New generator patterns: - run() with {locals: {x: val}} + evaluate(window.X) + expect().toEqual() → (let ((x val)) (eval-hs "expr") (assert= it expected)) - evaluate(() => _hyperscript.parse("X").evalStatically()).toBe(val) → (assert= (eval-hs "X") val) - toContain/toHaveLength assertions Converts 12 tests from NOT IMPLEMENTED stubs (43→31 remaining): - pick: 7/7 now pass (was 0/7 stubs) - evalStatically: 5/8 now pass (was 0/8 stubs) 449/831 (54%), +12 from generator improvements. Co-Authored-By: Claude Opus 4.6 (1M context) --- spec/tests/test-hyperscript-behavioral.sx | 194 ++++++++++++++++------ tests/playwright/generate-sx-tests.py | 88 +++++++++- 2 files changed, 226 insertions(+), 56 deletions(-) diff --git a/spec/tests/test-hyperscript-behavioral.sx b/spec/tests/test-hyperscript-behavioral.sx index ac144cb9..1f436a6b 100644 --- a/spec/tests/test-hyperscript-behavioral.sx +++ b/spec/tests/test-hyperscript-behavioral.sx @@ -684,6 +684,10 @@ (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-d2) (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-get-style (dom-query-by-id "d2") "opacity") "0") + (dom-dispatch _el-div "click" nil) + (assert= (dom-get-style (dom-query-by-id "d2") "opacity") "1") )) (deftest "can toggle visibility on other elt" (hs-cleanup!) @@ -693,6 +697,10 @@ (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-d2) (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-get-style (dom-query-by-id "d2") "visibility") "hidden") + (dom-dispatch _el-div "click" nil) + (assert= (dom-get-style (dom-query-by-id "d2") "visibility") "visible") )) (deftest "can toggle *display between two values" (hs-cleanup!) @@ -1722,7 +1730,7 @@ (deftest "if properly supports nested if statements and end block" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click n then if window.tmp thenn then put \"foo\" into men else if not window.tmp thenn // do nothingn endn catch en // just here for the parsing...n") + (dom-set-attr _el-div "_" "on click if window.tmp then put \"foo\" into me then else if not window.tmp then // do nothing then end catch e then // just here for the parsing... then") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) @@ -1732,7 +1740,7 @@ (deftest "if on new line does not join w/ else" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click n then if window.tmp thenn elsen then if window.tmp then endn then put \"foo\" into men endn") + (dom-set-attr _el-div "_" "on click if window.tmp then else then if window.tmp then end put \"foo\" into me then end") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) @@ -1757,7 +1765,7 @@ (deftest "basic for loop with null works" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat for x in null then put x at end of me end") + (dom-set-attr _el-div "_" "on click repeat for x in null put x at end of me end") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) @@ -1766,7 +1774,7 @@ (deftest "waiting in for loop works" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat for x in [1, 2, 3]n then log me then put x at end of men then wait 1msn end") + (dom-set-attr _el-div "_" "on click repeat for x in [1, 2, 3] log me then put x at end of me then wait 1ms then end") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) @@ -1784,7 +1792,7 @@ (deftest "basic raw for loop works" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click for x in null then put x at end of me end") + (dom-set-attr _el-div "_" "on click for x in null put x at end of me end") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) @@ -1793,7 +1801,7 @@ (deftest "waiting in raw for loop works" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click for x in [1, 2, 3]n then put x at end of men then wait 1msn end") + (dom-set-attr _el-div "_" "on click for x in [1, 2, 3] put x at end of me then wait 1ms then end") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) @@ -1937,7 +1945,7 @@ (deftest "loop continue works" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat 2 times for x in ['A', 'B', 'C', 'D'] if (x != 'D') then put 'success ' + x + '. ' at end of me then continue then put 'FAIL!!. ' at end of me then end then put 'expected D. ' at end of me then end then end then") + (dom-set-attr _el-div "_" "on click repeat 2 times for x in ['A', 'B', 'C', 'D'] if (x != 'D') then put 'success ' + x + '. ' at end of me then continue then put 'FAIL!!. ' at end of me then end put 'expected D. ' at end of me then end end") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) @@ -1946,7 +1954,7 @@ (deftest "loop break works" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat 2 times for x in ['A', 'B', 'C', 'D'] if x is 'C' then break then end then put x at end of me then end then end then") + (dom-set-attr _el-div "_" "on click repeat 2 times for x in ['A', 'B', 'C', 'D'] if x is 'C' then break then end put x at end of me then end end") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) @@ -1955,7 +1963,7 @@ (deftest "basic raw for loop with null works" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click for x in null then put x at end of me end") + (dom-set-attr _el-div "_" "on click for x in null put x at end of me end") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) @@ -1964,7 +1972,7 @@ (deftest "basic property for loop works" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set x to {foo:1, bar:2, baz:3} then for prop in x then put x[prop] at end of me end") + (dom-set-attr _el-div "_" "on click set x to {foo:1, bar:2, baz:3} then for prop in x put x[prop] at end of me end") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) @@ -1973,7 +1981,7 @@ (deftest "bottom-tested repeat until" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set x to 0 then repeat then set x to until x is 3 end then put x into me") + (dom-set-attr _el-div "_" "on click set x to 0 then repeat then set x to until x is 3 end put x into me") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) @@ -1982,7 +1990,7 @@ (deftest "bottom-tested repeat while" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set x to 0 then repeat then set x to while x < 3 end then put x into me") + (dom-set-attr _el-div "_" "on click set x to 0 then repeat then set x to while x < 3 end put x into me") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) @@ -1991,7 +1999,7 @@ (deftest "bottom-tested loop always runs at least once" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set x to 0 then repeat then set x to until true end then put x into me") + (dom-set-attr _el-div "_" "on click set x to 0 then repeat then set x to until true end put x into me") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) @@ -2000,7 +2008,7 @@ (deftest "break exits a simple repeat loop" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set x to 0 then repeat 10 times set x to x + 1 then if x is 3 break end then end then put x into me then") + (dom-set-attr _el-div "_" "on click set x to 0 then repeat 10 times set x to x + 1 then if x is 3 break end end put x into me then") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) @@ -2009,7 +2017,7 @@ (deftest "continue skips rest of iteration in simple repeat loop" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat for x in [1, 2, 3, 4, 5] if x is 3 continue end then put x at end of me then end then") + (dom-set-attr _el-div "_" "on click repeat for x in [1, 2, 3, 4, 5] if x is 3 continue end put x at end of me then end") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) @@ -2018,7 +2026,7 @@ (deftest "break exits a for-in loop" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat for x in [1, 2, 3, 4, 5] if x is 4 break end then put x at end of me then end then") + (dom-set-attr _el-div "_" "on click repeat for x in [1, 2, 3, 4, 5] if x is 4 break end put x at end of me then end") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) @@ -2027,7 +2035,7 @@ (deftest "break exits a while loop" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set x to 0 then repeat while x < 100 then set x to x + 1 then if x is 5 break end then end then put x into me then") + (dom-set-attr _el-div "_" "on click set x to 0 then repeat while x < 100 then set x to x + 1 then if x is 5 break end end put x into me then") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) @@ -2036,7 +2044,7 @@ (deftest "for loop over undefined skips without error" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat for x in doesNotExist put x at end of me end then put \"done\" into me") + (dom-set-attr _el-div "_" "on click repeat for x in doesNotExist put x at end of me end put \"done\" into me") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) @@ -2638,6 +2646,8 @@ (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-foo) (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-get-style (dom-query-by-id "foo") "width") "100px") )) (deftest "can transition on another element with possessive" (hs-cleanup!) @@ -2647,6 +2657,8 @@ (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-foo) (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-get-style (dom-query-by-id "foo") "width") "100px") )) (deftest "can transition on another element with it" (hs-cleanup!) @@ -2656,6 +2668,8 @@ (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-foo) (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-get-style (dom-query-by-id "foo") "width") "100px") )) (deftest "can transition with a custom transition string" (hs-cleanup!) @@ -2665,6 +2679,8 @@ (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-foo) (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-get-style (dom-query-by-id "foo") "width") "100px") )) (deftest "can transition a single property on form using style ref" (hs-cleanup!) @@ -3383,7 +3399,7 @@ (hs-cleanup!) (let ((_el-d1 (dom-create-element "div")) (_el-p1 (dom-create-element "p")) (_el-p2 (dom-create-element "p")) (_el-d2 (dom-create-element "div"))) (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click add .foo then tell

in me then add .bar") + (dom-set-attr _el-d1 "_" "on click add .foo then tell

in me add .bar") (dom-set-attr _el-p1 "id" "p1") (dom-set-attr _el-p2 "id" "p2") (dom-set-attr _el-d2 "id" "d2") @@ -3406,7 +3422,7 @@ (hs-cleanup!) (let ((_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click tell #d2 then add .bar end then add .foo") + (dom-set-attr _el-d1 "_" "on click tell #d2 then add .bar end add .foo") (dom-set-attr _el-d2 "id" "d2") (dom-append (dom-body) _el-d1) (dom-append (dom-body) _el-d2) @@ -3421,7 +3437,7 @@ (hs-cleanup!) (let ((_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click tell null then add .bar end then add .foo") + (dom-set-attr _el-d1 "_" "on click tell null then add .bar end add .foo") (dom-set-attr _el-d2 "id" "d2") (dom-append (dom-body) _el-d1) (dom-append (dom-body) _el-d2) @@ -4784,6 +4800,8 @@ (dom-append (dom-body) _el-button) (hs-activate! _el-button) (dom-dispatch _el-button "click" nil) + (assert= (dom-text-content (dom-query ".clearme")) "") + (assert= (dom-text-content (dom-query ".clearme")) "") )) (deftest "can empty an array" (hs-cleanup!) @@ -4950,6 +4968,7 @@ (dom-set-attr _el-i1 "_" "on focus wait 10ms then blur me") (dom-append (dom-body) _el-i1) (hs-activate! _el-i1) + (dom-focus (dom-query-by-id "i1")) )) ) @@ -5272,6 +5291,9 @@ (dom-append _el-f1 _el-rst) (hs-activate! _el-button) (hs-activate! _el-rst) + (dom-set-prop (dom-query-by-id "t1") "value" "changed") + (dom-dispatch (dom-query-by-id "t1") "input" nil) + (assert= (dom-get-prop (dom-query-by-id "t1") "value") "changed") (dom-dispatch (dom-query-by-id "rst") "click" nil) (assert= (dom-get-prop (dom-query-by-id "t1") "value") "original") )) @@ -5285,6 +5307,9 @@ (dom-append (dom-body) _el-form) (dom-append _el-form _el-t2) (hs-activate! _el-form) + (dom-set-prop (dom-query-by-id "t2") "value" "modified") + (dom-dispatch (dom-query-by-id "t2") "input" nil) + (assert= (dom-get-prop (dom-query-by-id "t2") "value") "modified") (dom-dispatch _el-form "custom" nil) (assert= (dom-get-prop (dom-query-by-id "t2") "value") "default") )) @@ -5299,6 +5324,9 @@ (dom-append (dom-body) _el-t3) (dom-append (dom-body) _el-button) (hs-activate! _el-button) + (dom-set-prop (dom-query-by-id "t3") "value" "goodbye") + (dom-dispatch (dom-query-by-id "t3") "input" nil) + (assert= (dom-get-prop (dom-query-by-id "t3") "value") "goodbye") (dom-dispatch _el-button "click" nil) (assert= (dom-get-prop (dom-query-by-id "t3") "value") "hello") )) @@ -5312,6 +5340,9 @@ (dom-append (dom-body) _el-cb1) (dom-append (dom-body) _el-button) (hs-activate! _el-button) + (dom-set-prop (dom-query-by-id "cb1") "checked" false) + (dom-dispatch (dom-query-by-id "cb1") "change" nil) + (assert (not (dom-get-prop (dom-query-by-id "cb1") "checked"))) (dom-dispatch _el-button "click" nil) (assert (dom-get-prop (dom-query-by-id "cb1") "checked")) )) @@ -5325,6 +5356,9 @@ (dom-append (dom-body) _el-cb2) (dom-append (dom-body) _el-button) (hs-activate! _el-button) + (dom-set-prop (dom-query-by-id "cb2") "checked" true) + (dom-dispatch (dom-query-by-id "cb2") "change" nil) + (assert (dom-get-prop (dom-query-by-id "cb2") "checked")) (dom-dispatch _el-button "click" nil) (assert (not (dom-get-prop (dom-query-by-id "cb2") "checked"))) )) @@ -5338,6 +5372,9 @@ (dom-append (dom-body) _el-ta1) (dom-append (dom-body) _el-button) (hs-activate! _el-button) + (dom-set-prop (dom-query-by-id "ta1") "value" "new text") + (dom-dispatch (dom-query-by-id "ta1") "input" nil) + (assert= (dom-get-prop (dom-query-by-id "ta1") "value") "new text") (dom-dispatch _el-button "click" nil) (assert= (dom-get-prop (dom-query-by-id "ta1") "value") "original text") )) @@ -5359,6 +5396,9 @@ (dom-append _el-sel1 _el-option3) (dom-append (dom-body) _el-button) (hs-activate! _el-button) + (dom-set-prop (dom-query-by-id "sel1") "value" "c") + (dom-dispatch (dom-query-by-id "sel1") "change" nil) + (assert= (dom-get-prop (dom-query-by-id "sel1") "value") "c") (dom-dispatch _el-button "click" nil) (assert= (dom-get-prop (dom-query-by-id "sel1") "value") "b") )) @@ -5377,7 +5417,13 @@ (dom-append (dom-body) _el-input1) (dom-append (dom-body) _el-button) (hs-activate! _el-button) + (dom-set-prop (dom-query ".resettable") "value" "changed1") + (dom-dispatch (dom-query ".resettable") "input" nil) + (dom-set-prop (dom-query ".resettable") "value" "changed2") + (dom-dispatch (dom-query ".resettable") "input" nil) (dom-dispatch _el-button "click" nil) + (assert= (dom-get-prop (dom-query ".resettable") "value") "one") + (assert= (dom-get-prop (dom-query ".resettable") "value") "two") )) ) @@ -5609,7 +5655,7 @@ (dom-set-attr _el-name-input "id" "name-input") (dom-set-attr _el-name-input "type" "text") (dom-set-attr _el-name-input "value" "Alice") - (dom-set-attr _el-span "_" "bind $name and #name-input.value end then when $name changes put it into me") + (dom-set-attr _el-span "_" "bind $name and #name-input.value end when $name changes put it into me") (dom-append (dom-body) _el-name-input) (dom-append (dom-body) _el-span) (hs-activate! _el-span) @@ -5634,7 +5680,7 @@ (dom-set-attr _el-city-input "id" "city-input") (dom-set-attr _el-city-input "type" "text") (dom-set-attr _el-city-input "value" "Paris") - (dom-set-attr _el-span "_" "bind $city to #city-input.value end then when $city changes put it into me") + (dom-set-attr _el-span "_" "bind $city to #city-input.value end when $city changes put it into me") (dom-append (dom-body) _el-city-input) (dom-append (dom-body) _el-span) (hs-activate! _el-span) @@ -5642,12 +5688,14 @@ (deftest "shorthand on text input binds to value" (hs-cleanup!) (let ((_el-input (dom-create-element "input")) (_el-span (dom-create-element "span"))) - (dom-set-attr _el-input "_" "bind $greeting to me end then when $greeting changes put it into next ") + (dom-set-attr _el-input "_" "bind $greeting to me end when $greeting changes put it into next ") (dom-set-attr _el-input "type" "text") (dom-set-attr _el-input "value" "hello") (dom-append (dom-body) _el-input) (dom-append (dom-body) _el-span) (hs-activate! _el-input) + (dom-set-prop _el-input "value" "goodbye") + (dom-dispatch _el-input "input" nil) )) (deftest "shorthand on checkbox binds to checked" (hs-cleanup!) @@ -5659,6 +5707,8 @@ (dom-append (dom-body) _el-span) (hs-activate! _el-input) (hs-activate! _el-span) + (dom-set-prop _el-input "checked" true) + (dom-dispatch _el-input "change" nil) )) (deftest "shorthand on textarea binds to value" (hs-cleanup!) @@ -5670,6 +5720,8 @@ (dom-append (dom-body) _el-span) (hs-activate! _el-textarea) (hs-activate! _el-span) + (dom-set-prop _el-textarea "value" "New bio") + (dom-dispatch _el-textarea "input" nil) )) (deftest "shorthand on select binds to value" (hs-cleanup!) @@ -5689,6 +5741,8 @@ (dom-append (dom-body) _el-span) (hs-activate! _el-select) (hs-activate! _el-span) + (dom-set-prop _el-select "value" "uk") + (dom-dispatch _el-select "change" nil) )) (deftest "unsupported element: bind to plain div errors" (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) @@ -5759,6 +5813,8 @@ (dom-append (dom-body) _el-span) (hs-activate! _el-input) (hs-activate! _el-span) + (dom-set-prop _el-input "value" "user typed this") + (dom-dispatch _el-input "input" nil) )) (deftest "clicking a radio sets the variable to its value" (hs-cleanup!) @@ -5911,6 +5967,8 @@ (dom-set-attr _el-input "value" "hello") (dom-append (dom-body) _el-input) (hs-activate! _el-input) + (dom-set-prop _el-input "value" "world") + (dom-dispatch _el-input "input" nil) )) (deftest "possessive attribute: bind $var and my @data-label" (hs-cleanup!) @@ -5940,6 +5998,10 @@ (dom-append (dom-body) _el-dark-toggle) (dom-append (dom-body) _el-div) (hs-activate! _el-div) + (dom-set-prop (dom-query-by-id "dark-toggle") "checked" true) + (dom-dispatch (dom-query-by-id "dark-toggle") "change" nil) + (dom-set-prop (dom-query-by-id "dark-toggle") "checked" false) + (dom-dispatch (dom-query-by-id "dark-toggle") "change" nil) )) (deftest "attribute bound to another element input value" (hs-cleanup!) @@ -5951,6 +6013,8 @@ (dom-append (dom-body) _el-title-input) (dom-append (dom-body) _el-h1) (hs-activate! _el-h1) + (dom-set-prop (dom-query-by-id "title-input") "value" "World") + (dom-dispatch (dom-query-by-id "title-input") "input" nil) )) (deftest "two inputs synced via bind" (hs-cleanup!) @@ -6078,7 +6142,7 @@ (deftest "derives a variable from a computed expression" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "live set $total to ($price * $qty) end then when $total changes put it into me") + (dom-set-attr _el-div "_" "live set $total to ($price * $qty) end when $total changes put it into me") (dom-append (dom-body) _el-div) (hs-activate! _el-div) )) @@ -6133,7 +6197,7 @@ (dom-set-attr _el-w "_" "when $doubleWidth changes put it into me") (dom-set-attr _el-h "id" "h") (dom-set-attr _el-h "_" "when $doubleHeight changes put it into me") - (dom-set-attr _el-div "_" "live set $doubleWidth to ($width * 2) end then live set $doubleHeight to ($height * 2)") + (dom-set-attr _el-div "_" "live set $doubleWidth to ($width * 2) end live set $doubleHeight to ($height * 2)") (dom-append (dom-body) _el-w) (dom-append (dom-body) _el-h) (dom-append (dom-body) _el-div) @@ -6144,14 +6208,14 @@ (deftest "block form cascades inter-dependent commands" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "live then set $subtotal to ($price * $qty) then set $total to ($subtotal + $tax) then end then when $total changes put it into me") + (dom-set-attr _el-div "_" "live then set $subtotal to ($price * $qty) then set $total to ($subtotal + $tax) then end when $total changes put it into me") (dom-append (dom-body) _el-div) (hs-activate! _el-div) )) (deftest "toggles a class based on a boolean variable" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "live then if $isActive add .active to me else remove .active from me end then end") + (dom-set-attr _el-div "_" "live then if $isActive add .active to me else remove .active from me end end") (dom-set-inner-html _el-div "test") (dom-append (dom-body) _el-div) (hs-activate! _el-div) @@ -6159,7 +6223,7 @@ (deftest "toggles display style based on a boolean variable" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "live then if $isVisible set *display to 'block' else set *display to 'none' end then end") + (dom-set-attr _el-div "_" "live then if $isVisible set *display to 'block' else set *display to 'none' end end") (dom-set-inner-html _el-div "content") (dom-append (dom-body) _el-div) (hs-activate! _el-div) @@ -6174,28 +6238,28 @@ (deftest "conditional branch only tracks the active dependency" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "live then if $showFirst put $firstName into me else put $lastName into me end then end") + (dom-set-attr _el-div "_" "live then if $showFirst put $firstName into me else put $lastName into me end end") (dom-append (dom-body) _el-div) (hs-activate! _el-div) )) (deftest "multiple live on same element work independently" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "live set my @data-name to $firstName end then live set my @data-age to $age") + (dom-set-attr _el-div "_" "live set my @data-name to $firstName end live set my @data-age to $age") (dom-append (dom-body) _el-div) (hs-activate! _el-div) )) (deftest "live and when on same element do not interfere" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "live set my @data-status to $status end then when $status changes put 'Status: ' + it into me") + (dom-set-attr _el-div "_" "live set my @data-status to $status end when $status changes put 'Status: ' + it into me") (dom-append (dom-body) _el-div) (hs-activate! _el-div) )) (deftest "bind and live on same element do not interfere" (hs-cleanup!) (let ((_el-input (dom-create-element "input")) (_el-span (dom-create-element "span"))) - (dom-set-attr _el-input "_" "bind $username to me end then live set my @data-mirror to $username") + (dom-set-attr _el-input "_" "bind $username to me end live set my @data-mirror to $username") (dom-set-attr _el-input "type" "text") (dom-set-attr _el-input "value" "alice") (dom-set-attr _el-span "_" "when $username changes put it into me") @@ -6203,6 +6267,8 @@ (dom-append (dom-body) _el-span) (hs-activate! _el-input) (hs-activate! _el-span) + (dom-set-prop _el-input "value" "bob") + (dom-dispatch _el-input "input" nil) )) (deftest "reactive effects are stopped on cleanup" (hs-cleanup!) @@ -6365,7 +6431,7 @@ (deftest "detects changes from :element variable" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "init set :count to 0 end then when :count changes put it into me end then on click increment :count") + (dom-set-attr _el-div "_" "init set :count to 0 end when :count changes put it into me end on click increment :count") (dom-set-inner-html _el-div "0") (dom-append (dom-body) _el-div) (hs-activate! _el-div) @@ -6473,14 +6539,14 @@ (deftest "supports multiple when features on the same element" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "when $left changes put it into my @data-left end then when $right changes put it into my @data-right") + (dom-set-attr _el-div "_" "when $left changes put it into my @data-left end when $right changes put it into my @data-right") (dom-append (dom-body) _el-div) (hs-activate! _el-div) )) (deftest "works with on handlers that modify the watched variable" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "init set :label to 'initial' end then when :label changes put it into me end then on click set :label to 'clicked'") + (dom-set-attr _el-div "_" "init set :label to 'initial' end when :label changes put it into me end on click set :label to 'clicked'") (dom-set-inner-html _el-div "initial") (dom-append (dom-body) _el-div) (hs-activate! _el-div) @@ -6505,10 +6571,10 @@ (hs-cleanup!) (let ((_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "init set :value to 'A' end then when :value changes put it into me end then on click set :value to 'A-clicked'") + (dom-set-attr _el-d1 "_" "init set :value to 'A' end when :value changes put it into me end on click set :value to 'A-clicked'") (dom-set-inner-html _el-d1 "A") (dom-set-attr _el-d2 "id" "d2") - (dom-set-attr _el-d2 "_" "init set :value to 'B' end then when :value changes put it into me end then on click set :value to 'B-clicked'") + (dom-set-attr _el-d2 "_" "init set :value to 'B' end when :value changes put it into me end on click set :value to 'B-clicked'") (dom-set-inner-html _el-d2 "B") (dom-append (dom-body) _el-d1) (dom-append (dom-body) _el-d2) @@ -6555,6 +6621,8 @@ (dom-append (dom-body) _el-cb-input) (dom-append (dom-body) _el-span) (hs-activate! _el-span) + (dom-set-prop (dom-query-by-id "cb-input") "checked" true) + (dom-dispatch (dom-query-by-id "cb-input") "change" nil) )) (deftest "my @attr is tracked" (hs-cleanup!) @@ -6574,6 +6642,8 @@ (dom-append (dom-body) _el-of-input) (dom-append (dom-body) _el-span) (hs-activate! _el-span) + (dom-set-prop (dom-query-by-id "of-input") "value" "changed") + (dom-dispatch (dom-query-by-id "of-input") "input" nil) )) (deftest "math on tracked symbols works" (hs-cleanup!) @@ -7087,6 +7157,8 @@ (hs-activate! _el-div) (hs-activate! _el-input) (hs-activate! _el-output) + (dom-set-prop _el-input "value" "hello") + (dom-dispatch _el-input "input" nil) )) (deftest "derived ^var chains reactively" (hs-cleanup!) @@ -7159,15 +7231,24 @@ ;; ── evalStatically (8 tests) ── (defsuite "hs-upstream-evalStatically" (deftest "works on number literals" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (eval-hs "42") 42) + (assert= (eval-hs "3.14") 3.14) + ) (deftest "works on boolean literals" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (eval-hs "true") true) + (assert= (eval-hs "false") false) + ) (deftest "works on null literal" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (eval-hs "null") nil) + ) (deftest "works on plain string literals" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (eval-hs "\"hello\"") "hello") + (assert= (eval-hs "'world'") "world") + ) (deftest "works on time expressions" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (eval-hs "200ms") 200) + (assert= (eval-hs "2s") 2000) + ) (deftest "throws on template strings" (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) (deftest "throws on symbol references" @@ -7416,7 +7497,7 @@ (dom-set-inner-html _el-li2 "B") (dom-add-class _el-li3 "yes") (dom-set-inner-html _el-li3 "C") - (dom-set-attr _el-button "_" "on click set items to

  • in #list then set matches to items where it matches .yes then put matches mapped to its textContent into #out") + (dom-set-attr _el-button "_" "on click set items to
  • in #list set matches to items where it matches .yes then put matches mapped to its textContent into #out") (dom-set-inner-html _el-button "Go") (dom-set-attr _el-out "id" "out") (dom-append (dom-body) _el-list) @@ -7519,6 +7600,8 @@ (dom-append _el-box _el-span3) (dom-append (dom-body) _el-button) (dom-append (dom-body) _el-b2) + (dom-dispatch _el-button "click" nil) + (assert= (dom-text-content _el-button) "2") (dom-dispatch (dom-query-by-id "b2") "click" nil) (assert= (dom-text-content (dom-query-by-id "b2")) "2") )) @@ -7634,6 +7717,7 @@ (dom-append _el-td11 _el-master) (hs-activate! _el-master) (dom-dispatch (dom-query-by-id "master") "click" nil) + (dom-dispatch (dom-query ".cb") "click" nil) )) ) @@ -8076,19 +8160,26 @@ ;; ── pick (7 tests) ── (defsuite "hs-upstream-pick" (deftest "does not hang on zero-length regex matches" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (let ((haystack "a1b")) (eval-hs "pick matches of \"d*\" from haystack set window.test to it") (assert (not (nil? it)))) + ) (deftest "can pick first n items" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (let ((arr (list 10 20 30 40 50))) (eval-hs "pick first 3 of arr set $test to it") (assert= it (list 10 20 30))) + ) (deftest "can pick last n items" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (let ((arr (list 10 20 30 40 50))) (eval-hs "pick last 2 of arr set $test to it") (assert= it (list 40 50))) + ) (deftest "can pick random item" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (let ((arr (list 10 20 30))) (eval-hs "pick random of arr set $test to it") (assert (not (nil? it)))) + ) (deftest "can pick random n items" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (let ((arr (list 10 20 30 40 50))) (eval-hs "pick random 2 of arr set $test to it") (assert= (len it) 2)) + ) (deftest "can pick items using 'of' syntax" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (let ((arr (list 10 11 12 13 14 15 16))) (eval-hs "pick items 1 to 3 of arr set $test to it") (assert= it (list 11 12))) + ) (deftest "can pick match using 'of' syntax" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (eval-hs "pick match of \"d+\" of haystack set window.test to it") (list "32")) + ) ) ;; ── settle (1 tests) ── @@ -8105,6 +8196,7 @@ (dom-append (dom-body) _el-trigger) (hs-activate! _el-trigger) (dom-dispatch (dom-query-by-id "trigger") "click" nil) + (assert (dom-has-class? (dom-query ".item") "done")) )) ) diff --git a/tests/playwright/generate-sx-tests.py b/tests/playwright/generate-sx-tests.py index 381c88d4..171ced32 100644 --- a/tests/playwright/generate-sx-tests.py +++ b/tests/playwright/generate-sx-tests.py @@ -457,8 +457,8 @@ def parse_dev_body(body, elements, var_names): if line.startswith('//'): continue - # Action: find('selector').click() or .dispatchEvent('event') - m = re.search(r"find\((['\"])(.+?)\1\)\.(click|dispatchEvent)\(([^)]*)\)", line) + # Action: find('selector')[.first()/.last()].click/dispatchEvent/fill/check/uncheck/focus() + m = re.search(r"find\((['\"])(.+?)\1\)(?:\.(?:first|last)\(\))?\.(click|dispatchEvent|fill|check|uncheck|focus|selectOption)\(([^)]*)\)", line) if m and 'expect' not in line: found_first_action = True selector = m.group(2) @@ -469,15 +469,31 @@ def parse_dev_body(body, elements, var_names): ops.append(f'(dom-dispatch {target} "click" nil)') elif action_type == 'dispatchEvent': ops.append(f'(dom-dispatch {target} "{action_arg}" nil)') + elif action_type == 'fill': + escaped = action_arg.replace('\\', '\\\\').replace('"', '\\"') + ops.append(f'(dom-set-prop {target} "value" "{escaped}")') + ops.append(f'(dom-dispatch {target} "input" nil)') + elif action_type == 'check': + ops.append(f'(dom-set-prop {target} "checked" true)') + ops.append(f'(dom-dispatch {target} "change" nil)') + elif action_type == 'uncheck': + ops.append(f'(dom-set-prop {target} "checked" false)') + ops.append(f'(dom-dispatch {target} "change" nil)') + elif action_type == 'focus': + ops.append(f'(dom-focus {target})') + elif action_type == 'selectOption': + escaped = action_arg.replace('\\', '\\\\').replace('"', '\\"') + ops.append(f'(dom-set-prop {target} "value" "{escaped}")') + ops.append(f'(dom-dispatch {target} "change" nil)') continue # Skip lines before first action (pre-checks, setup) if not found_first_action: continue - # Assertion: expect(find('selector')).[not.]toHaveText("value") + # Assertion: expect(find('selector')[.first()/.last()]).[not.]toHaveText("value") m = re.search( - r"expect\(find\((['\"])(.+?)\1\)\)\.(not\.)?" + r"expect\(find\((['\"])(.+?)\1\)(?:\.(?:first|last)\(\))?\)\.(not\.)?" r"(toHaveText|toHaveClass|toHaveCSS|toHaveAttribute|toHaveValue|toBeVisible|toBeHidden|toBeChecked)" r"\(([^)]*)\)", line @@ -500,6 +516,8 @@ def parse_dev_body(body, elements, var_names): def process_hs_val(hs_val): """Process a raw HS attribute value: collapse whitespace, insert 'then' separators.""" + # Convert escaped newlines/tabs to real whitespace before stripping backslashes + hs_val = hs_val.replace('\\n', '\n').replace('\\t', ' ') hs_val = hs_val.replace('\\', '') cmd_kws = r'(?:set|put|get|add|remove|toggle|hide|show|if|repeat|for|wait|send|trigger|log|call|take|throw|return|append|tell|go|halt|settle|increment|decrement|fetch|make|install|measure|empty|reset|swap|default|morph|render|scroll|focus|select|pick|beep!)' hs_val = re.sub(r'\s{2,}(?=' + cmd_kws + r'\b)', ' then ', hs_val) @@ -507,8 +525,9 @@ def process_hs_val(hs_val): hs_val = re.sub(r'\s+', ' ', hs_val) hs_val = re.sub(r'(then\s*)+then', 'then', hs_val) hs_val = re.sub(r'\bon (\w[\w.:+-]*) then\b', r'on \1 ', hs_val) - hs_val = re.sub(r'(\bin \[.*?\]) then\b', r'\1 ', hs_val) + hs_val = re.sub(r'(\bin (?:\[.*?\]|\S+)) then\b', r'\1 ', hs_val) hs_val = re.sub(r'\btimes then\b', 'times ', hs_val) + hs_val = re.sub(r'\bend then\b', 'end ', hs_val) return hs_val.strip() @@ -740,6 +759,65 @@ def generate_eval_only_test(test, idx): expected_sx = js_val_to_sx(m.group(1)) assertions.append(f' (assert= (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]}}); + # const result = await evaluate(() => window.$test); + # expect(result).toEqual([1,2,3]); + if not assertions: + run_match = re.search( + r'(?:await\s+)?run\((?:String\.raw)?(' + _Q + r')(.+?)\1\s*,\s*\{locals:\s*\{(.*?)\}\}', + body, re.DOTALL + ) + if run_match: + hs_expr = extract_hs_expr(run_match.group(2)) + locals_str = run_match.group(3).strip() + # Parse locals: {key: val, ...} + local_bindings = [] + for lm in re.finditer(r'(\w+)\s*:\s*(.+?)(?:,\s*(?=\w+\s*:)|$)', locals_str): + lname = lm.group(1) + lval = js_val_to_sx(lm.group(2).strip().rstrip(',')) + local_bindings.append(f'({lname} {lval})') + + # Find expect().toBe() or .toEqual() + for m in re.finditer(r'expect\([^)]*\)\.toBe\(([^)]+)\)', body): + expected_sx = js_val_to_sx(m.group(1)) + if local_bindings: + assertions.append(f' (let ({" ".join(local_bindings)}) (eval-hs "{hs_expr}") (assert= it {expected_sx}))') + else: + assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})') + for m in re.finditer(r'expect\([^)]*\)\.toEqual\((\[.*?\])\)', body, re.DOTALL): + expected_sx = js_val_to_sx(m.group(1)) + if local_bindings: + assertions.append(f' (let ({" ".join(local_bindings)}) (eval-hs "{hs_expr}") (assert= it {expected_sx}))') + else: + assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})') + for m in re.finditer(r'expect\([^)]*\)\.toContain\(([^)]+)\)', body): + expected_sx = js_val_to_sx(m.group(1)) + if local_bindings: + assertions.append(f' (let ({" ".join(local_bindings)}) (eval-hs "{hs_expr}") (assert (not (nil? it))))') + else: + assertions.append(f' (assert (not (nil? (eval-hs "{hs_expr}"))))') + for m in re.finditer(r'expect\([^)]*\)\.toHaveLength\((\d+)\)', body): + length = m.group(1) + if local_bindings: + assertions.append(f' (let ({" ".join(local_bindings)}) (eval-hs "{hs_expr}") (assert= (len it) {length}))') + else: + assertions.append(f' (assert= (len (eval-hs "{hs_expr}")) {length})') + + # Pattern 2c: evaluate(() => _hyperscript.parse("expr").evalStatically()).toBe(val) + if not assertions: + for m in re.finditer( + r'evaluate\(\(\)\s*=>\s*_hyperscript\.parse\((["\x27])(.+?)\1\)\.evalStatically\(\)\)', + body + ): + hs_expr = extract_hs_expr(m.group(2)) + # Find corresponding .toBe() + rest = body[m.end():] + be_match = re.search(r'\.toBe\(([^)]+)\)', rest) + if be_match: + expected_sx = js_val_to_sx(be_match.group(1)) + assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})') + # Pattern 3: toThrow — expect(() => run("expr")).toThrow() for m in re.finditer( r'run\((?:String\.raw)?(["\x27`])(.+?)\1\).*?\.toThrow\(\)',