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

@@ -2543,27 +2543,55 @@
(let
((source (if (match-kw "from") (parse-expr) nil)))
(let
((body (parse-cmd-list)))
((h-margin nil) (h-threshold nil))
(define
consume-having!
(fn
()
(cond
((and (= (tp-type) "ident") (= (tp-val) "having"))
(do
(adv!)
(cond
((and (= (tp-type) "ident") (= (tp-val) "margin"))
(do
(adv!)
(set! h-margin (parse-expr))
(consume-having!)))
((and (= (tp-type) "ident") (= (tp-val) "threshold"))
(do
(adv!)
(set! h-threshold (parse-expr))
(consume-having!)))
(true nil))))
(true nil))))
(consume-having!)
(let
((catch-clause (if (match-kw "catch") (let ((var (let ((v (tp-val))) (adv!) v)) (handler (parse-cmd-list))) (list var handler)) nil))
(finally-clause
(if (match-kw "finally") (parse-cmd-list) nil)))
(match-kw "end")
((having (if (or h-margin h-threshold) (dict "margin" h-margin "threshold" h-threshold) nil)))
(let
((parts (list (quote on) event-name)))
((body (parse-cmd-list)))
(let
((parts (if every? (append parts (list :every true)) parts)))
((catch-clause (if (match-kw "catch") (let ((var (let ((v (tp-val))) (adv!) v)) (handler (parse-cmd-list))) (list var handler)) nil))
(finally-clause
(if (match-kw "finally") (parse-cmd-list) nil)))
(match-kw "end")
(let
((parts (if flt (append parts (list :filter flt)) parts)))
((parts (list (quote on) event-name)))
(let
((parts (if source (append parts (list :from source)) parts)))
((parts (if every? (append parts (list :every true)) parts)))
(let
((parts (if catch-clause (append parts (list :catch catch-clause)) parts)))
((parts (if flt (append parts (list :filter flt)) parts)))
(let
((parts (if finally-clause (append parts (list :finally finally-clause)) parts)))
((parts (if source (append parts (list :from source)) parts)))
(let
((parts (append parts (list body))))
parts)))))))))))))))
((parts (if having (append parts (list :having having)) parts)))
(let
((parts (if catch-clause (append parts (list :catch catch-clause)) parts)))
(let
((parts (if finally-clause (append parts (list :finally finally-clause)) parts)))
(let
((parts (append parts (list body))))
parts))))))))))))))))))
(define
parse-init-feat
(fn