feat: add time-based render throttle (~30fps) matching Ink

Add leading+trailing throttle to the commit scheduler in production mode.
In debug/test mode (immediate: true), commits fire without delay to
preserve test determinism. Adjusted the non-debug static test to account
for the throttle timer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yunfei He
2026-05-26 00:55:02 +08:00
parent b8778c1847
commit 8565f41827
4 changed files with 85 additions and 10 deletions
@@ -111,6 +111,8 @@ test("Static flush clears the dynamic frame first (non-debug mode)", async () =>
stdout.chunks.length = 0;
items.value = ["STATIC_ITEM"];
await nextTick();
// Wait for the render throttle trailing timer (~32ms) to fire in production mode.
await new Promise((r) => setTimeout(r, 50));
// Collect everything written during this render cycle
const renderOutput = stdout.chunks.join("");
@@ -0,0 +1,24 @@
import { defineComponent, nextTick, shallowRef } from "vue";
import { expect, test } from "vite-plus/test";
import { render } from "@vue-tui/testing";
import { Text } from "@vue-tui/runtime";
test("rapid mutations in debug mode produce immediate frames (no throttle)", async () => {
const count = shallowRef(0);
const App = defineComponent(() => {
return () => <Text>{String(count.value)}</Text>;
});
const { frames } = await render(App);
const before = frames.length;
// Each mutation + tick should produce a frame in debug mode
for (let i = 1; i <= 5; i++) {
count.value = i;
await nextTick();
await nextTick();
}
// In debug mode (used by testing), each mutation produces a frame
expect(frames.length - before).toBe(5);
});