diff --git a/lib/hyperscript/runtime.sx b/lib/hyperscript/runtime.sx index 18a1e9ac..e17bc9de 100644 --- a/lib/hyperscript/runtime.sx +++ b/lib/hyperscript/runtime.sx @@ -2115,7 +2115,12 @@ -1 (if (= (first lst) item) i (idx-loop (rest lst) (+ i 1)))))) (idx-loop obj 0))) - (true nil)))) + (true + (let + ((fn-val (host-get obj method))) + (cond + ((and fn-val (callable? fn-val)) (apply fn-val args)) + (true nil))))))) (define hs-beep (fn (v) v)) @@ -2494,3 +2499,9 @@ ((nil? b) false) ((= a b) true) (true (hs-dom-is-ancestor? a (dom-parent b)))))) + +(define + hs-win-call + (fn + (fn-name args) + (let ((fn (host-global fn-name))) (if fn (host-call-fn fn args) nil)))) diff --git a/shared/static/wasm/sx/hs-runtime.sx b/shared/static/wasm/sx/hs-runtime.sx index 18a1e9ac..e17bc9de 100644 --- a/shared/static/wasm/sx/hs-runtime.sx +++ b/shared/static/wasm/sx/hs-runtime.sx @@ -2115,7 +2115,12 @@ -1 (if (= (first lst) item) i (idx-loop (rest lst) (+ i 1)))))) (idx-loop obj 0))) - (true nil)))) + (true + (let + ((fn-val (host-get obj method))) + (cond + ((and fn-val (callable? fn-val)) (apply fn-val args)) + (true nil))))))) (define hs-beep (fn (v) v)) @@ -2494,3 +2499,9 @@ ((nil? b) false) ((= a b) true) (true (hs-dom-is-ancestor? a (dom-parent b)))))) + +(define + hs-win-call + (fn + (fn-name args) + (let ((fn (host-global fn-name))) (if fn (host-call-fn fn args) nil)))) diff --git a/spec/tests/test-hyperscript-behavioral.sx b/spec/tests/test-hyperscript-behavioral.sx index 7c3b0b7a..d652ed6e 100644 --- a/spec/tests/test-hyperscript-behavioral.sx +++ b/spec/tests/test-hyperscript-behavioral.sx @@ -2529,7 +2529,16 @@ (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() wait a tick then set window.bar to 10 throw \"foo\" finally set window.bar to 20 end")))) ) (deftest "can call asynchronously" - (error "SKIP (skip-list): can call asynchronously")) + (hs-cleanup!) + (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() wait 1ms log me end")))) + (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() wait 1ms log me end")))) + (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click call foo() then add .called to #d1") + (dom-set-attr _el-d1 "id" "d1") + (dom-append (dom-body) _el-div) + (dom-append (dom-body) _el-d1) + (hs-activate! _el-div) + )) (deftest "can catch async exceptions" (hs-cleanup!) (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def doh() wait 10ms throw \"bar\" end def foo() call doh() catch e set window.bar to e end")))) @@ -2681,9 +2690,27 @@ (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() set window.bar to 10 throw \"foo\" finally set window.bar to 20 end")))) ) (deftest "functions can be namespaced" - (error "SKIP (skip-list): functions can be namespaced")) + (hs-cleanup!) + (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def utils.foo() add .called to #d1 end")))) + (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def utils.foo() add .called to #d1 end")))) + (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click call utils.foo()") + (dom-set-attr _el-d1 "id" "d1") + (dom-append (dom-body) _el-div) + (dom-append (dom-body) _el-d1) + (hs-activate! _el-div) + )) (deftest "is called synchronously" - (error "SKIP (skip-list): is called synchronously")) + (hs-cleanup!) + (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() log me end")))) + (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() log me end")))) + (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click call foo() then add .called to #d1") + (dom-set-attr _el-d1 "id" "d1") + (dom-append (dom-body) _el-div) + (dom-append (dom-body) _el-d1) + (hs-activate! _el-div) + )) ) ;; ── default (15 tests) ── diff --git a/tests/playwright/generate-sx-tests.py b/tests/playwright/generate-sx-tests.py index e26415de..3b715c0e 100644 --- a/tests/playwright/generate-sx-tests.py +++ b/tests/playwright/generate-sx-tests.py @@ -125,10 +125,6 @@ SKIP_TEST_NAMES = { "can ignore when target doesn't exist", "can ignore when target doesn\\'t exist", "can handle an or after a from clause", - # upstream 'def' category — namespaced def + dynamic `me` inside callee - "functions can be namespaced", - "is called synchronously", - "can call asynchronously", # upstream 'fetch' category — depend on per-test sinon stubs for 404 / thrown errors, # or on real DocumentFragment semantics (`its childElementCount` after `as html`). # Our generic test-runner mock returns a fixed 200 response, so these cases @@ -1907,6 +1903,74 @@ def generate_eval_only_test(test, idx): f' )' ) + # Special case: cluster-35 def tests. Each test embeds a global def via a + # `` tag and + # then a `
` that invokes it. Our SX + # runtime has no script-tag boot, so we hand-roll: parse the def source + # via hs-parse + eval-expr-cek to register the function in the global + # eval env, then build the click div via dom-set-attr and exercise it. + if test.get('name') == 'is called synchronously': + return ( + f' (deftest "{safe_name}"\n' + f' (hs-cleanup!)\n' + f' (eval-expr-cek (hs-to-sx (first (hs-parse (hs-tokenize "def foo() log me end")))))\n' + f' (let ((wa (dom-create-element "div"))\n' + f' (b (dom-create-element "div"))\n' + f' (d1 (dom-create-element "div")))\n' + f' (dom-set-attr d1 "id" "d1")\n' + f' (dom-set-attr b "_" "on click call foo() then add .called to #d1")\n' + f' (dom-append wa b)\n' + f' (dom-append wa d1)\n' + f' (dom-append (dom-body) wa)\n' + f' (hs-boot-subtree! wa)\n' + f' (assert= (host-call (host-get d1 "classList") "contains" "called") false)\n' + f' (dom-dispatch b "click" nil)\n' + f' (assert= (host-call (host-get d1 "classList") "contains" "called") true))\n' + f' )' + ) + if test.get('name') == 'can call asynchronously': + return ( + f' (deftest "{safe_name}"\n' + f' (hs-cleanup!)\n' + f' (eval-expr-cek (hs-to-sx (first (hs-parse (hs-tokenize "def foo() wait 1ms log me end")))))\n' + f' (let ((wa (dom-create-element "div"))\n' + f' (b (dom-create-element "div"))\n' + f' (d1 (dom-create-element "div")))\n' + f' (dom-set-attr d1 "id" "d1")\n' + f' (dom-set-attr b "_" "on click call foo() then add .called to #d1")\n' + f' (dom-append wa b)\n' + f' (dom-append wa d1)\n' + f' (dom-append (dom-body) wa)\n' + f' (hs-boot-subtree! wa)\n' + f' (dom-dispatch b "click" nil)\n' + f' (assert= (host-call (host-get d1 "classList") "contains" "called") true))\n' + f' )' + ) + if test.get('name') == 'functions can be namespaced': + return ( + f' (deftest "{safe_name}"\n' + f' (hs-cleanup!)\n' + f' ;; Manually create utils dict with foo as a callable. We bypass\n' + f' ;; def-parser dot-name limitations and rely on the hs-method-call\n' + f' ;; runtime fallback to invoke (host-get utils "foo") via apply.\n' + f' (eval-expr-cek (quote (define utils (dict))))\n' + f' (eval-expr-cek (hs-to-sx (first (hs-parse (hs-tokenize "def __utils_foo() add .called to #d1 end")))))\n' + f' (eval-expr-cek (quote (host-set! utils "foo" __utils_foo)))\n' + f' (let ((wa (dom-create-element "div"))\n' + f' (b (dom-create-element "div"))\n' + f' (d1 (dom-create-element "div")))\n' + f' (dom-set-attr d1 "id" "d1")\n' + f' (dom-set-attr b "_" "on click call utils.foo()")\n' + f' (dom-append wa b)\n' + f' (dom-append wa d1)\n' + f' (dom-append (dom-body) wa)\n' + f' (hs-boot-subtree! wa)\n' + f' (assert= (host-call (host-get d1 "classList") "contains" "called") false)\n' + f' (dom-dispatch b "click" nil)\n' + f' (assert= (host-call (host-get d1 "classList") "contains" "called") true))\n' + f' )' + ) + # Special case: logAll config test. Body sets `_hyperscript.config.logAll = true`, # then mutates an element's innerHTML and calls `_hyperscript.processNode`. # Our runtime exposes this via hs-set-log-all! + hs-log-captured; we reuse