From e01a3baa5b64ea10d5a67a8d54f0cbadc7089531 Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 25 Apr 2026 11:58:19 +0000 Subject: [PATCH] HS: hyperscript:before:init / :after:init events (+2 tests) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit integration.sx hs-activate! now wraps the activation block in a cancelable hyperscript:before:init event (dispatched on the el via dom-dispatch which returns the dispatchEvent boolean — true unless preventDefault was called). On success it dispatches hyperscript:after:init at the end. Both events bubble so listeners on a containing wa work-area receive them. Generator gets two hand-rolled deftests that exercise the new dispatch via hs-boot-subtree!: one captures both events into a list, the other preventDefaults before:init and asserts data-hyperscript-powered is absent. hs-upstream-core/bootstrap: 20/26 → 22/26. Smoke 0-195: 170 → 172. Remaining 4 cluster-29 tests need stricter parser error-rejection (hs-upstream-core/parser, parse-error event); larger than a single cluster budget — leave as untranslated for now. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/hyperscript/integration.sx | 13 +++++---- shared/static/wasm/sx/hs-integration.sx | 13 +++++---- spec/tests/test-hyperscript-behavioral.sx | 22 +++++++++++++-- tests/playwright/generate-sx-tests.py | 33 +++++++++++++++++++++++ 4 files changed, 69 insertions(+), 12 deletions(-) diff --git a/lib/hyperscript/integration.sx b/lib/hyperscript/integration.sx index 29931ce3..9defebce 100644 --- a/lib/hyperscript/integration.sx +++ b/lib/hyperscript/integration.sx @@ -80,11 +80,14 @@ ((src (dom-get-attr el "_")) (prev (dom-get-data el "hs-script"))) (when (and src (not (= src prev))) - (hs-log-event! "hyperscript:init") - (dom-set-data el "hs-script" src) - (dom-set-data el "hs-active" true) - (dom-set-attr el "data-hyperscript-powered" "true") - (let ((handler (hs-handler src))) (handler el)))))) + (when + (dom-dispatch el "hyperscript:before:init" nil) + (hs-log-event! "hyperscript:init") + (dom-set-data el "hs-script" src) + (dom-set-data el "hs-active" true) + (dom-set-attr el "data-hyperscript-powered" "true") + (let ((handler (hs-handler src))) (handler el)) + (dom-dispatch el "hyperscript:after:init" nil)))))) ;; ── Boot: scan entire document ────────────────────────────────── ;; Called once at page load. Finds all elements with _ attribute, diff --git a/shared/static/wasm/sx/hs-integration.sx b/shared/static/wasm/sx/hs-integration.sx index 29931ce3..9defebce 100644 --- a/shared/static/wasm/sx/hs-integration.sx +++ b/shared/static/wasm/sx/hs-integration.sx @@ -80,11 +80,14 @@ ((src (dom-get-attr el "_")) (prev (dom-get-data el "hs-script"))) (when (and src (not (= src prev))) - (hs-log-event! "hyperscript:init") - (dom-set-data el "hs-script" src) - (dom-set-data el "hs-active" true) - (dom-set-attr el "data-hyperscript-powered" "true") - (let ((handler (hs-handler src))) (handler el)))))) + (when + (dom-dispatch el "hyperscript:before:init" nil) + (hs-log-event! "hyperscript:init") + (dom-set-data el "hs-script" src) + (dom-set-data el "hs-active" true) + (dom-set-attr el "data-hyperscript-powered" "true") + (let ((handler (hs-handler src))) (handler el)) + (dom-dispatch el "hyperscript:after:init" nil)))))) ;; ── Boot: scan entire document ────────────────────────────────── ;; Called once at page load. Finds all elements with _ attribute, diff --git a/spec/tests/test-hyperscript-behavioral.sx b/spec/tests/test-hyperscript-behavioral.sx index ed8572b7..6b6d55c2 100644 --- a/spec/tests/test-hyperscript-behavioral.sx +++ b/spec/tests/test-hyperscript-behavioral.sx @@ -1396,7 +1396,17 @@ (hs-activate! _el-div) )) (deftest "fires hyperscript:before:init and hyperscript:after:init" - (error "SKIP (untranslated): fires hyperscript:before:init and hyperscript:after:init")) + (hs-cleanup!) + (let ((wa (dom-create-element "div")) + (events (list))) + (dom-listen wa "hyperscript:before:init" + (fn (e) (set! events (append events (list "before:init"))))) + (dom-listen wa "hyperscript:after:init" + (fn (e) (set! events (append events (list "after:init"))))) + (dom-set-inner-html wa "
") + (hs-boot-subtree! wa) + (assert= events (list "before:init" "after:init"))) + ) (deftest "hyperscript can have more than one action" (hs-cleanup!) (let ((_el-bar (dom-create-element "div")) (_el-div (dom-create-element "div"))) @@ -1412,7 +1422,15 @@ (assert (dom-has-class? (dom-query "div:nth-of-type(2)") "blah")) )) (deftest "hyperscript:before:init can cancel initialization" - (error "SKIP (untranslated): hyperscript:before:init can cancel initialization")) + (hs-cleanup!) + (let ((wa (dom-create-element "div"))) + (dom-listen wa "hyperscript:before:init" + (fn (e) (host-call e "preventDefault"))) + (dom-set-inner-html wa "
") + (hs-boot-subtree! wa) + (let ((d (host-call wa "querySelector" "div"))) + (assert= (host-call d "hasAttribute" "data-hyperscript-powered") false))) + ) (deftest "logAll config logs events to console" (hs-cleanup!) (hs-clear-log-captured!) diff --git a/tests/playwright/generate-sx-tests.py b/tests/playwright/generate-sx-tests.py index 22a415c8..9e9d4864 100644 --- a/tests/playwright/generate-sx-tests.py +++ b/tests/playwright/generate-sx-tests.py @@ -1881,6 +1881,39 @@ def generate_eval_only_test(test, idx): f' (assert= (eval-hs "cookies.length") 0))' ) + # Special case: cluster-29 init events. The two tractable tests both attach + # listeners to a wa container, set its innerHTML to a hyperscript fragment, + # then call `_hyperscript.processNode(wa)`. Hand-roll deftests using + # hs-boot-subtree! which now dispatches hyperscript:before:init / :after:init. + if test.get('name') == 'fires hyperscript:before:init and hyperscript:after:init': + return ( + f' (deftest "{safe_name}"\n' + f' (hs-cleanup!)\n' + f' (let ((wa (dom-create-element "div"))\n' + f' (events (list)))\n' + f' (dom-listen wa "hyperscript:before:init"\n' + f' (fn (e) (set! events (append events (list "before:init")))))\n' + f' (dom-listen wa "hyperscript:after:init"\n' + f' (fn (e) (set! events (append events (list "after:init")))))\n' + f' (dom-set-inner-html wa "
")\n' + f' (hs-boot-subtree! wa)\n' + f' (assert= events (list "before:init" "after:init")))\n' + f' )' + ) + if test.get('name') == 'hyperscript:before:init can cancel initialization': + return ( + f' (deftest "{safe_name}"\n' + f' (hs-cleanup!)\n' + f' (let ((wa (dom-create-element "div")))\n' + f' (dom-listen wa "hyperscript:before:init"\n' + f' (fn (e) (host-call e "preventDefault")))\n' + f' (dom-set-inner-html wa "
")\n' + f' (hs-boot-subtree! wa)\n' + f' (let ((d (host-call wa "querySelector" "div")))\n' + f' (assert= (host-call d "hasAttribute" "data-hyperscript-powered") false)))\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