From f03ee1c1eec3558466434b4dbc234475c10577d6 Mon Sep 17 00:00:00 2001 From: Miguel Angel Simon Sierra Date: Sat, 3 Oct 2026 09:46:25 -0700 Subject: [PATCH 1/2] test(core): load the runtime once per file so the entry tests' first timeout covers only its own work --- packages/core/src/runtime/entry.test.ts | 43 ++++++++++++++----------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/packages/core/src/runtime/entry.test.ts b/packages/core/src/runtime/entry.test.ts index abc146f4002..9398089799c 100644 --- a/packages/core/src/runtime/entry.test.ts +++ b/packages/core/src/runtime/entry.test.ts @@ -52,6 +52,30 @@ async function evaluateRuntime(): Promise { await import("./entry"); } +function resetRuntimeGlobals(): void { + vi.useRealTimers(); + window.__hfRuntimeTeardown?.(); + document.head.innerHTML = ""; + document.body.innerHTML = ""; + window.__timelines = {}; + delete window.__player; + delete window.__playerReady; + delete window.__renderReady; + delete window.__hfTimelinesBuilding; + const win = window as { + __hyperframeRuntimeBootstrapped?: boolean; + __hfFirstPassHidden?: boolean; + }; + delete win.__hyperframeRuntimeBootstrapped; + delete win.__hfFirstPassHidden; + delete (document as { readyState?: unknown }).readyState; +} + +// The first evaluation transforms the whole runtime graph. Paying it while the file loads keeps +// that one-time cost out of the first test's timeout; every test still evaluates a fresh copy. +await evaluateRuntime(); +resetRuntimeGlobals(); + const visibility = (...els: HTMLElement[]) => els.map((el) => getComputedStyle(el).visibility); const imageSkipped = (...clips: HTMLElement[]) => clips.map((clip) => getComputedStyle(clip.querySelector("img")!).display === "none"); @@ -70,24 +94,7 @@ const neverDecodes = (clip: HTMLElement) => { }; describe("runtime entry", () => { - afterEach(() => { - vi.useRealTimers(); - window.__hfRuntimeTeardown?.(); - document.head.innerHTML = ""; - document.body.innerHTML = ""; - window.__timelines = {}; - delete window.__player; - delete window.__playerReady; - delete window.__renderReady; - delete window.__hfTimelinesBuilding; - const win = window as { - __hyperframeRuntimeBootstrapped?: boolean; - __hfFirstPassHidden?: boolean; - }; - delete win.__hyperframeRuntimeBootstrapped; - delete win.__hfFirstPassHidden; - delete (document as { readyState?: unknown }).readyState; - }); + afterEach(resetRuntimeGlobals); it("paints no timed clip, from script evaluation until the first visibility pass decides it", async () => { servePreview(); From c704912be6d3ccceca41eea0cbb1e7186555ce53 Mon Sep 17 00:00:00 2001 From: Miguel Angel Simon Sierra Date: Sat, 3 Oct 2026 11:26:25 -0700 Subject: [PATCH 2/2] fix(core): a torn-down runtime's pending readiness check no longer touches the next document --- packages/core/src/runtime/init.test.ts | 23 +++++++++++++++++++++++ packages/core/src/runtime/init.ts | 1 + 2 files changed, 24 insertions(+) diff --git a/packages/core/src/runtime/init.test.ts b/packages/core/src/runtime/init.test.ts index 23ebd30bc0b..ca9f1bc7386 100644 --- a/packages/core/src/runtime/init.test.ts +++ b/packages/core/src/runtime/init.test.ts @@ -3798,6 +3798,29 @@ describe("initSandboxRuntimeModular", () => { expect(window.__renderReady).toBe(true); }); + it("a torn-down runtime's pending readiness check leaves the next document alone", () => { + vi.useFakeTimers(); + try { + const root = document.createElement("div"); + root.setAttribute("data-composition-id", "main"); + root.setAttribute("data-root", "true"); + root.setAttribute("data-start", "0"); + document.body.appendChild(root); + window.__timelines = { main: createMockTimeline(10) }; + + initSandboxRuntimeModular(); + window.__hfRuntimeTeardown?.(); + // The next document is still batching its timelines when the old check fires. + delete window.__renderReady; + window.__hfTimelinesBuilding = true; + vi.runAllTimers(); + + expect(window.__renderReady).toBeUndefined(); + } finally { + vi.useRealTimers(); + } + }); + it("sets __renderReady even without a GSAP timeline (CSS/WAAPI compositions)", () => { const root = document.createElement("div"); root.setAttribute("data-composition-id", "main"); diff --git a/packages/core/src/runtime/init.ts b/packages/core/src/runtime/init.ts index b81afa25cda..6c549729267 100644 --- a/packages/core/src/runtime/init.ts +++ b/packages/core/src/runtime/init.ts @@ -4114,6 +4114,7 @@ export function initSandboxRuntimeModular(): void { }); maybePublishRenderReady = () => { + if (state.tornDown) return; if (!externalCompositionsReady) { window.__renderReady = false; return;