HS: intersection observer mock + on intersection (+3 tests)

Applied from worktree-agent-ad6e17cbc4ea0c94b (commit 0a0fe314)
with manual re-apply onto post-cluster-26 HEAD:

- Parser: parse-on-feat collects `having margin X threshold Y`
  clauses between `from X` and the body; packs them into a
  `:having {"margin" M "threshold" T}` dict on the parts list.
- Compiler: scan-on threads a new `having-info` parameter through
  all recursions; when event-name is "intersection", wraps the
  hs-on call with `(do on-call (hs-on-intersection-attach! target
  margin threshold))`.
- Runtime: hs-on-intersection-attach! constructs an
  IntersectionObserver with {rootMargin, threshold} options and a
  callback that dispatches an "intersection" DOM event carrying
  {intersecting, entry} detail.
- Runner: HsIntersectionObserver mock fires the callback
  synchronously on observe() with isIntersecting=true so handlers
  run during activation; ignores margin/threshold (tests assert
  only that the handler fires).

Suite hs-upstream-on: 33/70 -> 36/70 (on intersection: 0/3 -> 3/3).
Smoke 0-195 unchanged at 165/195.
This commit is contained in:
2026-04-24 10:44:01 +00:00
parent cee9ae7f22
commit 0c31dd2735
7 changed files with 350 additions and 213 deletions

View File

@@ -158,7 +158,13 @@
(define
scan-on
(fn
(items source filter every? catch-info finally-info)
(items
source
filter
every?
catch-info
finally-info
having-info)
(cond
((<= (len items) 1)
(let
@@ -174,33 +180,28 @@
(let
((compiled-body (if (> (len event-refs) 0) (let ((bindings (map (fn (r) (let ((name (nth r 1))) (list (make-symbol name) (list (quote host-get) (list (quote host-get) (quote event) "detail") name)))) event-refs))) (list (quote let) bindings raw-compiled)) raw-compiled)))
(let
((wrapped-body (if catch-info (let ((var (make-symbol (nth catch-info 0))) (catch-body (hs-to-sx (nth catch-info 1)))) (if finally-info (list (quote do) (list (quote guard) (list var (list true catch-body)) compiled-body) (hs-to-sx finally-info)) (list (quote guard) (list var (list true catch-body)) compiled-body))) (if finally-info (list (quote do) compiled-body (hs-to-sx finally-info)) compiled-body)))
(handler
(let
((uses-the-result? (fn (expr) (cond ((= expr (quote the-result)) true) ((list? expr) (some (fn (x) (uses-the-result? x)) expr)) (true false)))))
((wrapped-body (if catch-info (let ((var (make-symbol (nth catch-info 0))) (catch-body (hs-to-sx (nth catch-info 1)))) (if finally-info (list (quote do) (list (quote guard) (list var (list true catch-body)) compiled-body) (hs-to-sx finally-info)) (list (quote guard) (list var (list true catch-body)) compiled-body))) (if finally-info (list (quote do) compiled-body (hs-to-sx finally-info)) compiled-body))))
(let
((handler (let ((uses-the-result? (fn (expr) (cond ((= expr (quote the-result)) true) ((list? expr) (some (fn (x) (uses-the-result? x)) expr)) (true false))))) (list (quote fn) (list (quote event)) (if (uses-the-result? wrapped-body) (list (quote let) (list (list (quote the-result) nil)) wrapped-body) wrapped-body)))))
(let
((on-call (if every? (list (quote hs-on-every) target event-name handler) (list (quote hs-on) target event-name handler))))
(if
(= event-name "intersection")
(list
(quote fn)
(list (quote event))
(if
(uses-the-result? wrapped-body)
(list
(quote let)
(list
(list (quote the-result) nil))
wrapped-body)
wrapped-body)))))
(if
every?
(list
(quote hs-on-every)
target
event-name
handler)
(list
(quote hs-on)
target
event-name
handler))))))))))
(quote do)
on-call
(list
(quote hs-on-intersection-attach!)
target
(if
having-info
(get having-info "margin")
nil)
(if
having-info
(get having-info "threshold")
nil)))
on-call)))))))))))
((= (first items) :from)
(scan-on
(rest (rest items))
@@ -208,7 +209,8 @@
filter
every?
catch-info
finally-info))
finally-info
having-info))
((= (first items) :filter)
(scan-on
(rest (rest items))
@@ -216,7 +218,8 @@
(nth items 1)
every?
catch-info
finally-info))
finally-info
having-info))
((= (first items) :every)
(scan-on
(rest (rest items))
@@ -224,7 +227,8 @@
filter
true
catch-info
finally-info))
finally-info
having-info))
((= (first items) :catch)
(scan-on
(rest (rest items))
@@ -232,7 +236,8 @@
filter
every?
(nth items 1)
finally-info))
finally-info
having-info))
((= (first items) :finally)
(scan-on
(rest (rest items))
@@ -240,6 +245,16 @@
filter
every?
catch-info
(nth items 1)
having-info))
((= (first items) :having)
(scan-on
(rest (rest items))
source
filter
every?
catch-info
finally-info
(nth items 1)))
(true
(scan-on
@@ -248,8 +263,9 @@
filter
every?
catch-info
finally-info)))))
(scan-on (rest parts) nil nil false nil nil)))))
finally-info
having-info)))))
(scan-on (rest parts) nil nil false nil nil nil)))))
(define
emit-send
(fn