@@ -3669,6 +3669,61 @@ describe("initSandboxRuntimeModular", () => {
36693669 }
36703670 } ) ;
36713671
3672+ it ( "keeps a root rebound during playback paused on GSAP's ticker" , ( ) => {
3673+ vi . useFakeTimers ( { toFake : [ "setTimeout" , "clearTimeout" , "Date" ] } ) ;
3674+ let nowMs = 1000 ;
3675+ vi . spyOn ( performance , "now" ) . mockImplementation ( ( ) => nowMs ) ;
3676+ const raf = createManualRaf ( ) ;
3677+ window . requestAnimationFrame = raf . requestAnimationFrame ;
3678+ window . cancelAnimationFrame = raf . cancelAnimationFrame ;
3679+ document . body . innerHTML =
3680+ '<div data-composition-id="main" data-root="true" data-start="0" data-duration="1">' +
3681+ '<video data-start="0"></video></div>' ;
3682+ const video = document . querySelector ( "video" ) ! ;
3683+ vi . spyOn ( video , "play" ) . mockResolvedValue ( undefined ) ;
3684+ vi . spyOn ( video , "pause" ) . mockImplementation ( ( ) => { } ) ;
3685+ const authored = gsap . timeline ( { paused : true } ) ;
3686+ window . gsap = gsap ;
3687+ window . __timelines = { main : authored } ;
3688+ const timelines = vi . spyOn ( gsap , "timeline" ) ;
3689+ const messages = vi . spyOn ( window . parent , "postMessage" ) ;
3690+
3691+ try {
3692+ initSandboxRuntimeModular ( ) ;
3693+ vi . advanceTimersByTime ( 0 ) ;
3694+ window . __player ! . play ( ) ;
3695+ nowMs = 1250 ;
3696+ raf . step ( 250 ) ;
3697+ Object . defineProperty ( video , "duration" , { value : 12 , configurable : true } ) ;
3698+ video . dispatchEvent ( new Event ( "loadedmetadata" ) ) ;
3699+ vi . advanceTimersByTime ( 100 ) ;
3700+
3701+ expect (
3702+ messages . mock . calls . some (
3703+ ( [ message ] ) =>
3704+ ( message as { code ?: string } ) . code === "timeline_rebind_after_media_metadata" ,
3705+ ) ,
3706+ ) . toBe ( true ) ;
3707+ const rebound = timelines . mock . results . at ( - 1 ) ?. value as gsap . core . Timeline ;
3708+ expect ( authored . parent ) . toBe ( rebound ) ;
3709+ expect ( window . __player ! . isPlaying ( ) ) . toBe ( true ) ;
3710+ expect ( rebound . paused ( ) ) . toBe ( true ) ;
3711+ const time = rebound . time ( ) ;
3712+ gsap . updateRoot ( gsap . globalTimeline . time ( ) + 0.5 ) ;
3713+ expect ( rebound . time ( ) ) . toBe ( time ) ;
3714+ expect ( window . __player ! . getTime ( ) ) . toBe ( 0.25 ) ;
3715+ nowMs = 1350 ;
3716+ raf . step ( 100 ) ;
3717+ expect ( rebound . time ( ) ) . toBeCloseTo ( 0.35 , 5 ) ;
3718+ } finally {
3719+ window . __hfRuntimeTeardown ?.( ) ;
3720+ for ( const result of timelines . mock . results ) {
3721+ if ( result . type === "return" ) ( result . value as gsap . core . Timeline ) . kill ( ) ;
3722+ }
3723+ authored . kill ( ) ;
3724+ }
3725+ } ) ;
3726+
36723727 it ( "sets __renderReady only after timeline is bound, not at __playerReady time" , async ( ) => {
36733728 const root = document . createElement ( "div" ) ;
36743729 root . setAttribute ( "data-composition-id" , "main" ) ;
0 commit comments