@@ -5,6 +5,11 @@ import { initSandboxRuntimeModular } from "./init";
55import { TYPEGPU_PRESENT_HEARTBEAT_MS } from "./adapters/typegpu" ;
66import { WebAudioTransport } from "./webAudioTransport" ;
77import type { RuntimeTimelineLike } from "./types" ;
8+ import {
9+ registerRuntimeDataHandler ,
10+ resetRuntimeDataForTests ,
11+ setRuntimeData ,
12+ } from "./runtimeData" ;
813
914it ( "schedules WebAudio element gain from author volume without bridge volume" , ( ) => {
1015 const source = readFileSync ( "src/runtime/init.ts" , "utf8" ) ;
@@ -107,6 +112,7 @@ describe("initSandboxRuntimeModular", () => {
107112 const originalCancelAnimationFrame = window . cancelAnimationFrame ;
108113
109114 beforeEach ( ( ) => {
115+ resetRuntimeDataForTests ( ) ;
110116 document . body . innerHTML = "" ;
111117 ( globalThis as typeof globalThis & { CSS ?: { escape ?: ( value : string ) => string } } ) . CSS ??= { } ;
112118 globalThis . CSS . escape ??= ( value : string ) => value ;
@@ -174,6 +180,7 @@ describe("initSandboxRuntimeModular", () => {
174180
175181 afterEach ( ( ) => {
176182 window . __hfRuntimeTeardown ?.( ) ;
183+ resetRuntimeDataForTests ( ) ;
177184 document . body . innerHTML = "" ;
178185 window . __timelines = { } as Record < string , RuntimeTimelineLike > ;
179186 delete window . __player ;
@@ -2688,6 +2695,111 @@ describe("initSandboxRuntimeModular", () => {
26882695 expect ( clipControl ?. style . visibility ) . toBe ( "visible" ) ;
26892696 } ) ;
26902697
2698+ it ( "rebinds the injected player before reporting runtime-data applied" , async ( ) => {
2699+ const root = document . createElement ( "div" ) ;
2700+ root . setAttribute ( "data-composition-id" , "main" ) ;
2701+ root . setAttribute ( "data-root" , "true" ) ;
2702+ root . setAttribute ( "data-duration" , "10" ) ;
2703+ root . setAttribute ( "data-width" , "1920" ) ;
2704+ root . setAttribute ( "data-height" , "1080" ) ;
2705+ document . body . appendChild ( root ) ;
2706+
2707+ const first = createMockTimeline ( 10 ) ;
2708+ const replacement = createMockTimeline ( 10 ) ;
2709+ window . __timelines = { main : first } ;
2710+ const applied : Array < Record < string , unknown > > = [ ] ;
2711+ vi . spyOn ( window . parent , "postMessage" ) . mockImplementation ( ( message : unknown ) => {
2712+ if ( typeof message !== "object" || message === null ) return ;
2713+ const payload = message as Record < string , unknown > ;
2714+ if ( payload . type === "runtime-data-applied" ) applied . push ( payload ) ;
2715+ } ) ;
2716+
2717+ initSandboxRuntimeModular ( ) ;
2718+ window . __player ?. seek ( 0.25 ) ;
2719+ registerRuntimeDataHandler ( "captions" , async ( ) => {
2720+ await Promise . resolve ( ) ;
2721+ window . __timelines = { main : replacement } ;
2722+ } ) ;
2723+
2724+ setRuntimeData ( "captions" , { style : "replacement" } , 7 ) ;
2725+ await vi . waitFor ( ( ) => expect ( applied ) . toHaveLength ( 1 ) ) ;
2726+
2727+ // Runtime seeks are canonicalized to the configured frame rate.
2728+ expect ( replacement . time ( ) ) . toBeCloseTo ( 7 / 30 , 5 ) ;
2729+ expect ( first . time ( ) ) . toBeCloseTo ( 7 / 30 , 5 ) ;
2730+
2731+ window . __player ?. seek ( 1.25 ) ;
2732+
2733+ expect ( first . time ( ) ) . toBeCloseTo ( 7 / 30 , 5 ) ;
2734+ expect ( replacement . time ( ) ) . toBeCloseTo ( 37 / 30 , 5 ) ;
2735+ expect ( applied [ 0 ] ) . toMatchObject ( { channel : "captions" , requestId : 7 } ) ;
2736+ } ) ;
2737+
2738+ it ( "does not seek a removed timeline after runtime data is cleared" , async ( ) => {
2739+ const root = document . createElement ( "div" ) ;
2740+ root . setAttribute ( "data-composition-id" , "main" ) ;
2741+ root . setAttribute ( "data-root" , "true" ) ;
2742+ root . setAttribute ( "data-duration" , "10" ) ;
2743+ root . setAttribute ( "data-width" , "1920" ) ;
2744+ root . setAttribute ( "data-height" , "1080" ) ;
2745+ document . body . appendChild ( root ) ;
2746+
2747+ const first = createMockTimeline ( 10 ) ;
2748+ window . __timelines = { main : first } ;
2749+ const applied : Array < Record < string , unknown > > = [ ] ;
2750+ vi . spyOn ( window . parent , "postMessage" ) . mockImplementation ( ( message : unknown ) => {
2751+ if ( typeof message !== "object" || message === null ) return ;
2752+ const payload = message as Record < string , unknown > ;
2753+ if ( payload . type === "runtime-data-applied" ) applied . push ( payload ) ;
2754+ } ) ;
2755+
2756+ initSandboxRuntimeModular ( ) ;
2757+ window . __player ?. seek ( 0.25 ) ;
2758+ registerRuntimeDataHandler ( "captions" , ( ) => {
2759+ window . __timelines = { } ;
2760+ } ) ;
2761+
2762+ setRuntimeData ( "captions" , undefined , 8 ) ;
2763+ await vi . waitFor ( ( ) => expect ( applied ) . toHaveLength ( 1 ) ) ;
2764+ const timeAtClear = first . time ( ) ;
2765+
2766+ window . __player ?. seek ( 1.25 ) ;
2767+
2768+ expect ( first . time ( ) ) . toBe ( timeAtClear ) ;
2769+ } ) ;
2770+
2771+ it ( "does not report applied when a runtime-data handler rejects" , async ( ) => {
2772+ const root = document . createElement ( "div" ) ;
2773+ root . setAttribute ( "data-composition-id" , "main" ) ;
2774+ root . setAttribute ( "data-root" , "true" ) ;
2775+ root . setAttribute ( "data-duration" , "10" ) ;
2776+ root . setAttribute ( "data-width" , "1920" ) ;
2777+ root . setAttribute ( "data-height" , "1080" ) ;
2778+ document . body . appendChild ( root ) ;
2779+ window . __timelines = { main : createMockTimeline ( 10 ) } ;
2780+
2781+ const applied : Array < Record < string , unknown > > = [ ] ;
2782+ const errors : Array < Record < string , unknown > > = [ ] ;
2783+ vi . spyOn ( window . parent , "postMessage" ) . mockImplementation ( ( message : unknown ) => {
2784+ if ( typeof message !== "object" || message === null ) return ;
2785+ const payload = message as Record < string , unknown > ;
2786+ if ( payload . type === "runtime-data-applied" ) applied . push ( payload ) ;
2787+ if ( payload . type === "runtime-data-error" ) errors . push ( payload ) ;
2788+ } ) ;
2789+
2790+ initSandboxRuntimeModular ( ) ;
2791+ registerRuntimeDataHandler ( "captions" , async ( ) => {
2792+ await Promise . resolve ( ) ;
2793+ throw new Error ( "attach failed" ) ;
2794+ } ) ;
2795+
2796+ setRuntimeData ( "captions" , { style : "broken" } , 9 ) ;
2797+ await vi . waitFor ( ( ) => expect ( errors ) . toHaveLength ( 1 ) ) ;
2798+
2799+ expect ( applied ) . toHaveLength ( 0 ) ;
2800+ expect ( errors [ 0 ] ) . toMatchObject ( { channel : "captions" , requestId : 9 } ) ;
2801+ } ) ;
2802+
26912803 it ( "onSetMuted preserves authored muted attribute on video elements" , ( ) => {
26922804 const root = document . createElement ( "div" ) ;
26932805 root . setAttribute ( "data-composition-id" , "root" ) ;
0 commit comments