@@ -525,6 +525,7 @@ describe("HyperframesPlayer shader transition options", () => {
525525 const player = document . createElement ( "hyperframes-player" ) as PlayerWithIframe ;
526526 player . setAttribute ( "shader-capture-scale" , "0.5" ) ;
527527 player . setAttribute ( "shader-loading" , "player" ) ;
528+ document . body . appendChild ( player ) ;
528529 player . setAttribute ( "src" , "/api/projects/demo/preview?x=1#stage" ) ;
529530
530531 const url = new URL ( player . iframeElement . src ) ;
@@ -539,6 +540,7 @@ describe("HyperframesPlayer shader transition options", () => {
539540 const player = document . createElement ( "hyperframes-player" ) as PlayerWithIframe ;
540541 player . setAttribute ( "shader-capture-scale" , "0.5" ) ;
541542 player . setAttribute ( "shader-loading" , "player" ) ;
543+ document . body . appendChild ( player ) ;
542544 player . setAttribute (
543545 "srcdoc" ,
544546 '<!doctype html><html><head><script src="composition.js"></script></head><body></body></html>' ,
@@ -1512,6 +1514,36 @@ describe("HyperframesPlayer srcdoc attribute", () => {
15121514 player . remove ( ) ;
15131515 } ) ;
15141516
1517+ it ( "does not navigate initial srcdoc before the runtime listener is connected" , ( ) => {
1518+ // React assigns custom-element attributes before inserting the element. If the observed
1519+ // attribute callback navigates the child iframe immediately, a fast srcdoc runtime can post
1520+ // its one-shot `ready` message before connectedCallback subscribes to `window.message`.
1521+ // Retained runtime data then waits forever and a caption style appears stuck on its bootstrap
1522+ // frame. The connect path owns the first navigation; attributeChangedCallback owns only
1523+ // subsequent swaps.
1524+ const player = document . createElement ( "hyperframes-player" ) as PlayerInternal ;
1525+ player . setAttribute ( "srcdoc" , "<!doctype html><html><body>deferred</body></html>" ) ;
1526+
1527+ expect ( player . iframe . hasAttribute ( "srcdoc" ) ) . toBe ( false ) ;
1528+
1529+ document . body . appendChild ( player ) ;
1530+ expect ( player . iframe . getAttribute ( "srcdoc" ) ) . toContain ( "<body>deferred</body>" ) ;
1531+
1532+ player . remove ( ) ;
1533+ } ) ;
1534+
1535+ it ( "does not navigate initial src before the runtime listener is connected" , ( ) => {
1536+ const player = document . createElement ( "hyperframes-player" ) as PlayerInternal ;
1537+ player . setAttribute ( "src" , "/api/projects/deferred/preview" ) ;
1538+
1539+ expect ( player . iframe . hasAttribute ( "src" ) ) . toBe ( false ) ;
1540+
1541+ document . body . appendChild ( player ) ;
1542+ expect ( player . iframe . getAttribute ( "src" ) ) . toBe ( "/api/projects/deferred/preview" ) ;
1543+
1544+ player . remove ( ) ;
1545+ } ) ;
1546+
15151547 it ( "forwards a srcdoc attribute set after connect to the iframe" , ( ) => {
15161548 // The composition-switching flow: same player element, new HTML.
15171549 // Without `attributeChangedCallback` wiring this would no-op.
0 commit comments