From 660674f46970b4de535f35262c7a705f67d09080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Sat, 3 Oct 2026 19:54:28 +0000 Subject: [PATCH] fix(core): torn-down runtimes stop checking readiness, and entry tests fit their timeout under load (#4960) * test(core): load the runtime once per file so the entry tests' first timeout covers only its own work * fix(core): a torn-down runtime's pending readiness check no longer touches the next document --- packages/core/src/runtime/entry.test.ts | 43 ++++++++++++++----------- packages/core/src/runtime/init.test.ts | 23 +++++++++++++ packages/core/src/runtime/init.ts | 1 + 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/packages/core/src/runtime/entry.test.ts b/packages/core/src/runtime/entry.test.ts index abc146f400..9398089799 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(); diff --git a/packages/core/src/runtime/init.test.ts b/packages/core/src/runtime/init.test.ts index 23ebd30bc0..ca9f1bc738 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 a2f8251afe..8ce7f5d777 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;