From d6137f0d6f376f842b63b7d7b24b176ccf6ff63a Mon Sep 17 00:00:00 2001 From: giles Date: Thu, 23 Apr 2026 17:15:40 +0000 Subject: [PATCH] HS reset/first/last: defaultValue tracking, list-of-elements reset, find().first|last MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mock DOM: - El now tracks defaultValue/defaultChecked/defaultSelected and a reset() method that walks descendant form controls, restoring them. - setAttribute(value|checked|selected) sets the matching default-* too, so the initial HTML state can be restored later. - parseHTMLFragments + _setInnerHTML capture a textarea's textContent as its value AND defaultValue. Generator (pw-body): - add_action / add_assertion extract .first() / .last() / .nth(N) modifiers into (nth (dom-query-all …) i) or a (let ((_all …)) (nth _all (- … 1))) tail so multi-match helpers hit the right element. Compiler: - emit-reset! with a ./. query target now compiles to hs-query-all so 'reset .resettable' resets every matching control (not just the first). Net: reset 1→8 (100%). --- lib/hyperscript/compiler.sx | 9 +- shared/static/wasm/sx/hs-compiler.sx | 9 +- spec/tests/test-hyperscript-behavioral.sx | 156 +++++++++++----------- tests/hs-run-filtered.js | 42 +++++- tests/playwright/generate-sx-tests.py | 28 ++-- 5 files changed, 153 insertions(+), 91 deletions(-) diff --git a/lib/hyperscript/compiler.sx b/lib/hyperscript/compiler.sx index 4d6d3e35..fa3d3dab 100644 --- a/lib/hyperscript/compiler.sx +++ b/lib/hyperscript/compiler.sx @@ -1835,7 +1835,14 @@ ((= head (quote select!)) (list (quote hs-select!) (hs-to-sx (nth ast 1)))) ((= head (quote reset!)) - (list (quote hs-reset!) (hs-to-sx (nth ast 1)))) + (let + ((raw-tgt (nth ast 1))) + (cond + ((and (list? raw-tgt) (= (first raw-tgt) (quote query))) + (list + (quote hs-reset!) + (list (quote hs-query-all) (nth raw-tgt 1)))) + (true (list (quote hs-reset!) (hs-to-sx raw-tgt)))))) ((= head (quote default!)) (let ((tgt-ast (nth ast 1)) diff --git a/shared/static/wasm/sx/hs-compiler.sx b/shared/static/wasm/sx/hs-compiler.sx index 4d6d3e35..fa3d3dab 100644 --- a/shared/static/wasm/sx/hs-compiler.sx +++ b/shared/static/wasm/sx/hs-compiler.sx @@ -1835,7 +1835,14 @@ ((= head (quote select!)) (list (quote hs-select!) (hs-to-sx (nth ast 1)))) ((= head (quote reset!)) - (list (quote hs-reset!) (hs-to-sx (nth ast 1)))) + (let + ((raw-tgt (nth ast 1))) + (cond + ((and (list? raw-tgt) (= (first raw-tgt) (quote query))) + (list + (quote hs-reset!) + (list (quote hs-query-all) (nth raw-tgt 1)))) + (true (list (quote hs-reset!) (hs-to-sx raw-tgt)))))) ((= head (quote default!)) (let ((tgt-ast (nth ast 1)) diff --git a/spec/tests/test-hyperscript-behavioral.sx b/spec/tests/test-hyperscript-behavioral.sx index edfdda7d..bff21ad6 100644 --- a/spec/tests/test-hyperscript-behavioral.sx +++ b/spec/tests/test-hyperscript-behavioral.sx @@ -1263,7 +1263,7 @@ (hs-activate! _el-div) (hs-activate! _el-bar) (assert (not (dom-has-class? (dom-query-by-id "bar") "foo-sent"))) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert (dom-has-class? (dom-query-by-id "bar") "foo-sent")) )) (deftest "can send events with args" @@ -1276,7 +1276,7 @@ (dom-append (dom-body) _el-bar) (hs-activate! _el-div) (hs-activate! _el-bar) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-text-content (dom-query-by-id "bar")) "42") )) (deftest "can set properties" @@ -1312,7 +1312,7 @@ (dom-append (dom-body) _el-div2) (hs-activate! _el-div1) (dom-dispatch (nth (dom-query-all (dom-body) ".divs") 1) "click" nil) - (assert (not (dom-has-class? (dom-query ".divs") "foo"))) + (assert (not (dom-has-class? (nth (dom-query-all (dom-body) ".divs") 0) "foo"))) (assert (dom-has-class? (nth (dom-query-all (dom-body) ".divs") 1) "foo")) (assert (not (dom-has-class? (nth (dom-query-all (dom-body) ".divs") 2) "foo"))) )) @@ -2050,7 +2050,7 @@ (dom-append _el-div _el-d1) (hs-activate! _el-d1) (assert= (dom-text-content (dom-query-by-id "d1")) "") - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-text-content (dom-query-by-id "d1")) "Foo") )) (deftest "properly interpolates values" @@ -3130,8 +3130,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")) "") + (assert= (dom-text-content (nth (dom-query-all (dom-body) ".clearme") 0)) "") + (assert= (dom-text-content (let ((_all (dom-query-all (dom-body) ".clearme"))) (nth _all (- (len _all) 1)))) "") )) (deftest "clear is an alias for empty" (hs-cleanup!) @@ -4123,7 +4123,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) + (dom-dispatch (nth (dom-query-all (dom-body) ".cb") 0) "click" nil) )) (deftest "joined by on null returns null" (eval-hs "set x to null then return x joined by ','") @@ -4207,8 +4207,8 @@ (dom-append (dom-body) _el-b2) (hs-activate! _el-button) (hs-activate! _el-b2) - (dom-dispatch _el-button "click" nil) - (assert= (dom-text-content _el-button) "2") + (dom-dispatch (nth (dom-query-all (dom-body) "button") 0) "click" nil) + (assert= (dom-text-content (nth (dom-query-all (dom-body) "button") 0)) "2") (dom-dispatch (dom-query-by-id "b2") "click" nil) (assert= (dom-text-content (dom-query-by-id "b2")) "2") )) @@ -9433,7 +9433,7 @@ (dom-append (dom-body) _el-d2) (dom-append _el-d2 _el-div4) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-text-content (dom-query-by-id "d1")) "foo") (assert= (dom-text-content (dom-query-by-id "d2")) "foo") )) @@ -9451,7 +9451,7 @@ (dom-append (dom-body) _el-d2) (dom-append _el-d2 _el-div4) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-text-content (dom-query-by-id "d1")) "foo") (assert= (dom-text-content (dom-query-by-id "d2")) "foo") )) @@ -9484,7 +9484,7 @@ (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-div2) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-attr (dom-query-by-id "div2") "bar") "foo") )) (deftest "can set into indirect attribute ref 3" @@ -9496,7 +9496,7 @@ (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-div2) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-attr (dom-query-by-id "div2") "bar") "foo") )) (deftest "can set into indirect style ref" @@ -9518,7 +9518,7 @@ (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-div2) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "div2") "color") "red") )) (deftest "can set into indirect style ref 3" @@ -9530,7 +9530,7 @@ (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-div2) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "div2") "color") "red") )) (deftest "can set into style ref" @@ -9869,7 +9869,7 @@ (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-that) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) )) (deftest "can remove parent element" (hs-cleanup!) @@ -10369,13 +10369,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-set-prop (nth (dom-query-all (dom-body) ".resettable") 0) "value" "changed1") + (dom-dispatch (nth (dom-query-all (dom-body) ".resettable") 0) "input" nil) + (dom-set-prop (let ((_all (dom-query-all (dom-body) ".resettable"))) (nth _all (- (len _all) 1))) "value" "changed2") + (dom-dispatch (let ((_all (dom-query-all (dom-body) ".resettable"))) (nth _all (- (len _all) 1))) "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") + (assert= (dom-get-prop (nth (dom-query-all (dom-body) ".resettable") 0) "value") "one") + (assert= (dom-get-prop (let ((_all (dom-query-all (dom-body) ".resettable"))) (nth _all (- (len _all) 1))) "value") "two") )) (deftest "reset with no target resets me (form)" (hs-cleanup!) @@ -10612,8 +10612,8 @@ (dom-append (dom-body) _el-bar) (hs-activate! _el-div) (hs-activate! _el-bar) - (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-div "foo-sent")) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) + (assert (dom-has-class? (nth (dom-query-all (dom-body) "div") 0) "foo-sent")) )) (deftest "can send events" (hs-cleanup!) @@ -10626,7 +10626,7 @@ (hs-activate! _el-div) (hs-activate! _el-bar) (assert (not (dom-has-class? (dom-query-by-id "bar") "foo-sent"))) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert (dom-has-class? (dom-query-by-id "bar") "foo-sent")) )) (deftest "can send events to any expression" @@ -10640,7 +10640,7 @@ (hs-activate! _el-div) (hs-activate! _el-bar) (assert (not (dom-has-class? (dom-query-by-id "bar") "foo-sent"))) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert (dom-has-class? (dom-query-by-id "bar") "foo-sent")) )) (deftest "can send events with args" @@ -10653,7 +10653,7 @@ (dom-append (dom-body) _el-bar) (hs-activate! _el-div) (hs-activate! _el-bar) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-text-content (dom-query-by-id "bar")) "42") )) (deftest "can send events with colons" @@ -10667,7 +10667,7 @@ (hs-activate! _el-div) (hs-activate! _el-bar) (assert (not (dom-has-class? (dom-query-by-id "bar") "foo-sent"))) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert (dom-has-class? (dom-query-by-id "bar") "foo-sent")) )) (deftest "can send events with colons with args" @@ -10680,7 +10680,7 @@ (dom-append (dom-body) _el-bar) (hs-activate! _el-div) (hs-activate! _el-bar) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-text-content (dom-query-by-id "bar")) "42") )) (deftest "can send events with dots" @@ -10694,7 +10694,7 @@ (hs-activate! _el-div) (hs-activate! _el-bar) (assert (not (dom-has-class? (dom-query-by-id "bar") "foo-sent"))) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert (dom-has-class? (dom-query-by-id "bar") "foo-sent")) )) (deftest "can send events with dots with args" @@ -10707,7 +10707,7 @@ (dom-append (dom-body) _el-bar) (hs-activate! _el-div) (hs-activate! _el-bar) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-text-content (dom-query-by-id "bar")) "42") )) ) @@ -10767,8 +10767,8 @@ (dom-append (dom-body) _el-div) (dom-append _el-div _el-d1) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "foo") + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) + (assert= (dom-text-content (nth (dom-query-all (dom-body) "div") 0)) "foo") )) (deftest "can set complex indirect properties lhs" (hs-cleanup!) @@ -10778,8 +10778,8 @@ (dom-append (dom-body) _el-div) (dom-append _el-div _el-d1) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "foo") + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) + (assert= (dom-text-content (nth (dom-query-all (dom-body) "div") 0)) "foo") )) (deftest "can set complex indirect properties rhs" (hs-cleanup!) @@ -10789,8 +10789,8 @@ (dom-append (dom-body) _el-div) (dom-append _el-div _el-d1) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "foo") + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) + (assert= (dom-text-content (nth (dom-query-all (dom-body) "div") 0)) "foo") )) (deftest "can set indirect properties" (hs-cleanup!) @@ -10821,8 +10821,8 @@ (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-div1) (hs-activate! _el-div) - (dom-dispatch (dom-query ".divs") "click" nil) - (assert= (dom-text-content (dom-query ".divs")) "foo") + (dom-dispatch (nth (dom-query-all (dom-body) ".divs") 0) "click" nil) + (assert= (dom-text-content (nth (dom-query-all (dom-body) ".divs") 0)) "foo") (assert= (dom-text-content (nth (dom-query-all (dom-body) ".divs") 1)) "foo") )) (deftest "can set into id ref" @@ -10844,7 +10844,7 @@ (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-div2) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-attr (dom-query-by-id "div2") "bar") "foo") )) (deftest "can set into indirect attribute ref 2" @@ -10856,7 +10856,7 @@ (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-div2) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-attr (dom-query-by-id "div2") "bar") "foo") )) (deftest "can set into indirect attribute ref 3" @@ -10868,7 +10868,7 @@ (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-div2) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-attr (dom-query-by-id "div2") "bar") "foo") )) (deftest "can set into indirect style ref" @@ -10880,7 +10880,7 @@ (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-div2) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "div2") "color") "red") )) (deftest "can set into indirect style ref 2" @@ -10892,7 +10892,7 @@ (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-div2) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "div2") "color") "red") )) (deftest "can set into indirect style ref 3" @@ -10904,7 +10904,7 @@ (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-div2) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "div2") "color") "red") )) (deftest "can set into style ref" @@ -11042,7 +11042,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")) + (assert (dom-has-class? (nth (dom-query-all (dom-body) ".item") 0) "done")) (assert (dom-has-class? (nth (dom-query-all (dom-body) ".item") 1) "done")) )) (deftest "can settle me no transition" @@ -11209,7 +11209,7 @@ (hs-activate! _el-div) (assert (not (dom-visible? (dom-query-by-id "d1")))) (assert (not (dom-visible? (dom-query-by-id "d2")))) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "d1") "display") "inline-block") (assert= (dom-get-style (dom-query-by-id "d2") "display") "inline-block") )) @@ -11227,7 +11227,7 @@ (hs-activate! _el-div) (assert (not (dom-visible? (dom-query-by-id "d1")))) (assert (not (dom-visible? (dom-query-by-id "d2")))) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "d1") "display") "inline-block") (assert= (dom-get-style (dom-query-by-id "d2") "display") "inline-block") )) @@ -11479,8 +11479,8 @@ (dom-append (dom-body) _el-div2) (hs-activate! _el-div1) (dom-dispatch (nth (dom-query-all (dom-body) "div") 1) "click" nil) - (assert (dom-has-class? _el-div "unselected")) - (assert (not (dom-has-class? _el-div "selected"))) + (assert (dom-has-class? (nth (dom-query-all (dom-body) "div") 0) "unselected")) + (assert (not (dom-has-class? (nth (dom-query-all (dom-body) "div") 0) "selected"))) (assert (dom-has-class? (nth (dom-query-all (dom-body) "div") 1) "selected")) (assert (not (dom-has-class? (nth (dom-query-all (dom-body) "div") 1) "unselected"))) (assert (dom-has-class? (nth (dom-query-all (dom-body) "div") 2) "unselected")) @@ -11498,8 +11498,8 @@ (dom-append (dom-body) _el-div2) (hs-activate! _el-div1) (dom-dispatch (nth (dom-query-all (dom-body) "div") 1) "click" nil) - (assert (not (dom-has-class? _el-div "selected"))) - (assert (dom-has-class? _el-div "unselected")) + (assert (not (dom-has-class? (nth (dom-query-all (dom-body) "div") 0) "selected"))) + (assert (dom-has-class? (nth (dom-query-all (dom-body) "div") 0) "unselected")) (assert (dom-has-class? (nth (dom-query-all (dom-body) "div") 1) "selected")) (assert (not (dom-has-class? (nth (dom-query-all (dom-body) "div") 1) "unselected"))) (assert (dom-has-class? (nth (dom-query-all (dom-body) "div") 2) "unselected")) @@ -11518,7 +11518,7 @@ (dom-append (dom-body) _el-d3) (hs-activate! _el-div1) (dom-dispatch (nth (dom-query-all (dom-body) "div") 1) "click" nil) - (assert (not (dom-has-class? _el-div "foo"))) + (assert (not (dom-has-class? (nth (dom-query-all (dom-body) "div") 0) "foo"))) (assert (not (dom-has-class? (nth (dom-query-all (dom-body) "div") 1) "foo"))) (assert (dom-has-class? (dom-query-by-id "d3") "foo")) )) @@ -11535,7 +11535,7 @@ (dom-append (dom-body) _el-div2) (hs-activate! _el-div1) (dom-dispatch (nth (dom-query-all (dom-body) "div") 1) "click" nil) - (assert (not (dom-has-class? _el-div "foo"))) + (assert (not (dom-has-class? (nth (dom-query-all (dom-body) "div") 0) "foo"))) (assert (dom-has-class? (nth (dom-query-all (dom-body) "div") 1) "foo")) (assert (not (dom-has-class? (nth (dom-query-all (dom-body) "div") 2) "foo"))) )) @@ -11552,7 +11552,7 @@ (dom-append (dom-body) _el-form2) (hs-activate! _el-form1) (dom-dispatch (nth (dom-query-all (dom-body) "form") 1) "click" nil) - (assert (not (dom-has-class? _el-form "foo"))) + (assert (not (dom-has-class? (nth (dom-query-all (dom-body) "form") 0) "foo"))) (assert (dom-has-class? (nth (dom-query-all (dom-body) "form") 1) "foo")) (assert (not (dom-has-class? (nth (dom-query-all (dom-body) "form") 2) "foo"))) )) @@ -11570,7 +11570,7 @@ (dom-append (dom-body) _el-d3) (hs-activate! _el-div1) (dom-dispatch (nth (dom-query-all (dom-body) "div") 1) "click" nil) - (assert (not (dom-has-attr? _el-div "data-foo"))) + (assert (not (dom-has-attr? (nth (dom-query-all (dom-body) "div") 0) "data-foo"))) (assert (not (dom-has-attr? (nth (dom-query-all (dom-body) "div") 1) "data-foo"))) (assert= (dom-get-attr (dom-query-by-id "d3") "data-foo") "") )) @@ -11586,9 +11586,9 @@ (dom-append (dom-body) _el-div1) (dom-append (dom-body) _el-div2) (hs-activate! _el-div1) - (assert= (dom-get-attr _el-div "data-foo") "bar") + (assert= (dom-get-attr (nth (dom-query-all (dom-body) "div") 0) "data-foo") "bar") (dom-dispatch (nth (dom-query-all (dom-body) "div") 1) "click" nil) - (assert (not (dom-has-attr? _el-div "data-foo"))) + (assert (not (dom-has-attr? (nth (dom-query-all (dom-body) "div") 0) "data-foo"))) (assert= (dom-get-attr (nth (dom-query-all (dom-body) "div") 1) "data-foo") "") (assert (not (dom-has-attr? (nth (dom-query-all (dom-body) "div") 2) "data-foo"))) )) @@ -11605,7 +11605,7 @@ (dom-append (dom-body) _el-div2) (hs-activate! _el-div1) (dom-dispatch (nth (dom-query-all (dom-body) "div") 1) "click" nil) - (assert= (dom-get-attr _el-div "data-foo") "qux") + (assert= (dom-get-attr (nth (dom-query-all (dom-body) "div") 0) "data-foo") "qux") (assert= (dom-get-attr (nth (dom-query-all (dom-body) "div") 1) "data-foo") "baz") (assert= (dom-get-attr (nth (dom-query-all (dom-body) "div") 2) "data-foo") "qux") )) @@ -11623,7 +11623,7 @@ (dom-append (dom-body) _el-div2) (hs-activate! _el-div1) (dom-dispatch (nth (dom-query-all (dom-body) "div") 1) "click" nil) - (assert= (dom-get-attr _el-div "data-foo") "qux") + (assert= (dom-get-attr (nth (dom-query-all (dom-body) "div") 0) "data-foo") "qux") (assert= (dom-get-attr (nth (dom-query-all (dom-body) "div") 1) "data-foo") "baz") (assert= (dom-get-attr (nth (dom-query-all (dom-body) "div") 2) "data-foo") "qux") )) @@ -11639,9 +11639,9 @@ (dom-append (dom-body) _el-div1) (dom-append (dom-body) _el-div2) (hs-activate! _el-div1) - (assert= (dom-get-attr _el-div "data-foo") "bar") + (assert= (dom-get-attr (nth (dom-query-all (dom-body) "div") 0) "data-foo") "bar") (dom-dispatch (nth (dom-query-all (dom-body) "div") 1) "click" nil) - (assert (not (dom-has-attr? _el-div "data-foo"))) + (assert (not (dom-has-attr? (nth (dom-query-all (dom-body) "div") 0) "data-foo"))) (assert= (dom-get-attr (nth (dom-query-all (dom-body) "div") 1) "data-foo") "baz") (assert (not (dom-has-attr? (nth (dom-query-all (dom-body) "div") 2) "data-foo"))) )) @@ -11659,7 +11659,7 @@ (dom-append (dom-body) _el-div2) (hs-activate! _el-div1) (dom-dispatch (nth (dom-query-all (dom-body) "div") 1) "click" nil) - (assert (not (dom-has-class? _el-div "foo"))) + (assert (not (dom-has-class? (nth (dom-query-all (dom-body) "div") 0) "foo"))) (assert (dom-has-class? (nth (dom-query-all (dom-body) "div") 1) "foo")) (assert (dom-has-class? (nth (dom-query-all (dom-body) "div") 1) "bar")) (assert (not (dom-has-class? (nth (dom-query-all (dom-body) "div") 2) "bar"))) @@ -11679,8 +11679,8 @@ (dom-append (dom-body) _el-div2) (hs-activate! _el-div1) (dom-dispatch (nth (dom-query-all (dom-body) "div") 1) "click" nil) - (assert (not (dom-has-class? _el-div "foo"))) - (assert (not (dom-has-class? _el-div "bar"))) + (assert (not (dom-has-class? (nth (dom-query-all (dom-body) "div") 0) "foo"))) + (assert (not (dom-has-class? (nth (dom-query-all (dom-body) "div") 0) "bar"))) (assert (dom-has-class? (nth (dom-query-all (dom-body) "div") 1) "foo")) (assert (dom-has-class? (nth (dom-query-all (dom-body) "div") 1) "bar")) (assert (dom-has-class? (nth (dom-query-all (dom-body) "div") 2) "bar")) @@ -11698,7 +11698,7 @@ (dom-append (dom-body) _el-div2) (hs-activate! _el-div1) (dom-dispatch (nth (dom-query-all (dom-body) "div") 1) "click" nil) - (assert= (dom-get-attr _el-div "data-foo") "qux") + (assert= (dom-get-attr (nth (dom-query-all (dom-body) "div") 0) "data-foo") "qux") (assert= (dom-get-attr (nth (dom-query-all (dom-body) "div") 1) "data-foo") "baz") (assert= (dom-get-attr (nth (dom-query-all (dom-body) "div") 2) "data-foo") "qux") )) @@ -12499,9 +12499,9 @@ end") (dom-append (dom-body) _el-d2) (hs-activate! _el-div) (assert= (dom-get-style (dom-query-by-id "d2") "display") "block") - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "d2") "display") "none") - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "d2") "display") "block") )) (deftest "can toggle display w/ my" @@ -12588,9 +12588,9 @@ end") (dom-append (dom-body) _el-d2) (hs-activate! _el-div) (assert= (dom-get-style (dom-query-by-id "d2") "opacity") "1") - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "d2") "opacity") "0") - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "d2") "opacity") "1") )) (deftest "can toggle opacity w/ my" @@ -12640,9 +12640,9 @@ end") (dom-append (dom-body) _el-d2) (hs-activate! _el-div) (assert= (dom-get-style (dom-query-by-id "d2") "visibility") "visible") - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "d2") "visibility") "hidden") - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "d2") "visibility") "visible") )) (deftest "can toggle visibility w/ my" @@ -12723,7 +12723,7 @@ end") (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-foo) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "foo") "width") "100px") )) (deftest "can transition on another element with it" @@ -12734,7 +12734,7 @@ end") (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-foo) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "foo") "width") "100px") )) (deftest "can transition on another element with of syntax" @@ -12745,7 +12745,7 @@ end") (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-foo) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "foo") "width") "100px") )) (deftest "can transition on another element with possessive" @@ -12756,7 +12756,7 @@ end") (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-foo) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "foo") "width") "100px") )) (deftest "can transition on query ref with of syntax" @@ -12788,7 +12788,7 @@ end") (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-foo) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "foo") "width") "100px") )) (deftest "can transition with a custom transition time via the over syntax" @@ -12799,7 +12799,7 @@ end") (dom-append (dom-body) _el-div) (dom-append (dom-body) _el-foo) (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) + (dom-dispatch (nth (dom-query-all (dom-body) "div") 0) "click" nil) (assert= (dom-get-style (dom-query-by-id "foo") "width") "100px") )) (deftest "can transition with parameterized values" diff --git a/tests/hs-run-filtered.js b/tests/hs-run-filtered.js index 9a39fcdb..93a8992d 100755 --- a/tests/hs-run-filtered.js +++ b/tests/hs-run-filtered.js @@ -33,8 +33,8 @@ function mkStyle(tag) { return s; } class El { - constructor(t) { this.tagName=t.toUpperCase(); this.nodeName=this.tagName; this.nodeType=1; this.id=''; this.className=''; this.classList=new CL(this); this.style=mkStyle(this.tagName); this.attributes={}; this.children=[]; this.childNodes=[]; this.childNodes.item=function(i){return this[i]||null;}; this.parentElement=null; this.parentNode=null; this.textContent=''; this.innerHTML=''; this._listeners={}; this.dataset={}; this.open=false; this.value=''; this.checked=false; this.disabled=false; this.type=''; this.name=''; this.selectedIndex=-1; this.options=[]; } - setAttribute(n,v) { this.attributes[n]=String(v); if(n==='id')this.id=v; if(n==='class'){this.className=v;this.classList._sync(v);} if(n==='value')this.value=v; if(n==='name')this.name=v; if(n==='type')this.type=v; if(n==='checked')this.checked=true; if(n==='selected')this.selected=true; if(n==='multiple')this.multiple=true; if(n==='disabled')this.disabled=true; if(n==='style'){const s=String(v);for(const d of s.split(';')){const c=d.indexOf(':');if(c>0){const k=d.slice(0,c).trim();const val=d.slice(c+1).trim();if(k)this.style.setProperty(k,val);}} } } + constructor(t) { this.tagName=t.toUpperCase(); this.nodeName=this.tagName; this.nodeType=1; this.id=''; this.className=''; this.classList=new CL(this); this.style=mkStyle(this.tagName); this.attributes={}; this.children=[]; this.childNodes=[]; this.childNodes.item=function(i){return this[i]||null;}; this.parentElement=null; this.parentNode=null; this.textContent=''; this.innerHTML=''; this._listeners={}; this.dataset={}; this.open=false; this.value=''; this.defaultValue=''; this.checked=false; this.defaultChecked=false; this.disabled=false; this.type=''; this.name=''; this.selectedIndex=-1; this.defaultSelected=false; this.selected=false; this.options=[]; } + setAttribute(n,v) { this.attributes[n]=String(v); if(n==='id')this.id=v; if(n==='class'){this.className=v;this.classList._sync(v);} if(n==='value'){this.value=v;this.defaultValue=v;} if(n==='name')this.name=v; if(n==='type')this.type=v; if(n==='checked'){this.checked=true;this.defaultChecked=true;} if(n==='selected'){this.selected=true;this.defaultSelected=true;} if(n==='multiple')this.multiple=true; if(n==='disabled')this.disabled=true; if(n==='style'){const s=String(v);for(const d of s.split(';')){const c=d.indexOf(':');if(c>0){const k=d.slice(0,c).trim();const val=d.slice(c+1).trim();if(k)this.style.setProperty(k,val);}} } } getAttribute(n) { return this.attributes[n]!==undefined?this.attributes[n]:null; } removeAttribute(n) { delete this.attributes[n]; if(n==='disabled')this.disabled=false; } hasAttribute(n) { return n in this.attributes; } @@ -52,6 +52,28 @@ class El { contains(o) { if(o===this)return true; for(const c of this.children)if(c===o||c.contains(o))return true; return false; } cloneNode(d) { const e=new El(this.tagName.toLowerCase()); Object.assign(e.attributes,this.attributes); e.id=this.id; e.className=this.className; e.classList._sync(this.className); for(const k of Object.keys(this.style)){if(typeof this.style[k]!=='function')e.style[k]=this.style[k];} e.textContent=this.textContent; e.innerHTML=this.innerHTML; e.value=this.value; if(d)for(const c of this.children)e.appendChild(c.cloneNode(true)); return e; } focus(){} blur(){} click(){this.dispatchEvent(new Ev('click',{bubbles:true}));} remove(){if(this.parentElement)this.parentElement.removeChild(this);} + reset(){ + // Form reset: walk descendants, restore defaultValue / defaultChecked / + // defaultSelected. + const walk = (el) => { + for (const c of (el.children || [])) { + const tag = c.tagName; + if (tag === 'INPUT' || tag === 'TEXTAREA') { + if (c.type === 'checkbox' || c.type === 'radio') c.checked = !!c.defaultChecked; + else c.value = c.defaultValue || ''; + } else if (tag === 'OPTION') { + c.selected = !!c.defaultSelected; + } else if (tag === 'SELECT') { + for (const o of (c.options || [])) o.selected = !!o.defaultSelected; + const first = (c.options || []).findIndex(o => o.selected); + c.selectedIndex = first >= 0 ? first : 0; + c.value = first >= 0 && c.options[first] ? (c.options[first].value || '') : ''; + } + walk(c); + } + }; + walk(this); + } _syncText() { // Sync textContent from children const t = this.children.map(c => c.textContent || '').join(''); @@ -70,6 +92,12 @@ class El { } else { this.textContent = ''; } + // Textarea: its value is its textContent. Capture defaultValue on first + // set so later reset() can restore. + if (this.tagName === 'TEXTAREA') { + this.value = this.textContent; + if (!('_origDefault' in this)) { this.defaultValue = this.textContent; this._origDefault = true; } + } } get firstElementChild() { return this.children[0]||null; } get lastElementChild() { return this.children[this.children.length-1]||null; } @@ -159,6 +187,16 @@ function parseHTMLFragments(html) { el.textContent = inner; } el.innerHTML = inner; + // Textarea: its "value" comes from inner text, not an attr. + if (el.tagName === 'TEXTAREA') { + el.value = inner; + el.defaultValue = inner; + } + // Option: textContent is the label; if no value attr, it defaults to + // the label. Track defaultSelected separately from runtime selected. + if (el.tagName === 'OPTION' && !el.attributes.value) { + el.value = inner.trim(); + } } results.push(el); lastIndex = re.lastIndex; diff --git a/tests/playwright/generate-sx-tests.py b/tests/playwright/generate-sx-tests.py index 5ecd9697..42c94325 100644 --- a/tests/playwright/generate-sx-tests.py +++ b/tests/playwright/generate-sx-tests.py @@ -800,19 +800,24 @@ def parse_dev_body(body, elements, var_names): def add_action(stmt): am = re.search( - r"find\((['\"])(.+?)\1\)(?:\.(?:first|last)\(\)|\.nth\((\d+)\))?" + r"find\((['\"])(.+?)\1\)(?:\.(first|last)\(\)|\.nth\((\d+)\))?" r"\.(click|dispatchEvent|fill|check|uncheck|focus|selectOption)\(([^)]*)\)", stmt, ) if not am or 'expect' in stmt: return False selector = am.group(2) - nth_idx = am.group(3) - action_type = am.group(4) - action_arg = am.group(5).strip("'\"") + first_last = am.group(3) + nth_idx = am.group(4) + action_type = am.group(5) + action_arg = am.group(6).strip("'\"") target = selector_to_sx(selector, elements, var_names) if nth_idx is not None: target = f'(nth (dom-query-all (dom-body) "{selector}") {nth_idx})' + elif first_last == 'last': + target = f'(let ((_all (dom-query-all (dom-body) "{selector}"))) (nth _all (- (len _all) 1)))' + elif first_last == 'first': + target = f'(nth (dom-query-all (dom-body) "{selector}") 0)' if action_type == 'click': ops.append(f'(dom-dispatch {target} "click" nil)') elif action_type == 'dispatchEvent': @@ -837,7 +842,7 @@ def parse_dev_body(body, elements, var_names): def add_assertion(stmt): em = re.search( - r"expect\(find\((['\"])(.+?)\1\)(?:\.(?:first|last)\(\)|\.nth\((\d+)\))?\)\.(not\.)?" + r"expect\(find\((['\"])(.+?)\1\)(?:\.(first|last)\(\)|\.nth\((\d+)\))?\)\.(not\.)?" r"(toHaveText|toHaveClass|toHaveCSS|toHaveAttribute|toHaveValue|toBeVisible|toBeHidden|toBeChecked)" r"\(((?:[^()]|\([^()]*\))*)\)", stmt, @@ -845,13 +850,18 @@ def parse_dev_body(body, elements, var_names): if not em: return False selector = em.group(2) - nth_idx = em.group(3) - negated = bool(em.group(4)) - assert_type = em.group(5) - args_str = em.group(6) + first_last = em.group(3) + nth_idx = em.group(4) + negated = bool(em.group(5)) + assert_type = em.group(6) + args_str = em.group(7) target = selector_to_sx(selector, elements, var_names) if nth_idx is not None: target = f'(nth (dom-query-all (dom-body) "{selector}") {nth_idx})' + elif first_last == 'last': + target = f'(let ((_all (dom-query-all (dom-body) "{selector}"))) (nth _all (- (len _all) 1)))' + elif first_last == 'first': + target = f'(nth (dom-query-all (dom-body) "{selector}") 0)' sx = pw_assertion_to_sx(target, negated, assert_type, args_str) if sx: ops.append(sx)