HS take command: class/attr with+giving, attr removal from scope, giving keyword

- tokenizer: add 'giving' as keyword so parse-take-cmd can detect it.
- parser.sx parse-take-cmd: loop over 'with <class>' / 'giving <class>' /
  'from <sel>' / 'for <tgt>' clauses in any order for both the class and
  attribute cases. Emits uniform (take! kind name from-sel for-tgt
  attr-val with-val) 7-slot AST.
- compiler emit-take: pass with-cls for the class case through to runtime.
- runtime hs-take!: with a class 'with' replacement, toggle both classes
  across scope + target. For attribute take, always strip the attr from
  the scope 'others' (setting to with-val if given, otherwise removing).
- generator pw-body: translate evaluate(() => document.querySelector(s).
  click()) and .dispatchEvent(new Event('name', …)) into dom-dispatch ops
  so bubbling-click assertions in 'parent takes…' tests work.
- generator toHaveClass: strip JS regex word-boundaries (\\b) from the
  expected class name.
- shared/static/wasm/sx/dom.sx: dom-child-list / dom-child-nodes mirror
  the dom-query-all SX-list passthrough — childNodes arrives pre-SXified.

Net: take 6→15 (100%), remove 16→17, fetch 11→15.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 16:51:17 +00:00
parent 3528cef35a
commit 601fdc1c34
11 changed files with 356 additions and 143 deletions

View File

@@ -1621,18 +1621,35 @@
((nil? from-sel) nil) ((nil? from-sel) nil)
((and (list? from-sel) (= (first from-sel) (quote query))) ((and (list? from-sel) (= (first from-sel) (quote query)))
(list (quote hs-query-all) (nth from-sel 1))) (list (quote hs-query-all) (nth from-sel 1)))
(true (hs-to-sx from-sel))))) (true (hs-to-sx from-sel))))
(if (with-sx
(and (= kind "attr") (or attr-val with-val)) (if
(list with-val
(quote hs-take!) (if
target (string? with-val)
kind with-val
name (hs-to-sx with-val))
scope nil)))
attr-val (cond
(if with-val (hs-to-sx with-val) nil)) ((and (= kind "attr") (or attr-val with-val))
(list (quote hs-take!) target kind name scope))))) (list
(quote hs-take!)
target
kind
name
scope
attr-val
with-sx))
((and (= kind "class") with-val)
(list
(quote hs-take!)
target
kind
name
scope
nil
with-sx))
(true (list (quote hs-take!) target kind name scope))))))
((= head (quote make)) (emit-make ast)) ((= head (quote make)) (emit-make ast))
((= head (quote install)) ((= head (quote install))
(cons (quote hs-install) (map hs-to-sx (rest ast)))) (cons (quote hs-install) (map hs-to-sx (rest ast))))

View File

@@ -1648,48 +1648,94 @@
((collect (fn () (when (= (tp-type) "class") (let ((v (tp-val))) (adv!) (set! classes (append classes (list v))) (collect)))))) ((collect (fn () (when (= (tp-type) "class") (let ((v (tp-val))) (adv!) (set! classes (append classes (list v))) (collect))))))
(collect) (collect)
(let (let
((from-sel (if (match-kw "from") (parse-expr) nil))) ((with-cls nil) (from-sel nil) (for-tgt nil))
(let (define
((for-tgt (if (match-kw "for") (parse-expr) nil))) parse-cls-clauses
(if (fn
(= (len classes) 1) ()
(list (cond
(quote take!) ((and (nil? with-cls) (match-kw "with") (= (tp-type) "class"))
"class" (do
(first classes) (set! with-cls (tp-val))
from-sel (adv!)
for-tgt) (parse-cls-clauses)))
(cons ((and (nil? with-cls) (match-kw "giving") (= (tp-type) "class"))
(quote do) (do
(map (set! with-cls (tp-val))
(fn (adv!)
(cls) (parse-cls-clauses)))
(list ((and (nil? from-sel) (match-kw "from"))
(quote take!) (do
"class" (set! from-sel (parse-expr))
cls (parse-cls-clauses)))
from-sel ((and (nil? for-tgt) (match-kw "for"))
for-tgt)) (do
classes)))))))) (set! for-tgt (parse-expr))
(parse-cls-clauses)))
(true nil))))
(parse-cls-clauses)
(if
(= (len classes) 1)
(list
(quote take!)
"class"
(first classes)
from-sel
for-tgt
nil
with-cls)
(cons
(quote do)
(map
(fn
(cls)
(list
(quote take!)
"class"
cls
from-sel
for-tgt
nil
with-cls))
classes)))))))
((= (tp-type) "attr") ((= (tp-type) "attr")
(let (let
((attr-name (get (adv!) "value"))) ((attr-name (get (adv!) "value")))
(let (let
((attr-val (if (and (= (tp-type) "op") (= (tp-val) "=")) (do (adv!) (get (adv!) "value")) nil))) ((attr-val (if (and (= (tp-type) "op") (= (tp-val) "=")) (do (adv!) (get (adv!) "value")) nil)))
(let (let
((with-val (if (match-kw "with") (parse-expr) nil))) ((with-val nil) (from-sel nil) (for-tgt nil))
(let (define
((from-sel (if (match-kw "from") (parse-expr) nil))) parse-attr-clauses
(let (fn
((for-tgt (if (match-kw "for") (parse-expr) nil))) ()
(list (cond
(quote take!) ((and (nil? with-val) (match-kw "with"))
"attr" (do
attr-name (set! with-val (parse-expr))
from-sel (parse-attr-clauses)))
for-tgt ((and (nil? with-val) (match-kw "giving"))
attr-val (do
with-val))))))) (set! with-val (parse-expr))
(parse-attr-clauses)))
((and (nil? from-sel) (match-kw "from"))
(do
(set! from-sel (parse-expr))
(parse-attr-clauses)))
((and (nil? for-tgt) (match-kw "for"))
(do
(set! for-tgt (parse-expr))
(parse-attr-clauses)))
(true nil))))
(parse-attr-clauses)
(list
(quote take!)
"attr"
attr-name
from-sel
for-tgt
attr-val
with-val)))))
(true nil)))) (true nil))))
(define (define
parse-pick-cmd parse-pick-cmd

View File

@@ -146,16 +146,32 @@
((els (if scope (if (list? scope) scope (list scope)) (let ((parent (dom-parent target))) (if parent (dom-child-list parent) (list)))))) ((els (if scope (if (list? scope) scope (list scope)) (let ((parent (dom-parent target))) (if parent (dom-child-list parent) (list))))))
(if (if
(= kind "class") (= kind "class")
(do (let
(for-each (fn (el) (dom-remove-class el name)) els) ((with-cls (if (> (len extra) 1) (nth extra 1) nil)))
(dom-add-class target name)) (do
(for-each
(fn
(el)
(do
(dom-remove-class el name)
(when with-cls (dom-add-class el with-cls))))
els)
(dom-add-class target name)
(when with-cls (dom-remove-class target with-cls))))
(let (let
((attr-val (if (> (len extra) 0) (first extra) nil)) ((attr-val (if (> (len extra) 0) (first extra) nil))
(with-val (if (> (len extra) 1) (nth extra 1) nil))) (with-val (if (> (len extra) 1) (nth extra 1) nil)))
(do (do
(when (for-each
with-val (fn
(for-each (fn (el) (dom-set-attr el name with-val)) els)) (el)
(when
(not (= el target))
(if
with-val
(dom-set-attr el name with-val)
(dom-remove-attr el name))))
els)
(if (if
attr-val attr-val
(dom-set-attr target name attr-val) (dom-set-attr target name attr-val)

View File

@@ -184,7 +184,8 @@
"blur" "blur"
"dom" "dom"
"morph" "morph"
"using")) "using"
"giving"))
(define hs-keyword? (fn (word) (some (fn (k) (= k word)) hs-keywords))) (define hs-keyword? (fn (word) (some (fn (k) (= k word)) hs-keywords)))

View File

@@ -308,42 +308,54 @@
(fn (fn
(el) (el)
"Return child nodes as an SX list." "Return child nodes as an SX list."
(if (cond
el ((nil? el) (list))
(let (true
((nl (host-get el "childNodes"))
(n (host-get nl "length"))
(result (list)))
(let (let
loop ((nl (host-get el "childNodes")))
((i 0)) (cond
(when ((nil? nl) (list))
(< i n) ((list? nl) nl)
(append! result (host-call nl "item" i)) (true
(loop (+ i 1)))) (let
result) ((n (host-get nl "length")) (result (list)))
(list)))) (when
(not (nil? n))
(let
loop
((i 0))
(when
(< i n)
(append! result (host-call nl "item" i))
(loop (+ i 1)))))
result))))))))
(define dom-is-fragment? (fn (el) (= (host-get el "nodeType") 11))) (define dom-is-fragment? (fn (el) (= (host-get el "nodeType") 11)))
(define (define
dom-child-nodes dom-child-nodes
(fn (fn
(el) (el)
"Return child nodes as an SX list." "Return child nodes as an SX list."
(if (cond
el ((nil? el) (list))
(let (true
((nl (host-get el "childNodes"))
(n (host-get nl "length"))
(result (list)))
(let (let
loop ((nl (host-get el "childNodes")))
((i 0)) (cond
(when ((nil? nl) (list))
(< i n) ((list? nl) nl)
(append! result (host-call nl "item" i)) (true
(loop (+ i 1)))) (let
result) ((n (host-get nl "length")) (result (list)))
(list)))) (when
(not (nil? n))
(let
loop
((i 0))
(when
(< i n)
(append! result (host-call nl "item" i))
(loop (+ i 1)))))
result))))))))
(define (define
dom-remove-children-after dom-remove-children-after
(fn (fn

View File

@@ -1621,18 +1621,35 @@
((nil? from-sel) nil) ((nil? from-sel) nil)
((and (list? from-sel) (= (first from-sel) (quote query))) ((and (list? from-sel) (= (first from-sel) (quote query)))
(list (quote hs-query-all) (nth from-sel 1))) (list (quote hs-query-all) (nth from-sel 1)))
(true (hs-to-sx from-sel))))) (true (hs-to-sx from-sel))))
(if (with-sx
(and (= kind "attr") (or attr-val with-val)) (if
(list with-val
(quote hs-take!) (if
target (string? with-val)
kind with-val
name (hs-to-sx with-val))
scope nil)))
attr-val (cond
(if with-val (hs-to-sx with-val) nil)) ((and (= kind "attr") (or attr-val with-val))
(list (quote hs-take!) target kind name scope))))) (list
(quote hs-take!)
target
kind
name
scope
attr-val
with-sx))
((and (= kind "class") with-val)
(list
(quote hs-take!)
target
kind
name
scope
nil
with-sx))
(true (list (quote hs-take!) target kind name scope))))))
((= head (quote make)) (emit-make ast)) ((= head (quote make)) (emit-make ast))
((= head (quote install)) ((= head (quote install))
(cons (quote hs-install) (map hs-to-sx (rest ast)))) (cons (quote hs-install) (map hs-to-sx (rest ast))))

View File

@@ -1648,48 +1648,94 @@
((collect (fn () (when (= (tp-type) "class") (let ((v (tp-val))) (adv!) (set! classes (append classes (list v))) (collect)))))) ((collect (fn () (when (= (tp-type) "class") (let ((v (tp-val))) (adv!) (set! classes (append classes (list v))) (collect))))))
(collect) (collect)
(let (let
((from-sel (if (match-kw "from") (parse-expr) nil))) ((with-cls nil) (from-sel nil) (for-tgt nil))
(let (define
((for-tgt (if (match-kw "for") (parse-expr) nil))) parse-cls-clauses
(if (fn
(= (len classes) 1) ()
(list (cond
(quote take!) ((and (nil? with-cls) (match-kw "with") (= (tp-type) "class"))
"class" (do
(first classes) (set! with-cls (tp-val))
from-sel (adv!)
for-tgt) (parse-cls-clauses)))
(cons ((and (nil? with-cls) (match-kw "giving") (= (tp-type) "class"))
(quote do) (do
(map (set! with-cls (tp-val))
(fn (adv!)
(cls) (parse-cls-clauses)))
(list ((and (nil? from-sel) (match-kw "from"))
(quote take!) (do
"class" (set! from-sel (parse-expr))
cls (parse-cls-clauses)))
from-sel ((and (nil? for-tgt) (match-kw "for"))
for-tgt)) (do
classes)))))))) (set! for-tgt (parse-expr))
(parse-cls-clauses)))
(true nil))))
(parse-cls-clauses)
(if
(= (len classes) 1)
(list
(quote take!)
"class"
(first classes)
from-sel
for-tgt
nil
with-cls)
(cons
(quote do)
(map
(fn
(cls)
(list
(quote take!)
"class"
cls
from-sel
for-tgt
nil
with-cls))
classes)))))))
((= (tp-type) "attr") ((= (tp-type) "attr")
(let (let
((attr-name (get (adv!) "value"))) ((attr-name (get (adv!) "value")))
(let (let
((attr-val (if (and (= (tp-type) "op") (= (tp-val) "=")) (do (adv!) (get (adv!) "value")) nil))) ((attr-val (if (and (= (tp-type) "op") (= (tp-val) "=")) (do (adv!) (get (adv!) "value")) nil)))
(let (let
((with-val (if (match-kw "with") (parse-expr) nil))) ((with-val nil) (from-sel nil) (for-tgt nil))
(let (define
((from-sel (if (match-kw "from") (parse-expr) nil))) parse-attr-clauses
(let (fn
((for-tgt (if (match-kw "for") (parse-expr) nil))) ()
(list (cond
(quote take!) ((and (nil? with-val) (match-kw "with"))
"attr" (do
attr-name (set! with-val (parse-expr))
from-sel (parse-attr-clauses)))
for-tgt ((and (nil? with-val) (match-kw "giving"))
attr-val (do
with-val))))))) (set! with-val (parse-expr))
(parse-attr-clauses)))
((and (nil? from-sel) (match-kw "from"))
(do
(set! from-sel (parse-expr))
(parse-attr-clauses)))
((and (nil? for-tgt) (match-kw "for"))
(do
(set! for-tgt (parse-expr))
(parse-attr-clauses)))
(true nil))))
(parse-attr-clauses)
(list
(quote take!)
"attr"
attr-name
from-sel
for-tgt
attr-val
with-val)))))
(true nil)))) (true nil))))
(define (define
parse-pick-cmd parse-pick-cmd

View File

@@ -146,16 +146,32 @@
((els (if scope (if (list? scope) scope (list scope)) (let ((parent (dom-parent target))) (if parent (dom-child-list parent) (list)))))) ((els (if scope (if (list? scope) scope (list scope)) (let ((parent (dom-parent target))) (if parent (dom-child-list parent) (list))))))
(if (if
(= kind "class") (= kind "class")
(do (let
(for-each (fn (el) (dom-remove-class el name)) els) ((with-cls (if (> (len extra) 1) (nth extra 1) nil)))
(dom-add-class target name)) (do
(for-each
(fn
(el)
(do
(dom-remove-class el name)
(when with-cls (dom-add-class el with-cls))))
els)
(dom-add-class target name)
(when with-cls (dom-remove-class target with-cls))))
(let (let
((attr-val (if (> (len extra) 0) (first extra) nil)) ((attr-val (if (> (len extra) 0) (first extra) nil))
(with-val (if (> (len extra) 1) (nth extra 1) nil))) (with-val (if (> (len extra) 1) (nth extra 1) nil)))
(do (do
(when (for-each
with-val (fn
(for-each (fn (el) (dom-set-attr el name with-val)) els)) (el)
(when
(not (= el target))
(if
with-val
(dom-set-attr el name with-val)
(dom-remove-attr el name))))
els)
(if (if
attr-val attr-val
(dom-set-attr target name attr-val) (dom-set-attr target name attr-val)

View File

@@ -184,7 +184,8 @@
"blur" "blur"
"dom" "dom"
"morph" "morph"
"using")) "using"
"giving"))
(define hs-keyword? (fn (word) (some (fn (k) (= k word)) hs-keywords))) (define hs-keyword? (fn (word) (some (fn (k) (= k word)) hs-keywords)))

View File

@@ -349,7 +349,9 @@
(dom-append _el-div _el-btn1) (dom-append _el-div _el-btn1)
(hs-activate! _el-div) (hs-activate! _el-div)
(host-set! (host-global "window") "clicks" 0) (host-set! (host-global "window") "clicks" 0)
(dom-dispatch (dom-query-by-id "btn1") "click" nil)
(dom-dispatch _el-div "click" nil) (dom-dispatch _el-div "click" nil)
(dom-dispatch (dom-query-by-id "btn1") "click" nil)
)) ))
(deftest "append to undefined ignores the undefined" (deftest "append to undefined ignores the undefined"
(hs-cleanup!) (hs-cleanup!)
@@ -1987,6 +1989,7 @@
(dom-append (dom-body) _el-target) (dom-append (dom-body) _el-target)
(dom-append (dom-body) _el-other) (dom-append (dom-body) _el-other)
(hs-activate! _el-target) (hs-activate! _el-target)
(dom-dispatch (dom-query-by-id "other") "click" nil)
)) ))
(deftest "can remove class by id" (deftest "can remove class by id"
(hs-cleanup!) (hs-cleanup!)
@@ -10177,6 +10180,7 @@
(let ((_el-untilTest (dom-create-element "div"))) (let ((_el-untilTest (dom-create-element "div")))
(dom-set-attr _el-untilTest "id" "untilTest") (dom-set-attr _el-untilTest "id" "untilTest")
(dom-append (dom-body) _el-untilTest) (dom-append (dom-body) _el-untilTest)
(dom-dispatch (dom-query-by-id "untilTest") "click" nil)
)) ))
(deftest "until keyword works" (deftest "until keyword works"
(hs-cleanup!) (hs-cleanup!)
@@ -11434,6 +11438,7 @@
(dom-append _el-div _el-d2) (dom-append _el-div _el-d2)
(dom-append _el-div _el-d3) (dom-append _el-div _el-d3)
(hs-activate! _el-div) (hs-activate! _el-div)
(dom-dispatch (dom-query-by-id "d2") "click" nil)
(assert (not (dom-has-class? (dom-query-by-id "d1") "foo"))) (assert (not (dom-has-class? (dom-query-by-id "d1") "foo")))
(assert (dom-has-class? (dom-query-by-id "d2") "foo")) (assert (dom-has-class? (dom-query-by-id "d2") "foo"))
(assert (not (dom-has-class? (dom-query-by-id "d3") "foo"))) (assert (not (dom-has-class? (dom-query-by-id "d3") "foo")))
@@ -11454,6 +11459,7 @@
(dom-append _el-div _el-d2) (dom-append _el-div _el-d2)
(dom-append _el-div _el-d3) (dom-append _el-div _el-d3)
(hs-activate! _el-div) (hs-activate! _el-div)
(dom-dispatch (dom-query-by-id "d2") "click" nil)
(assert (not (dom-has-attr? (dom-query-by-id "d1") "data-foo"))) (assert (not (dom-has-attr? (dom-query-by-id "d1") "data-foo")))
(assert= (dom-get-attr (dom-query-by-id "d2") "data-foo") "") (assert= (dom-get-attr (dom-query-by-id "d2") "data-foo") "")
(assert (not (dom-has-attr? (dom-query-by-id "d3") "data-foo"))) (assert (not (dom-has-attr? (dom-query-by-id "d3") "data-foo")))
@@ -11474,8 +11480,8 @@
(hs-activate! _el-div1) (hs-activate! _el-div1)
(dom-dispatch (nth (dom-query-all (dom-body) "div") 1) "click" nil) (dom-dispatch (nth (dom-query-all (dom-body) "div") 1) "click" nil)
(assert (dom-has-class? _el-div "unselected")) (assert (dom-has-class? _el-div "unselected"))
(assert (not (dom-has-class? _el-div "\bselected\b"))) (assert (not (dom-has-class? _el-div "selected")))
(assert (dom-has-class? (nth (dom-query-all (dom-body) "div") 1) "\bselected\b")) (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 (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")) (assert (dom-has-class? (nth (dom-query-all (dom-body) "div") 2) "unselected"))
)) ))
@@ -11492,9 +11498,9 @@
(dom-append (dom-body) _el-div2) (dom-append (dom-body) _el-div2)
(hs-activate! _el-div1) (hs-activate! _el-div1)
(dom-dispatch (nth (dom-query-all (dom-body) "div") 1) "click" nil) (dom-dispatch (nth (dom-query-all (dom-body) "div") 1) "click" nil)
(assert (not (dom-has-class? _el-div "\bselected\b"))) (assert (not (dom-has-class? _el-div "selected")))
(assert (dom-has-class? _el-div "unselected")) (assert (dom-has-class? _el-div "unselected"))
(assert (dom-has-class? (nth (dom-query-all (dom-body) "div") 1) "\bselected\b")) (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 (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")) (assert (dom-has-class? (nth (dom-query-all (dom-body) "div") 2) "unselected"))
)) ))
@@ -12610,6 +12616,7 @@ end")
(assert (not (dom-has-class? (dom-query "div:nth-of-type(2)") "foo"))) (assert (not (dom-has-class? (dom-query "div:nth-of-type(2)") "foo")))
(dom-dispatch (dom-query "div:nth-of-type(2)") "click" nil) (dom-dispatch (dom-query "div:nth-of-type(2)") "click" nil)
(assert (dom-has-class? (dom-query "div:nth-of-type(2)") "foo")) (assert (dom-has-class? (dom-query "div:nth-of-type(2)") "foo"))
(dom-dispatch (dom-query-by-id "d1") "foo" nil)
(assert (not (dom-has-class? (dom-query "div:nth-of-type(2)") "foo"))) (assert (not (dom-has-class? (dom-query "div:nth-of-type(2)") "foo")))
)) ))
(deftest "can toggle visibility" (deftest "can toggle visibility"
@@ -12980,6 +12987,7 @@ end")
(dom-append (dom-body) _el-div) (dom-append (dom-body) _el-div)
(hs-activate! _el-div) (hs-activate! _el-div)
(dom-dispatch _el-div "click" nil) (dom-dispatch _el-div "click" nil)
(dom-dispatch _el-div "foo" nil)
(assert= (dom-text-content _el-div) "bar") (assert= (dom-text-content _el-div) "bar")
)) ))
(deftest "can wait on event" (deftest "can wait on event"
@@ -12991,6 +12999,7 @@ end")
(dom-dispatch _el-div "click" nil) (dom-dispatch _el-div "click" nil)
(assert (dom-has-class? _el-div "foo")) (assert (dom-has-class? _el-div "foo"))
(assert (not (dom-has-class? _el-div "bar"))) (assert (not (dom-has-class? _el-div "bar")))
(dom-dispatch _el-div "foo" nil)
(assert (dom-has-class? _el-div "bar")) (assert (dom-has-class? _el-div "bar"))
)) ))
(deftest "can wait on event on another element" (deftest "can wait on event on another element"
@@ -13004,6 +13013,7 @@ end")
(dom-dispatch (dom-query "div:nth-of-type(2)") "click" nil) (dom-dispatch (dom-query "div:nth-of-type(2)") "click" nil)
(assert (dom-has-class? (dom-query "div:nth-of-type(2)") "foo")) (assert (dom-has-class? (dom-query "div:nth-of-type(2)") "foo"))
(assert (not (dom-has-class? (dom-query "div:nth-of-type(2)") "bar"))) (assert (not (dom-has-class? (dom-query "div:nth-of-type(2)") "bar")))
(dom-dispatch (dom-query-by-id "d2") "foo" nil)
(assert (dom-has-class? (dom-query "div:nth-of-type(2)") "bar")) (assert (dom-has-class? (dom-query "div:nth-of-type(2)") "bar"))
)) ))
(deftest "can wait on event or timeout 1" (deftest "can wait on event or timeout 1"
@@ -13043,6 +13053,7 @@ end")
(dom-append (dom-body) _el-div) (dom-append (dom-body) _el-div)
(hs-activate! _el-div) (hs-activate! _el-div)
(dom-dispatch _el-div "click" nil) (dom-dispatch _el-div "click" nil)
(dom-dispatch _el-div "foo" nil)
(assert= (dom-text-content _el-div) "hyperscript is hyper cool") (assert= (dom-text-content _el-div) "hyperscript is hyper cool")
)) ))
) )

View File

@@ -680,10 +680,14 @@ def pw_assertion_to_sx(target, negated, assert_type, args_str):
elif assert_type == 'toHaveClass': elif assert_type == 'toHaveClass':
cls = args[0] if args else '' cls = args[0] if args else ''
if not cls: if not cls:
# Handle regex like /outer-clicked/ # Handle regex like /outer-clicked/ or /\bselected\b/
m = re.match(r'/(.+?)/', args_str) m = re.match(r'/(.+?)/', args_str)
if m: if m:
cls = m.group(1) cls = m.group(1)
# Strip JS regex anchors/word-boundaries — the class name itself is
# a bare ident, not a regex pattern.
cls = re.sub(r'\\b', '', cls)
cls = cls.strip('^$')
if negated: if negated:
return f'(assert (not (dom-has-class? {target} "{cls}")))' return f'(assert (not (dom-has-class? {target} "{cls}")))'
return f'(assert (dom-has-class? {target} "{cls}"))' return f'(assert (dom-has-class? {target} "{cls}"))'
@@ -897,6 +901,32 @@ def parse_dev_body(body, elements, var_names):
ops.append(f'(dom-set-inner-html {target} "{val}")') ops.append(f'(dom-set-inner-html {target} "{val}")')
continue continue
# evaluate(() => document.querySelector(SEL).click()) — dispatch click
# on the matched element (bubbles so ancestors see it too).
m = re.match(
r"evaluate\(\s*\(\)\s*=>\s*document\.querySelector\(\s*(['\"])([^'\"]+)\1\s*\)"
r"\.click\(\)\s*\)\s*$",
stmt_na, re.DOTALL,
)
if m and seen_html:
sel = re.sub(r'^#work-area\s+', '', m.group(2))
target = selector_to_sx(sel, elements, var_names)
ops.append(f'(dom-dispatch {target} "click" nil)')
continue
# evaluate(() => document.querySelector(SEL).dispatchEvent(new Event/CustomEvent(NAME…)))
m = re.match(
r"evaluate\(\s*\(\)\s*=>\s*document\.querySelector\(\s*(['\"])([^'\"]+)\1\s*\)"
r"\.dispatchEvent\(\s*new\s+(?:Custom)?Event\(\s*(['\"])([^'\"]+)\3"
r"(?:\s*,\s*[^)]*)?\s*\)\s*\)\s*\)\s*$",
stmt_na, re.DOTALL,
)
if m and seen_html:
sel = re.sub(r'^#work-area\s+', '', m.group(2))
target = selector_to_sx(sel, elements, var_names)
ops.append(f'(dom-dispatch {target} "{m.group(4)}" nil)')
continue
if not seen_html: if not seen_html:
continue continue
if add_action(stmt_na): if add_action(stmt_na):