Files
vue-tui/packages/runtime-tests/integration/lifecycle/on-render.test.tsx
T
Yunfei He 3ee01dc278 feat: add patchConsole, waitUntilRenderFlush, onRender, and maxFps
Add four behavioral surfaces to the runtime:

- patchConsole (default: true): intercepts console.* methods to route
  output through writeToStdout/writeToStderr, preventing frame corruption.
  Disabled in debug mode. Restored before Vue cleanup on teardown.
- waitUntilRenderFlush(): flushes pending throttled renders and waits
  for the stdout write barrier, exposed on TuiApp and testing RenderResult.
- onRender callback: fires after each commit with { renderTime } in ms.
- maxFps option: overrides the scheduler's default 32ms throttle interval
  with 1000/maxFps. The scheduler now accepts a throttleMs option and
  exposes hasPending() for flush coordination.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 15:54:18 +08:00

95 lines
2.3 KiB
TypeScript

import { defineComponent, nextTick, shallowRef } from "vue";
import { expect, test } from "vite-plus/test";
import { createApp, Text } from "@vue-tui/runtime";
import { makeFakeStdin, makeFakeWritable } from "./test-streams.ts";
test("onRender callback is called with renderTime on each commit", async () => {
const renderTimes: number[] = [];
const App = defineComponent(() => () => <Text>hello</Text>);
const app = createApp(App);
const stdout = makeFakeWritable({ columns: 80 });
const stderr = makeFakeWritable({ columns: 80 });
const { stream: stdin } = makeFakeStdin();
app.mount({
stdout,
stdin,
stderr,
debug: true,
exitOnCtrlC: false,
onRender: (info) => {
renderTimes.push(info.renderTime);
},
});
await nextTick();
await nextTick();
expect(renderTimes.length).toBeGreaterThanOrEqual(1);
expect(renderTimes[0]).toBeTypeOf("number");
expect(renderTimes[0]).toBeGreaterThanOrEqual(0);
app.unmount();
});
test("onRender is called on subsequent state updates", async () => {
const renderTimes: number[] = [];
const msg = shallowRef("a");
const App = defineComponent(() => {
return () => <Text>{msg.value}</Text>;
});
const app = createApp(App);
const stdout = makeFakeWritable({ columns: 80 });
const stderr = makeFakeWritable({ columns: 80 });
const { stream: stdin } = makeFakeStdin();
app.mount({
stdout,
stdin,
stderr,
debug: true,
exitOnCtrlC: false,
onRender: (info) => {
renderTimes.push(info.renderTime);
},
});
await nextTick();
await nextTick();
const initialCount = renderTimes.length;
msg.value = "b";
await nextTick();
await nextTick();
expect(renderTimes.length).toBeGreaterThan(initialCount);
app.unmount();
});
test("no onRender callback when option is not provided", async () => {
// Just verify the app works fine without onRender
const App = defineComponent(() => () => <Text>no callback</Text>);
const app = createApp(App);
const stdout = makeFakeWritable({ columns: 80 });
const stderr = makeFakeWritable({ columns: 80 });
const { stream: stdin } = makeFakeStdin();
app.mount({
stdout,
stdin,
stderr,
debug: true,
exitOnCtrlC: false,
});
await nextTick();
await nextTick();
app.unmount();
});