fix(studio): populate keyframe lanes for every timeline composition

The keyframe cache was fetched for a single source file: the selected
element's, else the active composition. On open nothing is selected, so
a project whose clips live in sub-compositions loaded only index.html
and every property lane rendered empty until a clip was clicked.

Load the cache for each composition file the timeline has rows for, so
keyframed clips are expanded on open as intended. The AST load path
moves to keyframeCacheAstLoad.ts to keep useGsapTweenCache.ts under the
600-line cap.
This commit is contained in:
Miguel Angel Simon Sierra
2026-07-28 00:40:43 +02:00
parent 282cc883e5
commit 099d891fd3
3 changed files with 277 additions and 134 deletions
@@ -0,0 +1,68 @@
// @vitest-environment happy-dom
import React, { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { usePopulateKeyframeCacheForFile } from "./useGsapTweenCache";
import { usePlayerStore } from "../player/store/playerStore";
Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", true);
function HookHost() {
usePopulateKeyframeCacheForFile("demo", "index.html", 1);
return null;
}
let root: Root | null = null;
let container: HTMLElement | null = null;
beforeEach(() => {
usePlayerStore.setState({
elements: [
{
id: "lab",
tag: "div",
start: 0,
duration: 12,
track: 1,
compositionSrc: "compositions/keyframe-lab.html",
},
] as never,
});
});
afterEach(() => {
if (root) {
act(() => root?.unmount());
root = null;
}
container?.remove();
vi.unstubAllGlobals();
});
describe("usePopulateKeyframeCacheForFile", () => {
it("loads every sub-composition file the timeline shows, not just the active one", async () => {
// Keyframe lanes must be populated when the project opens. Fetching only the
// active file left them empty until a clip from the sub-composition was
// selected (which is the only thing that switched `sourceFile`).
const urls: string[] = [];
const fetchMock = vi.fn((input: RequestInfo | URL) => {
urls.push(String(input));
return Promise.resolve({ ok: true, json: () => Promise.resolve({ animations: [] }) });
});
vi.stubGlobal("fetch", fetchMock);
act(() => {
container = document.createElement("div");
document.body.appendChild(container);
root = createRoot(container);
root.render(<HookHost />);
});
await act(async () => {
await Promise.resolve();
});
expect(urls.some((u) => u.endsWith("/index.html"))).toBe(true);
expect(urls.some((u) => u.includes("keyframe-lab.html"))).toBe(true);
});
});