HS: fix empty/halt/morph/reset/dialog — 17 upstream tests pass

- parser `empty` no-target → (ref "me") (was bogus (sym "me"))
- parser `halt` modes distinguish: "all"/"bubbling"/"default" halt execution
  (raise hs-return), "the-event"/"the event's" only stop propagation/default.
  "'s" now matched as op token, not keyword.
- parser `get` cmd: dispatch + cmd-kw list + parse-get-cmd (parses expr with
  optional `as TYPE`). Required for `get result as JSON` in fetch chains.
- compiler empty-target for (local X): emit (set! X (hs-empty-like X)) so
  arrays/sets/maps clear the variable, not call DOM empty on the value.
- runtime hs-empty-like: container-of-same-type empty value.
- runtime hs-empty-target!: drop dead FORM branch that was short-circuiting
  to innerHTML=""; the querySelectorAll-over-inputs branch now runs.
- runtime hs-halt!: take ev param (was free `event` lookup); raise hs-return
  to stop execution unless mode is "the-event".
- runtime hs-reset!: type-aware — FORM → reset, INPUT/TEXTAREA → value/checked
  from defaults, SELECT → defaultSelected option.
- runtime hs-open!/hs-close!: toggle `open` attribute on details elements
  (not just the prop) so dom-has-attr? assertions work.
- runtime hs-coerce JSON: json-stringify dict/list (was str).
- test-runner mock: host-get on List + "length"/"size" (was only Dict);
  dom-set-attr tracks defaultChecked / defaultSelected / defaultValue;
  mock_query_all supports comma-separated selector groups.
- generator: emit boolean attrs (checked/selected/etc) even with null value;
  drop overcautious "skip HS with bare quotes or embedded HTML" guard so
  morph tests (source contains embedded <div>) emit properly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-22 15:36:01 +00:00
parent 5c66095b0f
commit 802ccd23e8
12 changed files with 1340 additions and 345 deletions

View File

@@ -16,9 +16,9 @@
"remove class from target"
(let
((sx (hs-to-sx-from-source "remove .old from #box")))
(assert= (quote dom-remove-class) (first sx))
(assert= (quote dom-query) (first (nth sx 1)))
(assert= "old" (nth sx 2))))
(assert= (quote for-each) (first sx))
(assert= (quote hs-query-all) (first (nth sx 2)))
(assert= "#box" (nth (nth sx 2) 1))))
(deftest
"toggle class"
(let
@@ -84,7 +84,7 @@
"for becomes for-each"
(let
((sx (hs-to-sx-from-source "for item in items log item end")))
(assert= (quote for-each) (first sx))
(assert= (quote hs-for-each) (first sx))
(assert= (quote fn) (first (nth sx 1)))))
(deftest
"tell rebinds me"
@@ -100,17 +100,16 @@
"hide sets display none"
(let
((sx (hs-to-sx-from-source "hide")))
(assert= (quote dom-set-style) (first sx))
(assert= (quote hs-hide!) (first sx))
(assert= (quote me) (nth sx 1))
(assert= "display" (nth sx 2))
(assert= "none" (nth sx 3))))
(assert= "display" (nth sx 2))))
(deftest
"show clears display"
(let
((sx (hs-to-sx-from-source "show")))
(assert= (quote dom-set-style) (first sx))
(assert= (quote hs-show!) (first sx))
(assert= (quote me) (nth sx 1))
(assert= "" (nth sx 3))))
(assert= "display" (nth sx 2))))
(deftest
"log passes through"
(let
@@ -121,7 +120,9 @@
"append becomes dom-append"
(let
((sx (hs-to-sx-from-source "append 'text' to me")))
(assert= (quote dom-append) (first sx)))))
(assert= (quote set!) (first sx))
(assert= (quote hs-append) (first (nth sx 2)))
(assert= "text" (nth (nth sx 2) 2)))))
;; ── Expressions ───────────────────────────────────────────────
(defsuite
@@ -138,7 +139,7 @@
"query emits dom-query"
(let
((sx (hs-to-sx (list (quote query) ".foo"))))
(assert= (quote dom-query) (first sx))
(assert= (quote hs-query-first) (first sx))
(assert= ".foo" (nth sx 1))))
(deftest
"attr emits dom-get-attr"
@@ -176,7 +177,7 @@
(let
((sx (hs-to-sx-from-source "on click from #btn add .clicked end")))
(assert= (quote hs-on) (first sx))
(assert= (quote dom-query) (first (nth sx 1)))))
(assert= (quote hs-query-first) (first (nth sx 1)))))
(deftest
"on every click"
(let
@@ -254,7 +255,10 @@
"hs-emit-return-throw"
(deftest
"return unwraps to value"
(let ((sx (hs-to-sx-from-source "return 42"))) (assert= 42 sx)))
(let
((sx (hs-to-sx-from-source "return 42")))
(assert= (quote raise) (first sx))
(assert= 42 (nth (nth sx 1) 2))))
(deftest
"throw becomes raise"
(let
@@ -306,11 +310,11 @@
(assert= (quote hs-on) (first sx))
(assert= "click" (nth sx 2))
(let
((body (nth (nth sx 3) 2)))
(assert= (quote do) (first body))
(assert= 3 (len (rest body)))
(assert= (quote hs-wait) (first (nth body 2)))
(assert= 1000 (nth (nth body 2) 1)))))
((body (nth (nth sx 3) 2)) (inner (nth (nth (nth sx 3) 2) 2)))
(assert= (quote let) (first body))
(assert= (quote let) (first inner))
(assert= (quote hs-wait) (first (nth (first (nth inner 1)) 1)))
(assert= 1000 (nth (nth (first (nth inner 1)) 1) 1)))))
(deftest
"count clicks: then chains increment and set in same handler"
(let
@@ -330,16 +334,16 @@
((sx (hs-to-sx-from-source "on click add .bounce to me then wait 1s then remove .bounce from me")))
(assert= (quote hs-on) (first sx))
(let
((body (nth (nth sx 3) 2)))
(assert= (quote do) (first body))
(assert= 3 (len (rest body)))
(assert= (quote hs-wait) (first (nth body 2)))
(assert= 1000 (nth (nth body 2) 1)))))
((inner (nth (nth (nth sx 3) 2) 2)))
(assert= (quote let) (first inner))
(assert= (quote hs-wait) (first (nth (first (nth inner 1)) 1)))
(assert= 1000 (nth (nth (first (nth inner 1)) 1) 1)))))
(deftest
"wait preserves ms value in handler"
(let
((sx (hs-to-sx-from-source "on click add .a then wait 2s then add .b")))
(let
((body (nth (nth sx 3) 2)))
(assert= (quote hs-wait) (first (nth body 2)))
(assert= 2000 (nth (nth body 2) 1))))))
((inner (nth (nth (nth sx 3) 2) 2)))
(assert= (quote let) (first inner))
(assert= (quote hs-wait) (first (nth (first (nth inner 1)) 1)))
(assert= 2000 (nth (nth (first (nth inner 1)) 1) 1))))))