diff --git a/packages/runtime-tests/integration/composables/use-animation.test.tsx b/packages/runtime-tests/integration/composables/use-animation.test.tsx index b0ec4c2..070a8ff 100644 --- a/packages/runtime-tests/integration/composables/use-animation.test.tsx +++ b/packages/runtime-tests/integration/composables/use-animation.test.tsx @@ -3,7 +3,7 @@ import { defineComponent, nextTick, shallowRef, watchEffect } from "vue"; import type { ShallowRef } from "vue"; import { describe, expect, test } from "vite-plus/test"; import { render } from "@vue-tui/testing"; -import { Text, useAnimation, createApp } from "@vue-tui/runtime"; +import { Text, useAnimation, createApp, renderToString } from "@vue-tui/runtime"; const delay = (ms: number) => new Promise((r) => setTimeout(r, ms)); @@ -968,4 +968,13 @@ describe("useAnimation", () => { unmount(); }); + + test("renderToString renders frame 0 without throwing or leaking timers", () => { + const App = defineComponent(() => { + const { frame } = useAnimation({ interval: 50 }); + return () => {`frame:${frame.value}`}; + }); + const output = renderToString(App); + expect(output).toContain("frame:0"); + }); }); diff --git a/packages/runtime/src/render-to-string.ts b/packages/runtime/src/render-to-string.ts index 46fbd6e..1fb0702 100644 --- a/packages/runtime/src/render-to-string.ts +++ b/packages/runtime/src/render-to-string.ts @@ -13,10 +13,12 @@ import { AppContextKey, FocusContextKey, StdinContextKey, + AnimationSchedulerKey, type AppContext, type FocusContext, type StdinContext, } from "./context.ts"; +import { createNoOpAnimationScheduler } from "./animation-scheduler.ts"; export interface RenderToStringOptions { /** @@ -94,6 +96,7 @@ export function renderToString(component: Component, options?: RenderToStringOpt app.provide(AppContextKey, appContext); app.provide(FocusContextKey, createNoOpFocusContext()); app.provide(StdinContextKey, createNoOpStdinContext()); + app.provide(AnimationSchedulerKey, createNoOpAnimationScheduler()); // Capture the first uncaught error so we can re-throw after cleanup. // Vue's error handling catches component errors internally; for a