From 596f0e07666401affcf5c51e478df4bffcb12c61 Mon Sep 17 00:00:00 2001 From: PJ Date: Thu, 13 Aug 2026 00:42:02 +0530 Subject: [PATCH] test(browser): drive a canvas-under-shadow-root fixture end to end --- test/browser/browser_test.go | 12 +++++++ test/browser/testdata/shadow/index.html | 43 +++++++++++++++++++++++++ test/browser/testdata/shadow/spec.ts | 17 ++++++++++ 3 files changed, 72 insertions(+) create mode 100644 test/browser/testdata/shadow/index.html create mode 100644 test/browser/testdata/shadow/spec.ts diff --git a/test/browser/browser_test.go b/test/browser/browser_test.go index e603d6c..b2b0b56 100644 --- a/test/browser/browser_test.go +++ b/test/browser/browser_test.go @@ -53,6 +53,18 @@ func TestBrowserCounterInvariantHolds(t *testing.T) { } } +// TestBrowserShadowDOMIsReachable drives a page whose entire UI (canvas, the +// button over it, and the counter) lives inside a shadow root, the shape +// Compose for Web produces. Both the enumeration and the selector lookup have +// to cross the boundary for the counter to move at all, so the property firing +// is the end-to-end evidence. +func TestBrowserShadowDOMIsReachable(t *testing.T) { + violations := runFixture(t, "shadow") + if !slices.Contains(violations, "counterNeverMoves") { + t.Fatalf("nothing inside the shadow root was ever tapped; violations=%v", violations) + } +} + // runFixture serves the named testdata case over an in-process file server, // drives it through headless Chrome with a fixed seed and a bounded step count, // and returns every property name that was ever reported violated. diff --git a/test/browser/testdata/shadow/index.html b/test/browser/testdata/shadow/index.html new file mode 100644 index 0000000..6e21e34 --- /dev/null +++ b/test/browser/testdata/shadow/index.html @@ -0,0 +1,43 @@ + + + + + shadow + + + +
+ + + diff --git a/test/browser/testdata/shadow/spec.ts b/test/browser/testdata/shadow/spec.ts new file mode 100644 index 0000000..c70f37b --- /dev/null +++ b/test/browser/testdata/shadow/spec.ts @@ -0,0 +1,17 @@ +import { always, extract, taps } from "@sanderling/spec"; + +const bumps = extract((s) => { + const el = s.ax.find({ id: "count" }); + return el ? parseInt(el.text, 10) || 0 : 0; +}).named("bumps"); + +// The button, the counter that records the taps, and the canvas that services +// them all live inside a shadow root. A host that stops at the shadow boundary +// enumerates no target to tap and resolves no selector to read, so the counter +// can never leave zero: this property firing IS the evidence that both the +// enumeration and the selector lookup crossed the boundary. +const counterNeverMoves = always(() => bumps.current === 0); + +export const properties = { counterNeverMoves }; + +export const actionsRoot = taps;