HS test generator: window/document binding + JS function-expr setups

Three related changes for the `evaluate(() => window.X = Y)` setup pattern:

1. extract_window_setups now also matches the single-expression form
   `evaluate(() => window.X = Y)` (no braces), in addition to the
   block form `evaluate(() => { window.X = Y; ... })`.

2. js_expr_to_sx now recognises `function(args) { return X; }` (and
   `function(args) { X; }`) in addition to arrow functions, so e.g.
   `window.select2 = function(){ return "select2"; }` translates to
   `(fn () "select2")`.

3. generate_test_chai / generate_test_pw (HTML+click test generators)
   inject `(host-set! (host-global "window") "X" <sx>)` for each window
   setup found in the test body, so HS code that reads `window.X` sees
   the right value at activation time.

4. Test-helper preamble now defines `window` and `document` as
   `(host-global "window")` / `(host-global "document")`, so HS
   expressions like `window.tmp` resolve through the host instead of
   erroring on an unbound `window` symbol.

Net effect on suites smoke-tested: nominal, because most affected tests
hit a separate `if/then/else` parser bug — the `then` keyword inserter
in process_hs_val turns multi-line if blocks into ones the HS parser
collapses to "always run the body". Fixing that is the next iteration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 11:58:32 +00:00
parent adb06ed1fd
commit 7330bc1a36
2 changed files with 83 additions and 13 deletions

View File

@@ -4,6 +4,11 @@
;; ── Test helpers ──────────────────────────────────────────────────
;; Bind `window` and `document` as plain SX symbols so HS code that
;; references them (e.g. `window.tmp`) can resolve through the host.
(define window (host-global "window"))
(define document (host-global "document"))
(define hs-test-el
(fn (tag hs-src)
(let ((el (dom-create-element tag)))
@@ -320,6 +325,7 @@
(defsuite "hs-upstream-append"
(deftest "append preserves existing content rather than overwriting it"
(hs-cleanup!)
(host-set! (host-global "window") "clicks" 0)
(let ((_el-div (dom-create-element "div")) (_el-btn1 (dom-create-element "button")))
(dom-set-attr _el-div "_" "on click append '<a>New Content</a>' to me")
(dom-set-attr _el-btn1 "id" "btn1")
@@ -610,6 +616,7 @@
))
(deftest "install throws when the path resolves to a non-function"
(hs-cleanup!)
(host-set! (host-global "window") "NotABehavior" {:hello "world"})
(let ((_el-div (dom-create-element "div")))
(dom-set-attr _el-div "_" "install NotABehavior")
(dom-append (dom-body) _el-div)
@@ -1139,6 +1146,7 @@
))
(deftest "can call functions w/ dollar signs"
(hs-cleanup!)
(host-set! (host-global "window") "called" false)
(let ((_el-div (dom-create-element "div")))
(dom-set-attr _el-div "_" "on click call $()")
(dom-append (dom-body) _el-div)
@@ -1147,6 +1155,7 @@
))
(deftest "can call functions w/ underscores"
(hs-cleanup!)
(host-set! (host-global "window") "called" false)
(let ((_el-div (dom-create-element "div")))
(dom-set-attr _el-div "_" "on click call global_function()")
(dom-append (dom-body) _el-div)
@@ -1155,6 +1164,7 @@
))
(deftest "can call global javascript functions"
(hs-cleanup!)
(host-set! (host-global "window") "calledWith" null)
(let ((_el-div (dom-create-element "div")))
(dom-set-attr _el-div "_" "on click call globalFunction(\"foo\")")
(dom-append (dom-body) _el-div)
@@ -1172,6 +1182,7 @@
))
(deftest "can call no argument functions"
(hs-cleanup!)
(host-set! (host-global "window") "called" false)
(let ((_el-div (dom-create-element "div")))
(dom-set-attr _el-div "_" "on click call globalFunction()")
(dom-append (dom-body) _el-div)
@@ -1184,6 +1195,7 @@
(defsuite "hs-upstream-core/api"
(deftest "processNodes does not reinitialize a node already processed"
(hs-cleanup!)
(host-set! (host-global "window") "global_int" 0)
(let ((_el-div (dom-create-element "div")))
(dom-set-attr _el-div "_" "on click set window.global_int to window.global_int + 1")
(dom-append (dom-body) _el-div)
@@ -1226,6 +1238,7 @@
(defsuite "hs-upstream-core/bootstrap"
(deftest "can call functions"
(hs-cleanup!)
(host-set! (host-global "window") "calledWith" null)
(let ((_el-div (dom-create-element "div")))
(dom-set-attr _el-div "_" "on click call globalFunction(\"foo\")")
(dom-append (dom-body) _el-div)
@@ -1963,6 +1976,7 @@
))
(deftest "can invoke functions w/ numbers in name"
(hs-cleanup!)
(host-set! (host-global "window") "select2" (fn () "select2"))
(let ((_el-button (dom-create-element "button")))
(dom-set-attr _el-button "_" "on click put select2() into me")
(dom-append (dom-body) _el-button)
@@ -2354,6 +2368,7 @@
))
(deftest "set favors local variables over global variables"
(hs-cleanup!)
(host-set! (host-global "window") "foo" 12)
(let ((_el-d1 (dom-create-element "div")))
(dom-set-attr _el-d1 "id" "d1")
(dom-set-attr _el-d1 "_" "on click 1 set foo to 20 then set @out to foo")
@@ -5207,7 +5222,7 @@
(assert= (eval-hs-locals "getObj().greet()" (list (list (quote getObj) (fn () {:greet (fn () "hi")})))) "hi")
)
(deftest "can invoke function on object"
(assert= (eval-hs-locals "obj.getValue()" (list (list (quote obj) {:value "foo" :getValue "function () { return this.value }"}))) "foo")
(assert= (eval-hs-locals "obj.getValue()" (list (list (quote obj) {:value "foo" :getValue (fn () (host-get this "value"))}))) "foo")
)
(deftest "can invoke function on object w/ async arg"
(error "SKIP (untranslated): can invoke function on object w/ async arg"))
@@ -5229,6 +5244,7 @@
)
(deftest "can pass multiple arguments"
(hs-cleanup!)
(host-set! (host-global "window") "add" (fn (a b c) (+ a b c)))
(let ((_el-div (dom-create-element "div")))
(dom-set-attr _el-div "_" "on click put add(1, 2, 3) into me")
(dom-append (dom-body) _el-div)
@@ -6376,10 +6392,10 @@
(assert= (eval-hs "`${1 + 2}`") "3")
)
(deftest "string templates work w/ props"
(assert= (eval-hs "`$window.foo`") "foo")
(assert= (eval-hs-locals "`$window.foo`" (list (list (quote foo) "foo"))) "foo")
)
(deftest "string templates work w/ props w/ braces"
(assert= (eval-hs "`${window.foo}`") "foo")
(assert= (eval-hs-locals "`${window.foo}`" (list (list (quote foo) "foo"))) "foo")
)
)
@@ -7247,6 +7263,7 @@
(error "SKIP (skip-list): don't throw passes through 404 response"))
(deftest "submits the fetch parameters to the event handler"
(hs-cleanup!)
(host-set! (host-global "window") "headerCheckPassed" false)
(let ((_el-div (dom-create-element "div")))
(dom-set-attr _el-div "_" "on click fetch \"/test\" {headers: {\"X-CustomHeader\": \"foo\"}} then put it into my.innerHTML end")
(dom-append (dom-body) _el-div)
@@ -7703,6 +7720,8 @@
))
(deftest "if on new line does not join w/ else"
(hs-cleanup!)
(host-set! (host-global "window") "tmp" false)
(host-set! (host-global "window") "tmp" true)
(let ((_el-div (dom-create-element "div")))
(dom-set-attr _el-div "_" "on click if window.tmp then else then if window.tmp then end put \"foo\" into me then end")
(dom-append (dom-body) _el-div)
@@ -7723,6 +7742,8 @@
))
(deftest "if properly supports nested if statements and end block"
(hs-cleanup!)
(host-set! (host-global "window") "tmp" false)
(host-set! (host-global "window") "tmp" true)
(let ((_el-div (dom-create-element "div")))
(dom-set-attr _el-div "_" "on click if window.tmp then put \"foo\" into me then else if not window.tmp then // do nothing then end catch e then // just here for the parsing... then")
(dom-append (dom-body) _el-div)
@@ -8155,6 +8176,7 @@
(defsuite "hs-upstream-js"
(deftest "can access values from _hyperscript"
(hs-cleanup!)
(host-set! (host-global "window") "testSuccess" false)
(let ((_el-div (dom-create-element "div")))
(dom-set-attr _el-div "_" "on click set t to true then js(t) window.testSuccess = t end")
(dom-append (dom-body) _el-div)
@@ -8163,6 +8185,7 @@
))
(deftest "can deal with empty input list"
(hs-cleanup!)
(host-set! (host-global "window") "testSuccess" false)
(let ((_el-div (dom-create-element "div")))
(dom-set-attr _el-div "_" "on click js() window.testSuccess = true end")
(dom-append (dom-body) _el-div)
@@ -8207,6 +8230,7 @@
))
(deftest "can run js"
(hs-cleanup!)
(host-set! (host-global "window") "testSuccess" false)
(let ((_el-div (dom-create-element "div")))
(dom-set-attr _el-div "_" "on click js window.testSuccess = true end")
(dom-append (dom-body) _el-div)
@@ -8215,6 +8239,7 @@
))
(deftest "can run js at the top level"
(hs-cleanup!)
(host-set! (host-global "window") "testSuccess" false)
(let ((_el-script (dom-create-element "script")))
(dom-set-attr _el-script "type" "text/hyperscript")
(dom-append (dom-body) _el-script)
@@ -8630,6 +8655,7 @@
))
(deftest "morph preserves element identity"
(hs-cleanup!)
(host-set! (host-global "window") "_savedRef" (host-call document "querySelector" "#target"))
(let ((_el-target (dom-create-element "div")) (_el-go (dom-create-element "button")))
(dom-set-attr _el-target "id" "target")
(dom-set-inner-html _el-target "old")
@@ -8644,6 +8670,7 @@
))
(deftest "morph preserves matched child identity"
(hs-cleanup!)
(host-set! (host-global "window") "_savedChild" (host-call document "querySelector" "#child"))
(let ((_el-target (dom-create-element "div")) (_el-child (dom-create-element "div")) (_el-go (dom-create-element "button")))
(dom-set-attr _el-target "id" "target")
(dom-set-attr _el-child "id" "child")
@@ -9262,6 +9289,7 @@
))
(deftest "Can use functions defined outside of the current element"
(hs-cleanup!)
(host-set! (host-global "window") "foo" (fn () "foo"))
(let ((_el-d1 (dom-create-element "div")))
(dom-set-attr _el-d1 "id" "d1")
(dom-set-attr _el-d1 "_" "on click foo() then put result into my bar")
@@ -9271,6 +9299,7 @@
))
(deftest "Can use indirect functions with a function root"
(hs-cleanup!)
(host-set! (host-global "window") "bar" (fn () {:foo (fn () "foo")}))
(let ((_el-d1 (dom-create-element "div")))
(dom-set-attr _el-d1 "id" "d1")
(dom-set-attr _el-d1 "_" "on click bar().foo() then put the result into my bar")
@@ -9280,6 +9309,7 @@
))
(deftest "Can use indirect functions with a symbol root"
(hs-cleanup!)
(host-set! (host-global "window") "bar" {:foo (fn () "foo")})
(let ((_el-d1 (dom-create-element "div")))
(dom-set-attr _el-d1 "id" "d1")
(dom-set-attr _el-d1 "_" "on click bar.foo() then put the result into my bar")
@@ -9289,6 +9319,7 @@
))
(deftest "Can use nested indirect functions with a symbol root"
(hs-cleanup!)
(host-set! (host-global "window") "bar" (fn () {:foo (fn () "foo")}))
(let ((_el-d1 (dom-create-element "div")))
(dom-set-attr _el-d1 "id" "d1")
(dom-set-attr _el-d1 "_" "on click window.bar().foo() then put the result into my bar")
@@ -10943,6 +10974,7 @@
))
(deftest "can set many properties at once with object literal"
(hs-cleanup!)
(host-set! (host-global "window") "obj" {:foo 1})
(let ((_el-div (dom-create-element "div")))
(dom-set-attr _el-div "_" "on click set {bar: 2, baz: 3} on obj")
(dom-append (dom-body) _el-div)
@@ -11780,6 +11812,9 @@
))
(deftest "async expressions in a loop resolve correctly"
(hs-cleanup!)
(host-set! (host-global "window") "asyncFn" (fn (v) (host-call Promise "resolve" "got:\" + v)
const tmpl = document.querySelector('#work-area script[type=\"text/hyperscript-template\"]')
return _hyperscript(\"render tmpl with items: items, asyncFn: asyncFn then put it into window.res" {:locals "{ items: [1, 2, 3], asyncFn: window.asyncFn, tmpl }"})))
(let ((_el-script (dom-create-element "script")))
(dom-set-attr _el-script "type" "text/hyperscript-template")
(dom-set-inner-html _el-script "#for x in items