@@ -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