Files
rose-ash/spec/tests/test-hyperscript-runtime.sx
giles 802ccd23e8 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>
2026-04-22 15:36:01 +00:00

146 lines
4.8 KiB
Plaintext

;; _hyperscript runtime tests
;; Pure function tests run directly; DOM-dependent shims tested via Playwright.
;; ── Type coercion ─────────────────────────────────────────────
(defsuite
"hs-runtime-coerce"
(deftest "coerce string to Int" (assert= 42 (hs-coerce "42" "Int")))
(deftest
"coerce string to Float"
(assert= 3.14 (hs-coerce "3.14" "Float")))
(deftest "coerce number to String" (assert= "42" (hs-coerce 42 "String")))
(deftest "coerce truthy to Boolean" (assert= true (hs-coerce 1 "Boolean")))
(deftest
"coerce falsy to Boolean"
(assert= false (hs-coerce nil "Boolean")))
(deftest
"coerce value to Array wraps"
(let
((result (hs-coerce 5 "Array")))
(assert= true (list? result))
(assert= 5 (first result))))
(deftest
"coerce list to Array passes through"
(let
((result (hs-coerce (list 1 2) "Array")))
(assert= 2 (len result))))
(deftest
"unknown type passes through"
(assert= "hello" (hs-coerce "hello" "Foo"))))
;; ── Object creation ───────────────────────────────────────────
(defsuite
"hs-runtime-make"
(deftest
"make Object returns dict"
(let ((obj (hs-make "Object"))) (assert= true (dict? obj))))
(deftest
"make Array returns list"
(let
((arr (hs-make "Array")))
(assert= true (list? arr))
(assert= 0 (len arr))))
(deftest
"make Set returns list"
(let ((s (hs-make "Set"))) (assert= true (list? s))))
(deftest
"make Map returns dict"
(let ((m (hs-make "Map"))) (assert= true (dict? m)))))
;; ── Iteration ─────────────────────────────────────────────────
(defsuite
"hs-runtime-repeat"
(deftest
"repeat-times calls thunk N times"
(let
((count 0))
(hs-repeat-times 5 (fn () (set! count (+ count 1))))
(assert= 5 count)))
(deftest
"repeat-times zero does nothing"
(let
((count 0))
(hs-repeat-times 0 (fn () (set! count (+ count 1))))
(assert= 0 count)))
(deftest
"repeat-times one"
(let
((count 0))
(hs-repeat-times 1 (fn () (set! count (+ count 1))))
(assert= 1 count))))
;; ── Init ──────────────────────────────────────────────────────
(defsuite
"hs-runtime-init"
(deftest
"init calls thunk"
(let
((called false))
(hs-init (fn () (set! called true)))
(assert= true called))))
;; ── End-to-end pipeline ───────────────────────────────────────
(defsuite
"hs-runtime-e2e"
(deftest
"source → SX shape: add class"
(let
((sx (hs-to-sx-from-source "add .active to me")))
(assert= (quote dom-add-class) (first sx))
(assert= 3 (len sx))))
(deftest
"source → SX shape: sequence"
(let
((sx (hs-to-sx-from-source "add .a to me then add .b to me")))
(assert= (quote do) (first sx))
(assert= 3 (len sx))))
(deftest
"source → SX shape: on handler"
(let
((sx (hs-to-sx-from-source "on click log 'hi' end")))
(assert= (quote hs-on) (first sx))
(assert= (quote me) (nth sx 1))
(assert= "click" (nth sx 2))))
(deftest
"source → SX shape: if-else"
(let
((sx (hs-to-sx-from-source "if true log 1 else log 0 end")))
(assert= (quote if) (first sx))))
(deftest
"source → SX shape: set variable"
(let
((sx (hs-to-sx-from-source "set x to 42")))
(assert= (quote set!) (first sx))
(assert= (quote x) (nth sx 1))
(assert= 42 (nth sx 2))))
(deftest
"source → SX shape: fetch"
(let
((sx (hs-to-sx-from-source "fetch '/api'")))
(assert= (quote hs-fetch) (first sx))
(assert= "text" (nth sx 2))))
(deftest
"source → SX shape: def function"
(let
((sx (hs-to-sx-from-source "def add(a, b) return a end")))
(assert= (quote define) (first sx))
(assert= (quote add) (nth sx 1))
(assert= (quote fn) (first (nth sx 2))))))
(defsuite
"hs-handler-extensions"
(deftest
"render compiles to render-to-html"
(let
((sx (hs-to-sx-from-source "render ~badge :label 'New'")))
(assert= (quote render-to-html) (first sx))
(assert= (quote ~badge) (nth sx 1))))
(deftest
"eval inlines SX with variable access"
(let
((h (hs-handler "set x to 10 then set y to eval (+ x 5)")))
(h "el")
(assert= 15 y)))
(deftest
"eval as command in handler"
(let ((h (hs-handler "eval (set! z 99)"))) (h "el") (assert= 99 z))))