fix(tui): avoid tall dialog redraw loops

closes #5990
This commit is contained in:
Vegard Stikbakke
2026-06-23 10:36:47 +02:00
parent d0e0b84cb9
commit 392bae6b67
3 changed files with 125 additions and 1 deletions
+64
View File
@@ -378,6 +378,44 @@ describe("TUI resize handling", () => {
});
});
it("full re-renders in Termux when height changes reveal skipped offscreen changes", async () => {
await withEnv({ TERMUX_VERSION: "1" }, async () => {
const terminal = new VirtualTerminal(20, 5);
const tui = new TUI(terminal);
const component = new TestComponent();
tui.addChild(component);
component.lines = Array.from({ length: 11 }, (_, i) => `Line ${i}`);
tui.start();
await terminal.waitForRender();
component.lines = Array.from({ length: 11 }, (_, i) => (i === 5 ? "Changed 5" : `Line ${i}`));
tui.requestRender();
await terminal.waitForRender();
const redrawsAfterSkippedChange = tui.fullRedraws;
assert.deepStrictEqual(terminal.getViewport(), ["Line 6", "Line 7", "Line 8", "Line 9", "Line 10"]);
terminal.resize(20, 6);
await terminal.waitForRender();
assert.ok(
tui.fullRedraws > redrawsAfterSkippedChange,
"Height change should redraw before skipped offscreen content becomes visible",
);
assert.deepStrictEqual(terminal.getViewport(), [
"Changed 5",
"Line 6",
"Line 7",
"Line 8",
"Line 9",
"Line 10",
]);
tui.stop();
});
});
it("triggers full re-render when terminal width changes", async () => {
const terminal = new VirtualTerminal(40, 10);
const tui = new TUI(terminal);
@@ -657,6 +695,32 @@ describe("TUI differential rendering", () => {
tui.stop();
});
it("does not full redraw for offscreen changes above the viewport", async () => {
const terminal = new VirtualTerminal(20, 5);
const tui = new TUI(terminal);
const component = new TestComponent();
tui.addChild(component);
const stableVisibleLines = Array.from({ length: 8 }, (_, i) => `Dialog ${i}`);
component.lines = ["Ticker 0", "Intro", "", ...stableVisibleLines];
tui.start();
await terminal.waitForRender();
const initialRedraws = tui.fullRedraws;
const initialViewport = terminal.getViewport();
for (let i = 1; i <= 3; i++) {
component.lines = [`Ticker ${i}`, "Intro", "", ...stableVisibleLines];
tui.requestRender();
await terminal.waitForRender();
}
assert.strictEqual(tui.fullRedraws, initialRedraws, "Offscreen changes should not force full redraws");
assert.deepStrictEqual(terminal.getViewport(), initialViewport, "Visible viewport should remain stable");
tui.stop();
});
it("full re-renders when deleted lines move the viewport upward", async () => {
const terminal = new VirtualTerminal(20, 5);
const tui = new TUI(terminal);