Skip to content

Commit be33afb

Browse files
authored
fix(core): align sub-composition scoping across runtime and bundler (#897)
1 parent 2355d50 commit be33afb

6 files changed

Lines changed: 1565 additions & 133 deletions

File tree

packages/core/src/compiler/compositionScoping.test.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,33 @@ body { margin: 0; }
7575
expect(fakeWindow.__captured).toEqual({ title: "Pro", price: "$29" });
7676
});
7777

78+
it("scoped getVariables reads from the runtime composition id when it differs", () => {
79+
const { document } = parseHTML(`<div data-composition-id="scene"></div>`);
80+
const fakeWindow: Record<string, unknown> = {
81+
document,
82+
__timelines: {},
83+
__hfVariablesByComp: {
84+
scene: { title: "Wrong" },
85+
scene__hf1: { title: "Right" },
86+
},
87+
__hyperframes: {
88+
getVariables: () => ({ title: "TOP-LEVEL-LEAK" }),
89+
fitTextFontSize: () => undefined,
90+
},
91+
};
92+
const wrapped = wrapScopedCompositionScript(
93+
`window.__captured = __hyperframes.getVariables();`,
94+
"scene",
95+
"[HyperFrames] composition script error:",
96+
undefined,
97+
"scene__hf1",
98+
);
99+
100+
new Function("window", wrapped)(fakeWindow);
101+
102+
expect(fakeWindow.__captured).toEqual({ title: "Right" });
103+
});
104+
78105
it("scoped getVariables returns {} when __hfVariablesByComp has no entry for the comp", () => {
79106
const { document } = parseHTML(`<div data-composition-id="missing"></div>`);
80107
const fakeWindow: Record<string, unknown> = {
@@ -208,6 +235,124 @@ window.__selectedComp =
208235
expect(fakeWindow.__selectedComp).toBe("scene-b");
209236
});
210237

238+
it("scopes authored root id lookups after the flattened root drops its literal id", () => {
239+
const { document } = parseHTML(`
240+
<div data-composition-id="scene">
241+
<div data-hf-authored-id="scene-root">
242+
<h1 class="title">Scene</h1>
243+
</div>
244+
</div>
245+
`);
246+
const fakeWindow = {
247+
document,
248+
__selectedTitle: "",
249+
__timelines: {},
250+
};
251+
const wrapped = wrapScopedCompositionScript(
252+
`
253+
window.__selectedTitle =
254+
document.getElementById("scene-root")
255+
?.querySelector(".title")
256+
?.textContent || "missing";
257+
`,
258+
"scene",
259+
"[HyperFrames] composition script error:",
260+
undefined,
261+
"scene",
262+
"scene-root",
263+
);
264+
265+
new Function("window", wrapped)(fakeWindow);
266+
267+
expect(fakeWindow.__selectedTitle).toBe("Scene");
268+
});
269+
270+
it("does not rewrite authored root hash text inside CSS attribute values", () => {
271+
const scoped = scopeCssToComposition(
272+
'a[href="#scene-root"] { color: red; }',
273+
"scene",
274+
undefined,
275+
"scene-root",
276+
);
277+
278+
expect(scoped).toContain('[data-composition-id="scene"] a[href="#scene-root"]');
279+
expect(scoped).not.toContain('[href="[data-hf-authored-id=');
280+
});
281+
282+
it("does not rewrite authored root hash text inside querySelector attribute values", () => {
283+
const { document } = parseHTML(`
284+
<div data-composition-id="scene">
285+
<a class="jump" href="#scene-root">Jump</a>
286+
<div data-hf-authored-id="scene-root"></div>
287+
</div>
288+
`);
289+
const fakeWindow = {
290+
document,
291+
__selectedHref: "",
292+
__timelines: {},
293+
};
294+
const wrapped = wrapScopedCompositionScript(
295+
`
296+
window.__selectedHref =
297+
document.querySelector('a[href="#scene-root"]')
298+
?.getAttribute("href") || "missing";
299+
`,
300+
"scene",
301+
"[HyperFrames] composition script error:",
302+
undefined,
303+
"scene",
304+
"scene-root",
305+
);
306+
307+
new Function("window", wrapped)(fakeWindow);
308+
309+
expect(fakeWindow.__selectedHref).toBe("#scene-root");
310+
});
311+
312+
it("normalizes gsap.utils.selector() selectors for authored root ids and root timing attrs", () => {
313+
const { document } = parseHTML(`
314+
<div data-composition-id="scene" data-start="0">
315+
<div data-hf-authored-id="scene-root">
316+
<h1 class="title">Scene</h1>
317+
</div>
318+
</div>
319+
<div data-composition-id="other" data-start="0">
320+
<div data-hf-authored-id="scene-root">
321+
<h1 class="title">Other</h1>
322+
</div>
323+
</div>
324+
`);
325+
const fakeWindow = {
326+
document,
327+
__selectedRootCount: 0,
328+
__selectedTimedCount: 0,
329+
__selectedTitle: "",
330+
__timelines: {},
331+
gsap: {
332+
utils: {},
333+
},
334+
};
335+
const wrapped = wrapScopedCompositionScript(
336+
`
337+
const select = gsap.utils.selector(document.querySelector('[data-composition-id="scene"]'));
338+
window.__selectedRootCount = select('#scene-root').length;
339+
window.__selectedTimedCount = select('[data-composition-id="scene"][data-start="0"] .title').length;
340+
window.__selectedTitle = select('#scene-root .title')[0]?.textContent || "missing";
341+
`,
342+
"scene",
343+
"[HyperFrames] composition script error:",
344+
undefined,
345+
"scene",
346+
"scene-root",
347+
);
348+
349+
new Function("window", "gsap", wrapped)(fakeWindow, fakeWindow.gsap);
350+
351+
expect(fakeWindow.__selectedRootCount).toBe(1);
352+
expect(fakeWindow.__selectedTimedCount).toBe(1);
353+
expect(fakeWindow.__selectedTitle).toBe("Scene");
354+
});
355+
211356
it("reads scoped proxy accessors with the original target receiver", () => {
212357
const root = {
213358
contains(node: unknown) {

0 commit comments

Comments
 (0)