fix(core,cli): address review — guard __renderReady, drop pre-quantization, add tests

- Guard __renderReady with `if (state.capturedTimeline)` in all three
  paths (setTimeout(0) and .finally() were setting it unconditionally
  even when bindRootTimelineIfAvailable returned false)
- Remove redundant fps=30 pre-quantization in snapshot — renderSeek
  already calls quantizeTimeToFrame internally with the runtime's
  canonicalFps, so pre-quantizing was double-quantizing at a
  potentially wrong grid
- Add regression tests: __renderReady is set when timeline exists,
  stays undefined when no timeline is available
This commit is contained in:
Miguel Ángel
2026-05-24 13:31:36 -04:00
parent 16e049b320
commit e7d0b392c7
3 changed files with 56 additions and 8 deletions
+2 -5
View File
@@ -276,17 +276,14 @@ async function captureSnapshots(
for (let i = 0; i < positions.length; i++) { for (let i = 0; i < positions.length; i++) {
const time = positions[i]!; const time = positions[i]!;
// 30 = runtime's default canonicalFps (not exposed on PlayerAPI)
await page.evaluate((t: number) => { await page.evaluate((t: number) => {
const player = (window as any).__player; const player = (window as any).__player;
if (!player) return; if (!player) return;
const safe = Math.max(0, Number(t) || 0); const safe = Math.max(0, Number(t) || 0);
const frame = Math.floor(safe * 30 + 1e-9);
const quantized = frame / 30;
if (typeof player.renderSeek === "function") { if (typeof player.renderSeek === "function") {
player.renderSeek(quantized); player.renderSeek(safe);
} else if (typeof player.seek === "function") { } else if (typeof player.seek === "function") {
player.seek(quantized); player.seek(safe);
} }
if ((window as any).gsap?.ticker?.tick) { if ((window as any).gsap?.ticker?.tick) {
(window as any).gsap.ticker.tick(); (window as any).gsap.ticker.tick();
+48
View File
@@ -576,4 +576,52 @@ describe("initSandboxRuntimeModular", () => {
expect(player?.getTime()).toBeCloseTo(1, 1); expect(player?.getTime()).toBeCloseTo(1, 1);
expect(childTimeline.time()).toBeCloseTo(1, 1); expect(childTimeline.time()).toBeCloseTo(1, 1);
}); });
it("sets __renderReady only after timeline is bound, not at __playerReady time", async () => {
const root = document.createElement("div");
root.setAttribute("data-composition-id", "main");
root.setAttribute("data-root", "true");
root.setAttribute("data-start", "0");
root.setAttribute("data-width", "1920");
root.setAttribute("data-height", "1080");
document.body.appendChild(root);
(window as Window & { __timelines?: Record<string, RuntimeTimelineLike> }).__timelines = {
main: createMockTimeline(10),
};
initSandboxRuntimeModular();
const win = window as Window & {
__playerReady?: boolean;
__renderReady?: boolean;
__player?: { _timeline: RuntimeTimelineLike | null };
};
expect(win.__playerReady).toBe(true);
expect(win.__renderReady).toBe(true);
expect(win.__player?._timeline).not.toBeNull();
});
it("does not set __renderReady when no timeline is available", () => {
const root = document.createElement("div");
root.setAttribute("data-composition-id", "main");
root.setAttribute("data-root", "true");
root.setAttribute("data-start", "0");
root.setAttribute("data-width", "1920");
root.setAttribute("data-height", "1080");
document.body.appendChild(root);
(window as Window & { __timelines?: Record<string, RuntimeTimelineLike> }).__timelines = {};
initSandboxRuntimeModular();
const win = window as Window & {
__playerReady?: boolean;
__renderReady?: boolean;
};
expect(win.__playerReady).toBe(true);
expect(win.__renderReady).toBeUndefined();
});
}); });
+6 -3
View File
@@ -1463,7 +1463,9 @@ export function initSandboxRuntimeModular(): void {
.finally(() => { .finally(() => {
externalCompositionsReady = true; externalCompositionsReady = true;
bindRootTimelineIfAvailable(); bindRootTimelineIfAvailable();
(window as Window & { __renderReady?: boolean }).__renderReady = true; if (state.capturedTimeline) {
(window as Window & { __renderReady?: boolean }).__renderReady = true;
}
runAdapters("discover", state.currentTime); runAdapters("discover", state.currentTime);
bindMediaMetadataListeners(); bindMediaMetadataListeners();
installAssetFailureDiagnostics(); installAssetFailureDiagnostics();
@@ -1650,9 +1652,10 @@ export function initSandboxRuntimeModular(): void {
if (bindRootTimelineIfAvailable() && state.capturedTimeline !== prevTimeline) { if (bindRootTimelineIfAvailable() && state.capturedTimeline !== prevTimeline) {
player._timeline = state.capturedTimeline; player._timeline = state.capturedTimeline;
} }
// Re-run adapters to discover new elements
runAdapters("discover", state.currentTime); runAdapters("discover", state.currentTime);
(window as Window & { __renderReady?: boolean }).__renderReady = true; if (state.capturedTimeline) {
(window as Window & { __renderReady?: boolean }).__renderReady = true;
}
postTimeline(); postTimeline();
postState(true); postState(true);
}, 0); }, 0);