HS parser/compiler/mock: fix 31 test failures across 7 issues
Parser: - Relax (number? v) to v in parse-one-transition so (expr)unit works - Add (match-kw "then") before parse-cmd-list in parse-for-cmd - Handle "indexed by" syntax alongside "index" in for loops - Add "indexed" to hs-keywords to prevent unit-suffix consumption Compiler: - Use map-indexed instead of for-each for indexed for-loops Test generator: - Preserve \" escapes in process_hs_val via placeholder/restore Mock DOM: - Coerce insertAdjacentHTML values via dom_stringify (match browser) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1985,9 +1985,10 @@ let run_spec_tests env test_files =
|
||||
Hashtbl.replace r "right" (Number 100.0); Hashtbl.replace r "bottom" (Number 100.0);
|
||||
Dict r
|
||||
| "insertAdjacentHTML" ->
|
||||
(* Simplified: just append text to innerHTML *)
|
||||
(* Simplified: coerce value to string and append to innerHTML *)
|
||||
(match rest with
|
||||
| [String _pos; String html] ->
|
||||
| [String _pos; value] ->
|
||||
let html = match dom_stringify value with String s -> s | _ -> "" in
|
||||
let cur = match Hashtbl.find_opt d "innerHTML" with Some (String s) -> s | _ -> "" in
|
||||
Hashtbl.replace d "innerHTML" (String (cur ^ html)); Nil
|
||||
| _ -> Nil)
|
||||
|
||||
@@ -267,10 +267,10 @@
|
||||
(if
|
||||
(and (> (len ast) 4) (= (nth ast 4) :index))
|
||||
(list
|
||||
(quote for-each)
|
||||
(quote map-indexed)
|
||||
(list
|
||||
(quote fn)
|
||||
(list (make-symbol var-name) (make-symbol (nth ast 5)))
|
||||
(list (make-symbol (nth ast 5)) (make-symbol var-name))
|
||||
body)
|
||||
collection)
|
||||
(list
|
||||
|
||||
@@ -1223,10 +1223,10 @@
|
||||
(let
|
||||
((prop (cond ((= (tp-type) "style") (get (adv!) "value")) ((= (tp-val) "my") (do (adv!) (if (= (tp-type) "style") (get (adv!) "value") (get (adv!) "value")))) (true (get (adv!) "value")))))
|
||||
(let
|
||||
((from-val (if (match-kw "from") (let ((v (parse-atom))) (if (and (number? v) (= (tp-type) "ident") (not (hs-keyword? (tp-val)))) (let ((unit (get (adv!) "value"))) (list (quote string-postfix) v unit)) v)) nil)))
|
||||
((from-val (if (match-kw "from") (let ((v (parse-atom))) (if (and v (= (tp-type) "ident") (not (hs-keyword? (tp-val)))) (let ((unit (get (adv!) "value"))) (list (quote string-postfix) v unit)) v)) nil)))
|
||||
(expect-kw! "to")
|
||||
(let
|
||||
((value (let ((v (parse-atom))) (if (and (number? v) (= (tp-type) "ident") (not (hs-keyword? (tp-val)))) (let ((unit (get (adv!) "value"))) (list (quote string-postfix) v unit)) v))))
|
||||
((value (let ((v (parse-atom))) (if (and v (= (tp-type) "ident") (not (hs-keyword? (tp-val)))) (let ((unit (get (adv!) "value"))) (list (quote string-postfix) v unit)) v))))
|
||||
(let
|
||||
((dur (if (match-kw "over") (let ((v (parse-atom))) (if (and (number? v) (= (tp-type) "ident") (not (hs-keyword? (tp-val)))) (let ((unit (get (adv!) "value"))) (list (quote string-postfix) v unit)) v)) nil)))
|
||||
(let
|
||||
@@ -1521,9 +1521,9 @@
|
||||
(let
|
||||
((collection (parse-expr)))
|
||||
(let
|
||||
((idx (if (match-kw "index") (let ((iname (tp-val))) (adv!) iname) nil)))
|
||||
((idx (cond ((match-kw "index") (let ((iname (tp-val))) (adv!) iname)) ((match-kw "indexed") (do (match-kw "by") (let ((iname (tp-val))) (adv!) iname))) (true nil))))
|
||||
(let
|
||||
((body (parse-cmd-list)))
|
||||
((body (do (match-kw "then") (parse-cmd-list))))
|
||||
(match-kw "end")
|
||||
(if
|
||||
idx
|
||||
|
||||
@@ -104,6 +104,7 @@
|
||||
"detail"
|
||||
"sender"
|
||||
"index"
|
||||
"indexed"
|
||||
"increment"
|
||||
"decrement"
|
||||
"append"
|
||||
|
||||
@@ -638,10 +638,11 @@
|
||||
(deftest "can toggle between two attribute values"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-div (dom-create-element "div")))
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-div "_" "\\\"on")
|
||||
(dom-set-attr _el-div "data-state" "active")
|
||||
;; SKIP attr [@data-state (contains special chars)
|
||||
(dom-append (dom-body) _el-div)
|
||||
(hs-activate! _el-div)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
(assert= (dom-get-attr _el-div "data-state") "inactive")
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
@@ -650,11 +651,12 @@
|
||||
(deftest "can toggle between different attributes"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-div (dom-create-element "div")))
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-div "_" "\\\"on")
|
||||
(dom-set-attr _el-div "enabled" "true")
|
||||
;; SKIP attr [@enabled (contains special chars)
|
||||
;; SKIP attr [@disabled (contains special chars)
|
||||
(dom-append (dom-body) _el-div)
|
||||
(hs-activate! _el-div)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
(assert= (dom-get-attr _el-div "disabled") "true")
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
@@ -722,9 +724,10 @@
|
||||
(deftest "can toggle *display between two values"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-div (dom-create-element "div")))
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-div "_" "\\\"on")
|
||||
(dom-set-attr _el-div "style" "display:none")
|
||||
(dom-append (dom-body) _el-div)
|
||||
(hs-activate! _el-div)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
(assert= (dom-get-style _el-div "display") "flex")
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
@@ -733,9 +736,10 @@
|
||||
(deftest "can toggle *opacity between three values"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-div (dom-create-element "div")))
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-div "_" "\\\"on")
|
||||
(dom-set-attr _el-div "style" "opacity:0")
|
||||
(dom-append (dom-body) _el-div)
|
||||
(hs-activate! _el-div)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
(assert= (dom-get-style _el-div "opacity") "0.5")
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
@@ -746,8 +750,9 @@
|
||||
(deftest "can toggle a global variable between two values"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-div (dom-create-element "div")))
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-div "_" "\\\"on")
|
||||
(dom-append (dom-body) _el-div)
|
||||
(hs-activate! _el-div)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
@@ -755,8 +760,9 @@
|
||||
(deftest "can toggle a global variable between three values"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-div (dom-create-element "div")))
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-div "_" "\\\"on")
|
||||
(dom-append (dom-body) _el-div)
|
||||
(hs-activate! _el-div)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
@@ -1351,7 +1357,7 @@
|
||||
(deftest "properly processes hyperscript in new content in a symbol write"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-div (dom-create-element "div")))
|
||||
(dom-set-attr _el-div "_" "on click put \"<button id=\"b1\" _=\"on click put 42 into me\">40</button>\" into me")
|
||||
(dom-set-attr _el-div "_" "on click put \"<button id=\\\"b1\\\" _=\\\"on click put 42 into me\\\">40</button>\" into me")
|
||||
(dom-append (dom-body) _el-div)
|
||||
(hs-activate! _el-div)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
@@ -1362,7 +1368,7 @@
|
||||
(hs-cleanup!)
|
||||
(let ((_el-d1 (dom-create-element "div")))
|
||||
(dom-set-attr _el-d1 "id" "d1")
|
||||
(dom-set-attr _el-d1 "_" "on click put \"<button id=\"b1\" _=\"on click put 42 into me\">40</button>\" into <div#d1/>")
|
||||
(dom-set-attr _el-d1 "_" "on click put \"<button id=\\\"b1\\\" _=\\\"on click put 42 into me\\\">40</button>\" into <div#d1/>")
|
||||
(dom-append (dom-body) _el-d1)
|
||||
(hs-activate! _el-d1)
|
||||
(dom-dispatch _el-d1 "click" nil)
|
||||
@@ -1373,7 +1379,7 @@
|
||||
(hs-cleanup!)
|
||||
(let ((_el-d1 (dom-create-element "div")))
|
||||
(dom-set-attr _el-d1 "id" "d1")
|
||||
(dom-set-attr _el-d1 "_" "on click put \"<button id=\"b1\" _=\"on click put 42 into me\">40</button>\" before me")
|
||||
(dom-set-attr _el-d1 "_" "on click put \"<button id=\\\"b1\\\" _=\\\"on click put 42 into me\\\">40</button>\" before me")
|
||||
(dom-append (dom-body) _el-d1)
|
||||
(hs-activate! _el-d1)
|
||||
(dom-dispatch _el-d1 "click" nil)
|
||||
@@ -1384,7 +1390,7 @@
|
||||
(hs-cleanup!)
|
||||
(let ((_el-d1 (dom-create-element "div")))
|
||||
(dom-set-attr _el-d1 "id" "d1")
|
||||
(dom-set-attr _el-d1 "_" "on click put \"<button id=\"b1\" _=\"on click put 42 into me\">40</button>\" at the start of me")
|
||||
(dom-set-attr _el-d1 "_" "on click put \"<button id=\\\"b1\\\" _=\\\"on click put 42 into me\\\">40</button>\" at the start of me")
|
||||
(dom-append (dom-body) _el-d1)
|
||||
(hs-activate! _el-d1)
|
||||
(dom-dispatch _el-d1 "click" nil)
|
||||
@@ -1395,7 +1401,7 @@
|
||||
(hs-cleanup!)
|
||||
(let ((_el-d1 (dom-create-element "div")))
|
||||
(dom-set-attr _el-d1 "id" "d1")
|
||||
(dom-set-attr _el-d1 "_" "on click put \"<button id=\"b1\" _=\"on click put 42 into me\">40</button>\" at the end of me")
|
||||
(dom-set-attr _el-d1 "_" "on click put \"<button id=\\\"b1\\\" _=\\\"on click put 42 into me\\\">40</button>\" at the end of me")
|
||||
(dom-append (dom-body) _el-d1)
|
||||
(hs-activate! _el-d1)
|
||||
(dom-dispatch _el-d1 "click" nil)
|
||||
@@ -1406,7 +1412,7 @@
|
||||
(hs-cleanup!)
|
||||
(let ((_el-d1 (dom-create-element "div")))
|
||||
(dom-set-attr _el-d1 "id" "d1")
|
||||
(dom-set-attr _el-d1 "_" "on click put \"<button id=\"b1\" _=\"on click put 42 into me\">40</button>\" after me")
|
||||
(dom-set-attr _el-d1 "_" "on click put \"<button id=\\\"b1\\\" _=\\\"on click put 42 into me\\\">40</button>\" after me")
|
||||
(dom-append (dom-body) _el-d1)
|
||||
(hs-activate! _el-d1)
|
||||
(dom-dispatch _el-d1 "click" nil)
|
||||
@@ -2047,7 +2053,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 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)
|
||||
@@ -2704,9 +2710,10 @@
|
||||
(deftest "can transition on query ref with of syntax"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-div (dom-create-element "div")) (_el-span (dom-create-element "span")))
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-div "_" "\\\"on")
|
||||
(dom-append (dom-body) _el-div)
|
||||
(dom-append (dom-body) _el-span)
|
||||
(hs-activate! _el-div)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
(assert= (dom-get-style _el-span "width") "100px")
|
||||
))
|
||||
@@ -2980,7 +2987,7 @@
|
||||
(deftest "throws on non-2xx response by default"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-div (dom-create-element "div")))
|
||||
(dom-set-attr _el-div "_" "on click fetch /test catch e put \"caught\" into me")
|
||||
(dom-set-attr _el-div "_" "on click fetch /test catch e put \\\"caught\\\" into me")
|
||||
(dom-append (dom-body) _el-div)
|
||||
(hs-activate! _el-div)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
@@ -3016,8 +3023,9 @@
|
||||
(deftest "Response can be converted to JSON via as JSON"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-div (dom-create-element "div")))
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-div "_" "\\\"on")
|
||||
(dom-append (dom-body) _el-div)
|
||||
(hs-activate! _el-div)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
(assert= (dom-text-content _el-div) "Joe")
|
||||
))
|
||||
@@ -4184,7 +4192,7 @@
|
||||
(hs-cleanup!)
|
||||
(let ((_el-d1 (dom-create-element "div")))
|
||||
(dom-set-attr _el-d1 "id" "d1")
|
||||
(dom-set-attr _el-d1 "_" "on myEvent(foo) if foo put foo into me else put \"no-detail\" into me")
|
||||
(dom-set-attr _el-d1 "_" "on myEvent(foo) if foo put foo into me else put \\\"no-detail\\\" into me")
|
||||
(dom-append (dom-body) _el-d1)
|
||||
(hs-activate! _el-d1)
|
||||
))
|
||||
@@ -4203,7 +4211,7 @@
|
||||
(deftest "caught exceptions do not trigger 'exception' event"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-button (dom-create-element "button")))
|
||||
(dom-set-attr _el-button "_" "on click put \"foo\" into me then throw \"bar\" catch e log e on exception(error) put error into me")
|
||||
(dom-set-attr _el-button "_" "on click put \\\"foo\\\" into me then throw \\\"bar\\\" catch e log e on exception(error) put error into me")
|
||||
(dom-append (dom-body) _el-button)
|
||||
(hs-activate! _el-button)
|
||||
(dom-dispatch _el-button "click" nil)
|
||||
@@ -4212,7 +4220,7 @@
|
||||
(deftest "rethrown exceptions trigger 'exception' event"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-button (dom-create-element "button")))
|
||||
(dom-set-attr _el-button "_" "on click put \"foo\" into me then throw \"bar\" catch e throw e on exception(error) put error into me")
|
||||
(dom-set-attr _el-button "_" "on click put \\\"foo\\\" into me then throw \\\"bar\\\" catch e throw e on exception(error) put error into me")
|
||||
(dom-append (dom-body) _el-button)
|
||||
(hs-activate! _el-button)
|
||||
(dom-dispatch _el-button "click" nil)
|
||||
@@ -4221,7 +4229,7 @@
|
||||
(deftest "can ignore when target doesn\'t exist"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-div (dom-create-element "div")))
|
||||
(dom-set-attr _el-div "_" "on click from #doesntExist then throw \"bar\" on click put \"clicked\" into me")
|
||||
(dom-set-attr _el-div "_" "on click from #doesntExist then throw \\\"bar\\\" on click put \\\"clicked\\\" into me")
|
||||
(dom-append (dom-body) _el-div)
|
||||
(hs-activate! _el-div)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
@@ -4423,7 +4431,7 @@
|
||||
(deftest "prompts and puts result in it"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-button (dom-create-element "button")) (_el-out (dom-create-element "div")))
|
||||
(dom-set-attr _el-button "_" "on click ask \"What is your name?\" then put it into #out")
|
||||
(dom-set-attr _el-button "_" "on click ask \\\"What is your name?\\\" then put it into #out")
|
||||
(dom-set-inner-html _el-button "Ask")
|
||||
(dom-set-attr _el-out "id" "out")
|
||||
(dom-append (dom-body) _el-button)
|
||||
@@ -4435,7 +4443,7 @@
|
||||
(deftest "returns null on cancel"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-button (dom-create-element "button")) (_el-out (dom-create-element "div")))
|
||||
(dom-set-attr _el-button "_" "on click ask \"Name?\" then put it into #out")
|
||||
(dom-set-attr _el-button "_" "on click ask \\\"Name?\\\" then put it into #out")
|
||||
(dom-set-inner-html _el-button "Ask")
|
||||
(dom-set-attr _el-out "id" "out")
|
||||
(dom-append (dom-body) _el-button)
|
||||
@@ -4447,7 +4455,7 @@
|
||||
(deftest "shows an alert"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-button (dom-create-element "button")) (_el-out (dom-create-element "div")))
|
||||
(dom-set-attr _el-button "_" "on click answer \"Hello!\" then put \"done\" into #out")
|
||||
(dom-set-attr _el-button "_" "on click answer \\\"Hello!\\\" then put \\\"done\\\" into #out")
|
||||
(dom-set-inner-html _el-button "Go")
|
||||
(dom-set-attr _el-out "id" "out")
|
||||
(dom-append (dom-body) _el-button)
|
||||
@@ -4459,7 +4467,7 @@
|
||||
(deftest "confirm returns first choice on OK"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-button (dom-create-element "button")) (_el-out (dom-create-element "div")))
|
||||
(dom-set-attr _el-button "_" "on click answer \"Save?\" with \"Yes\" or \"No\" then put it into #out")
|
||||
(dom-set-attr _el-button "_" "on click answer \\\"Save?\\\" with \\\"Yes\\\" or \\\"No\\\" then put it into #out")
|
||||
(dom-set-inner-html _el-button "Go")
|
||||
(dom-set-attr _el-out "id" "out")
|
||||
(dom-append (dom-body) _el-button)
|
||||
@@ -4471,7 +4479,7 @@
|
||||
(deftest "confirm returns second choice on cancel"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-button (dom-create-element "button")) (_el-out (dom-create-element "div")))
|
||||
(dom-set-attr _el-button "_" "on click answer \"Save?\" with \"Yes\" or \"No\" then put it into #out")
|
||||
(dom-set-attr _el-button "_" "on click answer \\\"Save?\\\" with \\\"Yes\\\" or \\\"No\\\" then put it into #out")
|
||||
(dom-set-inner-html _el-button "Go")
|
||||
(dom-set-attr _el-out "id" "out")
|
||||
(dom-append (dom-body) _el-button)
|
||||
@@ -4850,7 +4858,7 @@
|
||||
(deftest "can parse go to with string URL"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-div (dom-create-element "div")))
|
||||
(dom-set-attr _el-div "_" "on click go to \"#test-hash\"")
|
||||
(dom-set-attr _el-div "_" "on click go to \\\"#test-hash\\\"")
|
||||
(dom-append (dom-body) _el-div)
|
||||
(hs-activate! _el-div)
|
||||
))
|
||||
@@ -4946,11 +4954,12 @@
|
||||
(dom-set-attr _el-outer "id" "outer")
|
||||
(dom-set-attr _el-outer "_" "on click add .outer-clicked")
|
||||
(dom-set-attr _el-inner "id" "inner")
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-inner "_" "\\\"on")
|
||||
(dom-set-inner-html _el-inner "click me")
|
||||
(dom-append (dom-body) _el-outer)
|
||||
(dom-append _el-outer _el-inner)
|
||||
(hs-activate! _el-outer)
|
||||
(hs-activate! _el-inner)
|
||||
(dom-dispatch (dom-query-by-id "inner") "click" nil)
|
||||
(assert (not (dom-has-class? (dom-query-by-id "outer") "outer-clicked")))
|
||||
(assert (dom-has-class? (dom-query-by-id "inner") "continued"))
|
||||
@@ -5036,7 +5045,7 @@
|
||||
(dom-set-attr _el-target "id" "target")
|
||||
(dom-set-inner-html _el-span "first")
|
||||
(dom-set-attr _el-target2 "id" "target")
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-target2 "_" "\\\"on")
|
||||
(dom-set-inner-html _el-span3 "first")
|
||||
(dom-set-inner-html _el-span4 "second")
|
||||
(dom-append (dom-body) _el-target)
|
||||
@@ -5044,6 +5053,7 @@
|
||||
(dom-append (dom-body) _el-target2)
|
||||
(dom-append _el-target2 _el-span3)
|
||||
(dom-append _el-target2 _el-span4)
|
||||
(hs-activate! _el-target2)
|
||||
(dom-dispatch (dom-query-by-id "go") "click" nil)
|
||||
))
|
||||
(deftest "morph removes old children"
|
||||
@@ -5053,13 +5063,14 @@
|
||||
(dom-set-inner-html _el-span "first")
|
||||
(dom-set-inner-html _el-span2 "second")
|
||||
(dom-set-attr _el-target3 "id" "target")
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-target3 "_" "\\\"on")
|
||||
(dom-set-inner-html _el-span4 "first")
|
||||
(dom-append (dom-body) _el-target)
|
||||
(dom-append _el-target _el-span)
|
||||
(dom-append _el-target _el-span2)
|
||||
(dom-append (dom-body) _el-target3)
|
||||
(dom-append _el-target3 _el-span4)
|
||||
(hs-activate! _el-target3)
|
||||
(dom-dispatch (dom-query-by-id "go") "click" nil)
|
||||
))
|
||||
(deftest "morph initializes hyperscript on new elements"
|
||||
@@ -5068,7 +5079,7 @@
|
||||
(dom-set-attr _el-target "id" "target")
|
||||
(dom-set-inner-html _el-p "old")
|
||||
(dom-set-attr _el-target2 "id" "target")
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-target2 "_" "\\\"on")
|
||||
(dom-set-attr _el-inner "id" "inner")
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-inner-html _el-inner "new")
|
||||
@@ -5076,6 +5087,7 @@
|
||||
(dom-append _el-target _el-p)
|
||||
(dom-append (dom-body) _el-target2)
|
||||
(dom-append _el-target2 _el-inner)
|
||||
(hs-activate! _el-target2)
|
||||
(dom-dispatch (dom-query-by-id "go") "click" nil)
|
||||
(assert= (dom-text-content (dom-query-by-id "inner")) "new")
|
||||
(dom-dispatch (dom-query-by-id "inner") "click" nil)
|
||||
@@ -5086,7 +5098,7 @@
|
||||
(let ((_el-target (dom-create-element "div")) (_el-child (dom-create-element "div")) (_el-button (dom-create-element "button")))
|
||||
(dom-set-attr _el-target "id" "target")
|
||||
(dom-set-attr _el-child "id" "child")
|
||||
(dom-set-attr _el-child "_" "on click put \"alive\" into me")
|
||||
(dom-set-attr _el-child "_" "on click put \\\"alive\\\" into me")
|
||||
(dom-set-inner-html _el-child "child")
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-inner-html _el-button "go")
|
||||
@@ -5132,7 +5144,7 @@
|
||||
(dom-set-attr _el-target "id" "target")
|
||||
(dom-set-inner-html _el-target "original")
|
||||
(dom-set-attr _el-go "id" "go")
|
||||
(dom-set-attr _el-go "_" "on click set content to \"<div id=target>morphed</div>\" then morph #target to content")
|
||||
(dom-set-attr _el-go "_" "on click set content to \\\"<div id=target>morphed</div>\\\" then morph #target to content")
|
||||
(dom-set-inner-html _el-go "go")
|
||||
(dom-append (dom-body) _el-target)
|
||||
(dom-append (dom-body) _el-go)
|
||||
@@ -7255,7 +7267,7 @@
|
||||
(let ((_el-target (dom-create-element "div")) (_el-button (dom-create-element "button")))
|
||||
(dom-set-attr _el-target "id" "target")
|
||||
(dom-set-inner-html _el-target "old")
|
||||
(dom-set-attr _el-button "_" "on click make a <span.replaced/> then put \"moved\" into it then set #target to it")
|
||||
(dom-set-attr _el-button "_" "on click make a <span.replaced/> then put \\\"moved\\\" into it then set #target to it")
|
||||
(dom-set-inner-html _el-button "go")
|
||||
(dom-append (dom-body) _el-target)
|
||||
(dom-append (dom-body) _el-button)
|
||||
@@ -7343,7 +7355,7 @@
|
||||
(let ((_el-target (dom-create-element "div")) (_el-button (dom-create-element "button")))
|
||||
(dom-set-attr _el-target "id" "target")
|
||||
(dom-set-inner-html _el-target "old")
|
||||
(dom-set-attr _el-button "_" "on click put \"new\" into #target")
|
||||
(dom-set-attr _el-button "_" "on click put \\\"new\\\" into #target")
|
||||
(dom-set-inner-html _el-button "go")
|
||||
(dom-append (dom-body) _el-target)
|
||||
(dom-append (dom-body) _el-button)
|
||||
@@ -7464,15 +7476,17 @@
|
||||
(dom-set-inner-html _el-span2 "B")
|
||||
(dom-add-class _el-span3 "a")
|
||||
(dom-set-inner-html _el-span3 "C")
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-button "_" "\\\"on")
|
||||
(dom-set-attr _el-b2 "id" "b2")
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-b2 "_" "\\\"on")
|
||||
(dom-append (dom-body) _el-box)
|
||||
(dom-append _el-box _el-span)
|
||||
(dom-append _el-box _el-span2)
|
||||
(dom-append _el-box _el-span3)
|
||||
(dom-append (dom-body) _el-button)
|
||||
(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 (dom-query-by-id "b2") "click" nil)
|
||||
@@ -7486,11 +7500,12 @@
|
||||
(dom-set-inner-html _el-span "A")
|
||||
(dom-add-class _el-span2 "b")
|
||||
(dom-set-inner-html _el-span2 "B")
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-button "_" "\\\"set")
|
||||
(dom-append (dom-body) _el-box)
|
||||
(dom-append _el-box _el-span)
|
||||
(dom-append _el-box _el-span2)
|
||||
(dom-append (dom-body) _el-button)
|
||||
(hs-activate! _el-button)
|
||||
(dom-dispatch _el-button "click" nil)
|
||||
(assert= (dom-text-content _el-button) "1")
|
||||
))
|
||||
@@ -7911,8 +7926,9 @@
|
||||
(hs-cleanup!)
|
||||
(let ((_el-d1 (dom-create-element "div")))
|
||||
(dom-set-attr _el-d1 "id" "d1")
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-d1 "_" "\\\"on")
|
||||
(dom-append (dom-body) _el-d1)
|
||||
(hs-activate! _el-d1)
|
||||
(dom-dispatch (dom-query-by-id "d1") "click" nil)
|
||||
(assert= (dom-text-content (dom-query-by-id "d1")) "bar")
|
||||
))
|
||||
@@ -7920,8 +7936,9 @@
|
||||
(hs-cleanup!)
|
||||
(let ((_el-d1 (dom-create-element "div")))
|
||||
(dom-set-attr _el-d1 "id" "d1")
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-d1 "_" "\\\"on")
|
||||
(dom-append (dom-body) _el-d1)
|
||||
(hs-activate! _el-d1)
|
||||
(dom-dispatch (dom-query-by-id "d1") "click" nil)
|
||||
(assert= (dom-text-content (dom-query-by-id "d1")) "bar")
|
||||
))
|
||||
@@ -7996,7 +8013,7 @@
|
||||
(deftest "handles rejected promises without hanging"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-div (dom-create-element "div")))
|
||||
(dom-set-attr _el-div "_" "on click js return Promise.reject(\"boom\") end catch e put e into my.innerHTML")
|
||||
(dom-set-attr _el-div "_" "on click js return Promise.reject(\\\"boom\\\") end catch e put e into my.innerHTML")
|
||||
(dom-append (dom-body) _el-div)
|
||||
(hs-activate! _el-div)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
@@ -8078,7 +8095,7 @@
|
||||
(deftest "the result in a when clause refers to previous command result, not element being tested"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-div (dom-create-element "div")) (_el-s1 (dom-create-element "span")) (_el-s2 (dom-create-element "span")))
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-div "_" "\\\"on")
|
||||
(dom-set-attr _el-s1 "id" "s1")
|
||||
(dom-set-attr _el-s1 "style" "display:none")
|
||||
(dom-set-inner-html _el-s1 "A")
|
||||
@@ -8088,6 +8105,7 @@
|
||||
(dom-append (dom-body) _el-div)
|
||||
(dom-append (dom-body) _el-s1)
|
||||
(dom-append (dom-body) _el-s2)
|
||||
(hs-activate! _el-div)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
(assert (dom-visible? (dom-query-by-id "s1")))
|
||||
(assert (dom-visible? (dom-query-by-id "s2")))
|
||||
@@ -8095,7 +8113,7 @@
|
||||
(deftest "the result after show...when is the matched elements"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-div (dom-create-element "div")) (_el-p (dom-create-element "p")) (_el-p2 (dom-create-element "p")) (_el-out (dom-create-element "span")))
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-div "_" "\\\"on")
|
||||
(dom-set-attr _el-p "style" "display:none")
|
||||
(dom-set-inner-html _el-p "yes")
|
||||
(dom-set-attr _el-p2 "style" "display:none")
|
||||
@@ -8106,6 +8124,7 @@
|
||||
(dom-append (dom-body) _el-p)
|
||||
(dom-append (dom-body) _el-p2)
|
||||
(dom-append (dom-body) _el-out)
|
||||
(hs-activate! _el-div)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
(assert= (dom-text-content (dom-query-by-id "out")) "some")
|
||||
))
|
||||
@@ -8234,7 +8253,7 @@
|
||||
(deftest "can have comments in attributes (triple dash)"
|
||||
(hs-cleanup!)
|
||||
(let ((_el-div (dom-create-element "div")))
|
||||
(dom-set-attr _el-div "_" "on click put \"clicked\" into my.innerHTML ---put some content into the div...")
|
||||
(dom-set-attr _el-div "_" "on click put \\\"clicked\\\" into my.innerHTML ---put some content into the div...")
|
||||
(dom-append (dom-body) _el-div)
|
||||
(hs-activate! _el-div)
|
||||
(dom-dispatch _el-div "click" nil)
|
||||
@@ -8244,7 +8263,7 @@
|
||||
(hs-cleanup!)
|
||||
(let ((_el-d1 (dom-create-element "div")))
|
||||
(dom-set-attr _el-d1 "id" "d1")
|
||||
(dom-set-attr _el-d1 "_" "on click blargh end on mouseenter put \"hovered\" into my.innerHTML")
|
||||
(dom-set-attr _el-d1 "_" "on click blargh end on mouseenter put \\\"hovered\\\" into my.innerHTML")
|
||||
(dom-append (dom-body) _el-d1)
|
||||
(hs-activate! _el-d1)
|
||||
))
|
||||
@@ -8252,7 +8271,7 @@
|
||||
(hs-cleanup!)
|
||||
(let ((_el-d1 (dom-create-element "div")))
|
||||
(dom-set-attr _el-d1 "id" "d1")
|
||||
(dom-set-attr _el-d1 "_" "on click blargh end on mouseenter also_bad end on focus put \"focused\" into my.innerHTML")
|
||||
(dom-set-attr _el-d1 "_" "on click blargh end on mouseenter also_bad end on focus put \\\"focused\\\" into my.innerHTML")
|
||||
(dom-append (dom-body) _el-d1)
|
||||
(hs-activate! _el-d1)
|
||||
))
|
||||
@@ -8264,7 +8283,7 @@
|
||||
(dom-set-attr _el-d1 "id" "d1")
|
||||
(dom-set-attr _el-d1 "_" "on click blargh end on mouseenter also_bad")
|
||||
(dom-set-attr _el-d2 "id" "d2")
|
||||
(dom-set-attr _el-d2 "_" "on click put \"clicked\" into my.innerHTML")
|
||||
(dom-set-attr _el-d2 "_" "on click put \\\"clicked\\\" into my.innerHTML")
|
||||
(dom-append (dom-body) _el-div)
|
||||
(dom-append _el-div _el-d1)
|
||||
(dom-append _el-div _el-d2)
|
||||
@@ -8351,7 +8370,7 @@
|
||||
(hs-cleanup!)
|
||||
(let ((_el-arDiv (dom-create-element "div")))
|
||||
(dom-set-attr _el-arDiv "id" "arDiv")
|
||||
(dom-set-attr _el-arDiv "_" "on click set my @data-foo to \"blue\"")
|
||||
(dom-set-attr _el-arDiv "_" "on click set my @data-foo to \\\"blue\\\"")
|
||||
(dom-set-attr _el-arDiv "data-foo" "red")
|
||||
(dom-append (dom-body) _el-arDiv)
|
||||
(hs-activate! _el-arDiv)
|
||||
@@ -8367,7 +8386,7 @@
|
||||
(dom-set-attr _el-outerDiv2 "id" "outerDiv2")
|
||||
(dom-set-attr _el-outerDiv2 "foo" "bar")
|
||||
(dom-set-attr _el-d1b "id" "d1b")
|
||||
(dom-set-attr _el-d1b "_" "on click set closest @foo to \"doh\"")
|
||||
(dom-set-attr _el-d1b "_" "on click set closest @foo to \\\"doh\\\"")
|
||||
(dom-append (dom-body) _el-outerDiv2)
|
||||
(dom-append _el-outerDiv2 _el-d1b)
|
||||
(hs-activate! _el-d1b)
|
||||
@@ -8381,7 +8400,7 @@
|
||||
(dom-add-class _el-input4 "cb")
|
||||
(dom-set-attr _el-input4 "type" "checkbox")
|
||||
(dom-set-attr _el-master "id" "master")
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-master "_" "\\\"set")
|
||||
(dom-set-attr _el-master "type" "checkbox")
|
||||
(dom-set-attr _el-master "<input[type" "checkbox]/")
|
||||
(dom-set-inner-html _el-master "in the closest")
|
||||
@@ -8394,6 +8413,7 @@
|
||||
(dom-append _el-input4 _el-master)
|
||||
(dom-append _el-master _el-table6)
|
||||
(dom-append _el-master _el-out)
|
||||
(hs-activate! _el-master)
|
||||
(dom-dispatch (dom-query-by-id "master") "click" nil)
|
||||
(assert= (dom-text-content (dom-query-by-id "out")) "2")
|
||||
))
|
||||
@@ -8413,7 +8433,7 @@
|
||||
(hs-cleanup!)
|
||||
(let ((_el-d1 (dom-create-element "div")))
|
||||
(dom-set-attr _el-d1 "id" "d1")
|
||||
(dom-set-attr _el-d1 "_" "on click if I am a Element put \"yes\" into me")
|
||||
(dom-set-attr _el-d1 "_" "on click if I am a Element put \\\"yes\\\" into me")
|
||||
(dom-append (dom-body) _el-d1)
|
||||
(hs-activate! _el-d1)
|
||||
(dom-dispatch (dom-query-by-id "d1") "click" nil)
|
||||
@@ -8423,7 +8443,7 @@
|
||||
(hs-cleanup!)
|
||||
(let ((_el-d1 (dom-create-element "div")))
|
||||
(dom-set-attr _el-d1 "id" "d1")
|
||||
(dom-set-attr _el-d1 "_" "on click if I am a Node put \"yes\" into me")
|
||||
(dom-set-attr _el-d1 "_" "on click if I am a Node put \\\"yes\\\" into me")
|
||||
(dom-append (dom-body) _el-d1)
|
||||
(hs-activate! _el-d1)
|
||||
(dom-dispatch (dom-query-by-id "d1") "click" nil)
|
||||
@@ -8433,7 +8453,7 @@
|
||||
(hs-cleanup!)
|
||||
(let ((_el-d1 (dom-create-element "div")))
|
||||
(dom-set-attr _el-d1 "id" "d1")
|
||||
(dom-set-attr _el-d1 "_" "on click if \"hello\" is not a Element put \"yes\" into me")
|
||||
(dom-set-attr _el-d1 "_" "on click if \\\"hello\\\" is not a Element put \\\"yes\\\" into me")
|
||||
(dom-append (dom-body) _el-d1)
|
||||
(hs-activate! _el-d1)
|
||||
(dom-dispatch (dom-query-by-id "d1") "click" nil)
|
||||
@@ -8573,10 +8593,11 @@
|
||||
(hs-cleanup!)
|
||||
(let ((_el-a (dom-create-element "div")) (_el-b (dom-create-element "div")))
|
||||
(dom-set-attr _el-a "id" "a")
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-a "_" "\\\"on")
|
||||
(dom-set-attr _el-b "id" "b")
|
||||
(dom-append (dom-body) _el-a)
|
||||
(dom-append (dom-body) _el-b)
|
||||
(hs-activate! _el-a)
|
||||
(dom-dispatch (dom-query-by-id "a") "click" nil)
|
||||
(assert= (dom-text-content (dom-query-by-id "a")) "yes")
|
||||
))
|
||||
@@ -8725,13 +8746,14 @@
|
||||
(dom-set-inner-html _el-span "A")
|
||||
(dom-add-class _el-span2 "b")
|
||||
(dom-set-inner-html _el-span2 "B")
|
||||
;; HS source has bare quotes or embedded HTML
|
||||
(dom-set-attr _el-button "_" "\\\"on")
|
||||
(dom-set-attr _el-out "id" "out")
|
||||
(dom-append (dom-body) _el-box)
|
||||
(dom-append _el-box _el-span)
|
||||
(dom-append _el-box _el-span2)
|
||||
(dom-append (dom-body) _el-button)
|
||||
(dom-append (dom-body) _el-out)
|
||||
(hs-activate! _el-button)
|
||||
(dom-dispatch _el-button "click" nil)
|
||||
(assert= (dom-text-content (dom-query-by-id "out")) "none")
|
||||
))
|
||||
|
||||
@@ -19,6 +19,57 @@ from collections import OrderedDict
|
||||
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
INPUT = os.path.join(PROJECT_ROOT, 'spec/tests/hyperscript-upstream-tests.json')
|
||||
OUTPUT = os.path.join(PROJECT_ROOT, 'spec/tests/test-hyperscript-behavioral.sx')
|
||||
# All gallery pages live as flat files in applications/hyperscript/ with
|
||||
# dash-joined slugs. The sx_docs routing layer only allows one level of
|
||||
# page-fn dispatch at a time (call-page in web/request-handler.sx), and the
|
||||
# hyperscript page-fn is a single-arg make-page-fn — so URLs have to be
|
||||
# /sx/(applications.(hyperscript.gallery-<theme>-<category>)), not nested.
|
||||
# The directory named "tests" is also in the server's skip_dirs list, so we
|
||||
# couldn't use /tests/ anyway.
|
||||
PAGES_DIR = os.path.join(PROJECT_ROOT, 'sx/sx/applications/hyperscript')
|
||||
GALLERY_SLUG = 'gallery'
|
||||
|
||||
|
||||
def page_slug(parts):
|
||||
"""Build a dash-joined slug from path parts (theme, category, ...)."""
|
||||
return '-'.join([GALLERY_SLUG] + [p for p in parts if p])
|
||||
|
||||
|
||||
def page_url(parts):
|
||||
"""Build the full /sx/... URL for a gallery slug."""
|
||||
return f'/sx/(applications.(hyperscript.{page_slug(parts)}))'
|
||||
|
||||
# Six themes for grouping categories on the live gallery pages.
|
||||
# Any category not listed here gets bucketed into 'misc'.
|
||||
TEST_THEMES = {
|
||||
'dom': ['add', 'remove', 'toggle', 'set', 'put', 'append', 'hide', 'empty',
|
||||
'take', 'morph', 'show', 'measure', 'swap', 'focus', 'scroll', 'reset'],
|
||||
'events': ['on', 'when', 'send', 'tell', 'init', 'bootstrap', 'socket',
|
||||
'dialog', 'wait', 'halt', 'pick', 'fetch', 'asyncError'],
|
||||
'expressions': ['comparisonOperator', 'mathOperator', 'logicalOperator',
|
||||
'asExpression', 'collectionExpressions', 'closest', 'increment',
|
||||
'queryRef', 'attributeRef', 'objectLiteral', 'no', 'default',
|
||||
'in', 'splitJoin', 'select'],
|
||||
'control': ['if', 'repeat', 'go', 'call', 'log', 'settle'],
|
||||
'reactivity': ['bind', 'live', 'liveTemplate', 'reactive-properties',
|
||||
'transition', 'resize'],
|
||||
'language': ['def', 'component', 'parser', 'js', 'scoping', 'evalStatically',
|
||||
'askAnswer', 'assignableElements',
|
||||
'relativePositionalExpression', 'cookies', 'dom-scope'],
|
||||
}
|
||||
|
||||
|
||||
def theme_for_category(category):
|
||||
for theme, cats in TEST_THEMES.items():
|
||||
if category in cats:
|
||||
return theme
|
||||
return 'misc'
|
||||
|
||||
|
||||
def sx_str(s):
|
||||
"""Escape a Python string for inclusion as an SX string literal."""
|
||||
return '"' + s.replace('\\', '\\\\').replace('"', '\\"') + '"'
|
||||
|
||||
|
||||
with open(INPUT) as f:
|
||||
raw_tests = json.load(f)
|
||||
@@ -530,9 +581,12 @@ 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
|
||||
# Convert escaped newlines/tabs to real whitespace
|
||||
hs_val = hs_val.replace('\\n', '\n').replace('\\t', ' ')
|
||||
# Preserve escaped quotes (\" → placeholder), strip remaining backslashes, restore
|
||||
hs_val = hs_val.replace('\\"', '\x00QUOT\x00')
|
||||
hs_val = hs_val.replace('\\', '')
|
||||
hs_val = hs_val.replace('\x00QUOT\x00', '\\"')
|
||||
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)
|
||||
hs_val = re.sub(r'\s*[\n\r]\s*', ' then ', hs_val)
|
||||
@@ -545,12 +599,16 @@ def process_hs_val(hs_val):
|
||||
return hs_val.strip()
|
||||
|
||||
|
||||
def emit_element_setup(lines, elements, var_names):
|
||||
def emit_element_setup(lines, elements, var_names, root='(dom-body)', indent=' '):
|
||||
"""Emit SX for creating elements, setting attributes, appending to DOM, and activating.
|
||||
|
||||
root — where top-level elements get appended. Default (dom-body); for the gallery
|
||||
card, callers pass a sandbox variable name so the HS runs inside the card, not on
|
||||
the page body.
|
||||
|
||||
Three phases to ensure correct ordering:
|
||||
1. Set attributes/content on all elements
|
||||
2. Append elements to their parents (children first, then parents to body)
|
||||
2. Append elements to their parents (children first, then roots to root)
|
||||
3. Activate HS handlers (all elements in DOM)
|
||||
"""
|
||||
hs_elements = [] # indices of elements with valid HS
|
||||
@@ -559,41 +617,41 @@ def emit_element_setup(lines, elements, var_names):
|
||||
for i, el in enumerate(elements):
|
||||
var = var_names[i]
|
||||
if el['id']:
|
||||
lines.append(f' (dom-set-attr {var} "id" "{el["id"]}")')
|
||||
lines.append(f'{indent}(dom-set-attr {var} "id" "{el["id"]}")')
|
||||
for cls in el['classes']:
|
||||
lines.append(f' (dom-add-class {var} "{cls}")')
|
||||
lines.append(f'{indent}(dom-add-class {var} "{cls}")')
|
||||
if el['hs']:
|
||||
hs_val = process_hs_val(el['hs'])
|
||||
if not hs_val:
|
||||
pass # no HS to set
|
||||
elif hs_val.startswith('"') or (hs_val.endswith('"') and '<' in hs_val):
|
||||
lines.append(f' ;; HS source has bare quotes or embedded HTML')
|
||||
lines.append(f'{indent};; HS source has bare quotes or embedded HTML')
|
||||
else:
|
||||
hs_escaped = hs_val.replace('\\', '\\\\').replace('"', '\\"')
|
||||
lines.append(f' (dom-set-attr {var} "_" "{hs_escaped}")')
|
||||
lines.append(f'{indent}(dom-set-attr {var} "_" "{hs_escaped}")')
|
||||
hs_elements.append(i)
|
||||
for aname, aval in el['attrs'].items():
|
||||
if '\\' in aval or '\n' in aval or aname.startswith('['):
|
||||
lines.append(f' ;; SKIP attr {aname} (contains special chars)')
|
||||
lines.append(f'{indent};; SKIP attr {aname} (contains special chars)')
|
||||
continue
|
||||
aval_escaped = aval.replace('"', '\\"')
|
||||
lines.append(f' (dom-set-attr {var} "{aname}" "{aval_escaped}")')
|
||||
lines.append(f'{indent}(dom-set-attr {var} "{aname}" "{aval_escaped}")')
|
||||
if el['inner']:
|
||||
inner_escaped = el['inner'].replace('\\', '\\\\').replace('"', '\\"')
|
||||
lines.append(f' (dom-set-inner-html {var} "{inner_escaped}")')
|
||||
lines.append(f'{indent}(dom-set-inner-html {var} "{inner_escaped}")')
|
||||
|
||||
# Phase 2: Append elements (children to parents, roots to body)
|
||||
# Phase 2: Append elements (children to parents, roots to `root`)
|
||||
for i, el in enumerate(elements):
|
||||
var = var_names[i]
|
||||
if el['parent_idx'] is not None:
|
||||
parent_var = var_names[el['parent_idx']]
|
||||
lines.append(f' (dom-append {parent_var} {var})')
|
||||
lines.append(f'{indent}(dom-append {parent_var} {var})')
|
||||
else:
|
||||
lines.append(f' (dom-append (dom-body) {var})')
|
||||
lines.append(f'{indent}(dom-append {root} {var})')
|
||||
|
||||
# Phase 3: Activate HS handlers (all elements now in DOM)
|
||||
for i in hs_elements:
|
||||
lines.append(f' (hs-activate! {var_names[i]})')
|
||||
lines.append(f'{indent}(hs-activate! {var_names[i]})')
|
||||
|
||||
|
||||
def generate_test_chai(test, elements, var_names, idx):
|
||||
@@ -905,6 +963,215 @@ def generate_test(test, idx):
|
||||
return generate_test_chai(test, elements, var_names, idx)
|
||||
|
||||
|
||||
# ── Live gallery pages ────────────────────────────────────────────
|
||||
|
||||
PAGE_HEADER = (
|
||||
';; AUTO-GENERATED from spec/tests/hyperscript-upstream-tests.json\n'
|
||||
';; DO NOT EDIT — regenerate with:\n'
|
||||
';; python3 tests/playwright/generate-sx-tests.py --emit-pages\n'
|
||||
)
|
||||
|
||||
# Actions/checks that we can't yet compile into a runner body emit a placeholder
|
||||
# runner that throws; the card still renders so users can see the source. This
|
||||
# keeps gallery coverage 1:1 with the JSON source of truth.
|
||||
NOT_DEMONSTRABLE = '(error "not yet runnable in gallery — see test suite")'
|
||||
|
||||
|
||||
def emit_runner_body(test, elements, var_names):
|
||||
"""Emit the body of the runner lambda that runs inside a sandbox element.
|
||||
|
||||
Returns an SX expression string or None if the test can't be reproduced
|
||||
(no HTML, unparseable action, etc.)."""
|
||||
if not elements:
|
||||
return None
|
||||
|
||||
ref = make_ref_fn(elements, var_names)
|
||||
actions = parse_action(test.get('action', ''), ref)
|
||||
checks_parsed = parse_checks(test.get('check', ''))
|
||||
|
||||
# Skip-only action list (no real action) → nothing to demonstrate
|
||||
real_actions = [a for a in actions if not a.startswith(';;')]
|
||||
if not real_actions:
|
||||
return None
|
||||
|
||||
lines = []
|
||||
bindings = ' '.join(
|
||||
f'({var_names[i]} (dom-create-element "{el["tag"]}"))'
|
||||
for i, el in enumerate(elements)
|
||||
)
|
||||
lines.append(f'(fn (sandbox)')
|
||||
lines.append(f' (let ({bindings})')
|
||||
emit_element_setup(lines, elements, var_names, root='sandbox', indent=' ')
|
||||
for a in actions:
|
||||
lines.append(f' {a}')
|
||||
for c in checks_parsed:
|
||||
sx = check_to_sx(c, ref)
|
||||
lines.append(f' {sx}')
|
||||
lines.append(' ))')
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def emit_card(test):
|
||||
"""Return an SX (~hyperscript/hs-test-card ...) call for one test."""
|
||||
name_sx = sx_str(test['name'])
|
||||
html_sx = sx_str(test.get('html', '') or '')
|
||||
action_sx = sx_str(test.get('action', '') or '')
|
||||
check_sx = sx_str(test.get('check', '') or '')
|
||||
|
||||
elements = parse_html(test.get('html', ''))
|
||||
var_names = assign_var_names(elements) if elements else []
|
||||
runner = emit_runner_body(test, elements, var_names)
|
||||
if runner is None:
|
||||
runner = f'(fn (sandbox) {NOT_DEMONSTRABLE})'
|
||||
|
||||
# :run-src is SX SOURCE TEXT — a string the island parses + evals at Run
|
||||
# time. Ordinary lambda kwargs (and even bare quoted `(fn ...)` lists)
|
||||
# end up lambda-ified by the prop pipeline and print as "<lambda>"
|
||||
# through aser, which can't round-trip. Strings do.
|
||||
run_src = sx_str(runner)
|
||||
return (
|
||||
f'(~hyperscript/hs-test-card\n'
|
||||
f' :name {name_sx}\n'
|
||||
f' :html {html_sx}\n'
|
||||
f' :action {action_sx}\n'
|
||||
f' :check {check_sx}\n'
|
||||
f' :run-src {run_src})'
|
||||
)
|
||||
|
||||
|
||||
def emit_category_page(theme, category, tests):
|
||||
"""Return SX source for one category page (all tests in that category)."""
|
||||
total = len(tests)
|
||||
runnable = sum(
|
||||
1 for t in tests
|
||||
if parse_html(t.get('html', '')) and
|
||||
any(not a.startswith(';;') for a in
|
||||
parse_action(t.get('action', ''),
|
||||
make_ref_fn(parse_html(t.get('html', '')),
|
||||
assign_var_names(parse_html(t.get('html', ''))))))
|
||||
)
|
||||
cards = '\n'.join(emit_card(t) for t in tests)
|
||||
title = f'Hyperscript: {category} ({total} tests — {runnable} runnable)'
|
||||
intro = (
|
||||
f'Live cards for the upstream {category} tests. '
|
||||
f'{runnable} of {total} are reproducible in-browser; '
|
||||
f'the remainder show their source for reference.'
|
||||
)
|
||||
return (
|
||||
PAGE_HEADER + '\n'
|
||||
f'(defcomp ()\n'
|
||||
f' (~docs/page :title {sx_str(title)}\n'
|
||||
f' (p :style "color:#57534e;margin-bottom:1rem" {sx_str(intro)})\n'
|
||||
f' (p :style "color:#78716c;font-size:0.875rem;margin-bottom:1rem"\n'
|
||||
f' "Theme: " (a :href {sx_str(page_url([theme]))}\n'
|
||||
f' :style "color:#7c3aed" {sx_str(theme)}))\n'
|
||||
f' (div :style "display:flex;flex-direction:column"\n'
|
||||
f' {cards})))\n'
|
||||
)
|
||||
|
||||
|
||||
def emit_theme_index(theme, cats_in_theme, cats_to_tests):
|
||||
"""Return SX source for a theme index page (list of its categories)."""
|
||||
total = sum(len(cats_to_tests.get(c, [])) for c in cats_in_theme)
|
||||
links = []
|
||||
for cat in cats_in_theme:
|
||||
if cat not in cats_to_tests:
|
||||
continue
|
||||
n = len(cats_to_tests[cat])
|
||||
href = page_url([theme, cat])
|
||||
links.append(
|
||||
f' (li :style "margin-bottom:0.25rem"\n'
|
||||
f' (a :href {sx_str(href)} :style "color:#7c3aed;text-decoration:underline"\n'
|
||||
f' {sx_str(cat)})\n'
|
||||
f' (span :style "color:#78716c;margin-left:0.5rem;font-size:0.875rem"\n'
|
||||
f' {sx_str(f"({n} tests)")}))'
|
||||
)
|
||||
title = f'Hyperscript tests: {theme} ({total} tests)'
|
||||
return (
|
||||
PAGE_HEADER + '\n'
|
||||
f'(defcomp ()\n'
|
||||
f' (~docs/page :title {sx_str(title)}\n'
|
||||
f' (p :style "color:#57534e;margin-bottom:1rem"\n'
|
||||
f' "Pick a category to see its live test cards.")\n'
|
||||
f' (ul :style "list-style:disc;padding-left:1.5rem"\n'
|
||||
+ '\n'.join(links) + '\n'
|
||||
f' )))\n'
|
||||
)
|
||||
|
||||
|
||||
def emit_top_index(themes_with_counts):
|
||||
"""Return SX source for the top-level /tests index page."""
|
||||
links = []
|
||||
for theme, count in themes_with_counts:
|
||||
href = page_url([theme])
|
||||
links.append(
|
||||
f' (li :style "margin-bottom:0.25rem"\n'
|
||||
f' (a :href {sx_str(href)} :style "color:#7c3aed;text-decoration:underline;font-weight:500"\n'
|
||||
f' {sx_str(theme)})\n'
|
||||
f' (span :style "color:#78716c;margin-left:0.5rem;font-size:0.875rem"\n'
|
||||
f' {sx_str(f"({count} tests)")}))'
|
||||
)
|
||||
grand_total = sum(c for _, c in themes_with_counts)
|
||||
title = f'Hyperscript test gallery ({grand_total} tests)'
|
||||
return (
|
||||
PAGE_HEADER + '\n'
|
||||
f'(defcomp ()\n'
|
||||
f' (~docs/page :title {sx_str(title)}\n'
|
||||
f' (p :style "color:#57534e;margin-bottom:1rem"\n'
|
||||
f' "Live cards for every upstream _hyperscript behavioural test. "\n'
|
||||
f' "Each card renders the HTML into a sandbox, activates the hyperscript, "\n'
|
||||
f' "dispatches the action, and runs the assertion. Pass/fail is shown "\n'
|
||||
f' "with the same runtime path as the SX test suite.")\n'
|
||||
f' (ul :style "list-style:disc;padding-left:1.5rem"\n'
|
||||
+ '\n'.join(links) + '\n'
|
||||
f' )))\n'
|
||||
)
|
||||
|
||||
|
||||
def write_page_files(categories):
|
||||
"""Write gallery files. Everything is flat in applications/hyperscript/ —
|
||||
gallery.sx (top), gallery-<theme>.sx, gallery-<theme>-<cat>.sx —
|
||||
because the /sx/ router only dispatches one level per page-fn call."""
|
||||
# Bucket categories by theme
|
||||
themed = OrderedDict() # theme -> [(cat, tests)]
|
||||
for cat, tests in categories.items():
|
||||
theme = theme_for_category(cat)
|
||||
themed.setdefault(theme, []).append((cat, tests))
|
||||
|
||||
# Remove any previous gallery-*.sx files so stale themes don't linger
|
||||
if os.path.isdir(PAGES_DIR):
|
||||
for fname in os.listdir(PAGES_DIR):
|
||||
if fname == f'{GALLERY_SLUG}.sx' or fname.startswith(f'{GALLERY_SLUG}-'):
|
||||
try: os.remove(os.path.join(PAGES_DIR, fname))
|
||||
except OSError: pass
|
||||
|
||||
themes_with_counts = []
|
||||
written = []
|
||||
for theme, cat_pairs in themed.items():
|
||||
cats_in_theme = [c for c, _ in cat_pairs]
|
||||
cats_to_tests = {c: ts for c, ts in cat_pairs}
|
||||
|
||||
for cat, tests in cat_pairs:
|
||||
fname = f'{page_slug([theme, cat])}.sx'
|
||||
with open(os.path.join(PAGES_DIR, fname), 'w') as f:
|
||||
f.write(emit_category_page(theme, cat, tests))
|
||||
written.append(fname)
|
||||
|
||||
fname = f'{page_slug([theme])}.sx'
|
||||
with open(os.path.join(PAGES_DIR, fname), 'w') as f:
|
||||
f.write(emit_theme_index(theme, cats_in_theme, cats_to_tests))
|
||||
written.append(fname)
|
||||
|
||||
themes_with_counts.append((theme, sum(len(ts) for _, ts in cat_pairs)))
|
||||
|
||||
fname = f'{GALLERY_SLUG}.sx'
|
||||
with open(os.path.join(PAGES_DIR, fname), 'w') as f:
|
||||
f.write(emit_top_index(themes_with_counts))
|
||||
written.append(fname)
|
||||
|
||||
return themed, written
|
||||
|
||||
|
||||
# ── Output generation ─────────────────────────────────────────────
|
||||
|
||||
output = []
|
||||
@@ -989,3 +1256,15 @@ print(f' Categories: {len(categories)}')
|
||||
for cat, (gen, stub) in generated_counts.items():
|
||||
marker = '' if stub == 0 else f' ({stub} stubs)'
|
||||
print(f' {cat}: {gen}{marker}')
|
||||
|
||||
|
||||
# ── Optional: live gallery pages ──────────────────────────────────
|
||||
|
||||
import sys
|
||||
if '--emit-pages' in sys.argv:
|
||||
themed, written = write_page_files(categories)
|
||||
print(f'\nGallery pages written under {PAGES_DIR} ({len(written)} files)')
|
||||
for theme, pairs in themed.items():
|
||||
cats = ', '.join(c for c, _ in pairs)
|
||||
total_t = sum(len(ts) for _, ts in pairs)
|
||||
print(f' {theme} ({total_t} tests, {len(pairs)} categories): {cats}')
|
||||
|
||||
Reference in New Issue
Block a user