diff --git a/packages/tui/test/editor.test.ts b/packages/tui/test/editor.test.ts index 1af316879..f884fb314 100644 --- a/packages/tui/test/editor.test.ts +++ b/packages/tui/test/editor.test.ts @@ -1025,7 +1025,7 @@ describe("Editor component", () => { await terminal.waitForRender(); for (const char of "ast") editor.handleInput(char); tui.requestRender(); - await terminal.waitForRender(); + await terminal.waitForRender(() => terminal.getViewport()[1]?.includes("ast") === true); const beforePreedit = terminal.getViewport().map(row => row.trimEnd()); expect(beforePreedit.slice(0, 3)).toEqual(["+------------------+", "| ast", "+------------------+"]); diff --git a/packages/tui/test/virtual-terminal.ts b/packages/tui/test/virtual-terminal.ts index 3821ba0c8..8a7908bd6 100644 --- a/packages/tui/test/virtual-terminal.ts +++ b/packages/tui/test/virtual-terminal.ts @@ -280,12 +280,21 @@ export class VirtualTerminal implements Terminal { // --- Test-only helpers --------------------------------------------------- - /** Wait for TUI's throttled render pipeline to settle (matches the ~33ms frame budget). */ - async waitForRender(): Promise { + /** + * Wait for TUI's throttled render pipeline to settle (matches the ~33ms + * frame budget). Fixed sleeps race starved CI timers, so callers asserting + * on a specific frame pass `until`; polling continues (10ms slices, ~2s + * cap) until the predicate observes the expected viewport. + */ + async waitForRender(until?: () => boolean): Promise { const nextTick = Promise.withResolvers(); process.nextTick(nextTick.resolve); await nextTick.promise; await Bun.sleep(40); + if (until) { + const deadline = Date.now() + 2_000; + while (!until() && Date.now() < deadline) await Bun.sleep(10); + } await this.flush(); }