fix: add final unmount render to trigger shouldClearOnUnmount

Ink does an explicit onRender() with isUnmounting=true before unmount
(ink.tsx:755-761), which triggers clearTerminal for fullscreen apps.
vue-tui's teardown() was nulling scheduledCommit before unmount, making
shouldClearOnUnmount dead code. Now calls commit() synchronously before
disabling the scheduler, matching Ink's unmount render behavior.

Restores 2 erase PTY tests that were incorrectly attributed to a
React vs Vue rendering difference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yunfei He
2026-05-27 10:47:11 +08:00
parent 6ca120b6f3
commit 5b1035792c
2 changed files with 30 additions and 0 deletions
@@ -51,6 +51,26 @@ it("do not erase screen where <Static> is taller than viewport", async () => {
}
});
it("erase screen (content overflows viewport)", async () => {
const ps = term("erase", ["3"]);
await ps.waitForExit();
expect(ps.output).toContain(ansiEscapes.clearTerminal);
for (const letter of ["A", "B", "C"]) {
expect(ps.output).toContain(letter);
}
});
it("erase screen where <Static> exists but interactive part is taller than viewport", async () => {
const ps = term("erase-with-static", ["3"]);
await ps.waitForExit();
expect(ps.output).toContain(ansiEscapes.clearTerminal);
for (const letter of ["A", "B", "C", "D", "E", "F"]) {
expect(ps.output).toContain(letter);
}
});
it("erase screen where state changes", async () => {
const ps = term("erase-with-state-change", ["4"]);
await ps.waitForExit();
+10
View File
@@ -150,6 +150,7 @@ export function createApp(root: Component, rootProps?: RootProps | null): TuiApp
let mountedGetLastOutput: (() => string) | null = null;
let mountedRestoreConsole: (() => void) | null = null;
let mountedScheduler: ReturnType<typeof createCommitScheduler> | null = null;
let mountedCommit: (() => void) | null = null;
// The renderer's onCommit closure is wired at createApp time but only does
// real work after mount swaps in scheduler.schedule. One renderer per app
@@ -185,6 +186,14 @@ export function createApp(root: Component, rootProps?: RootProps | null): TuiApp
function teardown() {
if (teardownStarted) return;
teardownStarted = true;
// Final render before unmount (matching Ink ink.tsx:755-761).
// teardownStarted=true makes shouldClearTerminalForFrame see isUnmounting,
// so fullscreen apps get clearTerminal on exit.
if (mountedInteractive && !mountedDebug && mountedCommit) {
mountedCommit();
}
scheduledCommit = () => {};
// Restore console BEFORE Vue cleanup (matching Ink ink.tsx:779)
if (mountedRestoreConsole) {
@@ -523,6 +532,7 @@ export function createApp(root: Component, rootProps?: RootProps | null): TuiApp
}
const scheduler = createCommitScheduler(commit, schedulerOptions);
mountedScheduler = scheduler;
mountedCommit = commit;
scheduledCommit = scheduler.schedule;
// Internal provides — set before the actual mount so components can inject