Merge PR #8806: fix(tests): isolate TUI scrollback tests from terminal multiplexers (@Huang-404-Q)

This commit is contained in:
can1357
2026-08-19 01:36:58 +02:00
6 changed files with 92 additions and 11 deletions
@@ -0,0 +1,40 @@
import { afterEach, beforeEach } from "bun:test";
/**
* Neutralize every terminal-multiplexer signal for the calling test file.
*
* `isInsideTerminalMultiplexer()` treats `TMUX`, `STY`, `ZELLIJ`,
* `HERDR_ENV=1`, the `CMUX_*` markers and a `tmux`/`screen` `TERM` as
* authoritative, and `isMultiplexerSession()` then routes rendering down the
* path that cannot rebuild scrollback. Tests that assert the destructive
* full-paint behavior otherwise fail for anyone running the suite inside tmux,
* screen, Zellij or CMUX. Restores whatever was set afterwards.
*/
export function withoutTerminalMultiplexer(): void {
const KEYS = [
"TMUX",
"STY",
"ZELLIJ",
"HERDR_ENV",
"CMUX_WORKSPACE_ID",
"CMUX_SURFACE_ID",
"CMUX_REMOTE_TRANSPORT",
"TERM",
] as const;
const previous = new Map<string, string | undefined>();
beforeEach(() => {
for (const key of KEYS) {
previous.set(key, Bun.env[key]);
delete Bun.env[key];
}
});
afterEach(() => {
for (const [key, value] of previous) {
if (value === undefined) delete Bun.env[key];
else Bun.env[key] = value;
}
previous.clear();
});
}
@@ -11,6 +11,7 @@ import { previewWindowRows } from "@oh-my-pi/pi-coding-agent/tools/render-utils"
import { TUI, visibleWidth } from "@oh-my-pi/pi-tui";
import { removeWithRetries } from "@oh-my-pi/pi-utils";
import { VirtualTerminal } from "../../tui/test/virtual-terminal";
import { withoutTerminalMultiplexer } from "./helpers/terminal-multiplexer";
// The streaming edit preview is a fixed-height tail window ("cursor"): the last
// EDIT_STREAMING_PREVIEW_LINES rows of the recomputed diff are pinned to the
@@ -21,6 +22,8 @@ import { VirtualTerminal } from "../../tui/test/virtual-terminal";
// whole change segments grew and shrank tick to tick (the stutter), and the
// earlier high-water fix padded the deficit with blank rows (the "large
// rectangle that is half empty" regression). The tail window has neither.
withoutTerminalMultiplexer();
describe("streaming edit preview height (stable, full tail window)", () => {
const oldBlock = ["function foo() {", " const x = 1;", " return x;", "}"].join("\n");
const tail = ["", "function bar() {", " return 2;", "}", "", "function baz() {", " return 3;", "}", ""].join("\n");
@@ -4,6 +4,7 @@ import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
import { type Component, TUI } from "@oh-my-pi/pi-tui";
import { StressRenderScheduler } from "../../tui/test/render-stress-scheduler";
import { VirtualTerminal } from "../../tui/test/virtual-terminal";
import { withoutTerminalMultiplexer } from "./helpers/terminal-multiplexer";
function writeArgs(lineCount: number) {
return {
@@ -31,6 +32,8 @@ function plainBuffer(term: VirtualTerminal): string[] {
.filter(Boolean);
}
withoutTerminalMultiplexer();
describe("ToolExecutionComponent write repaint seam", () => {
const components: ToolExecutionComponent[] = [];
+3 -11
View File
@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import { describe, expect, it } from "bun:test";
import {
type Component,
Container,
@@ -10,20 +10,12 @@ import {
type NativeScrollbackReplay,
TUI,
} from "@oh-my-pi/pi-tui";
import { withoutTerminalMultiplexer } from "./helpers/terminal-multiplexer";
import { StressRenderScheduler } from "./render-stress-scheduler";
import { defaultEditorTheme } from "./test-themes";
import { VirtualTerminal } from "./virtual-terminal";
const ORIGINAL_HERDR_ENV = Bun.env.HERDR_ENV;
beforeEach(() => {
delete Bun.env.HERDR_ENV;
});
afterEach(() => {
if (ORIGINAL_HERDR_ENV === undefined) delete Bun.env.HERDR_ENV;
else Bun.env.HERDR_ENV = ORIGINAL_HERDR_ENV;
});
withoutTerminalMultiplexer();
// Behavioral tests for TUI.requestComponentRender: a component whose own
// content changed (spinner frame, blink) asks for a component-scoped frame.
@@ -0,0 +1,40 @@
import { afterEach, beforeEach } from "bun:test";
/**
* Neutralize every terminal-multiplexer signal for the calling test file.
*
* `isInsideTerminalMultiplexer()` treats `TMUX`, `STY`, `ZELLIJ`,
* `HERDR_ENV=1`, the `CMUX_*` markers and a `tmux`/`screen` `TERM` as
* authoritative, and `isMultiplexerSession()` then routes rendering down the
* path that cannot rebuild scrollback. Tests that assert the destructive
* full-paint behavior otherwise fail for anyone running the suite inside tmux,
* screen, Zellij or CMUX. Restores whatever was set afterwards.
*/
export function withoutTerminalMultiplexer(): void {
const KEYS = [
"TMUX",
"STY",
"ZELLIJ",
"HERDR_ENV",
"CMUX_WORKSPACE_ID",
"CMUX_SURFACE_ID",
"CMUX_REMOTE_TRANSPORT",
"TERM",
] as const;
const previous = new Map<string, string | undefined>();
beforeEach(() => {
for (const key of KEYS) {
previous.set(key, Bun.env[key]);
delete Bun.env[key];
}
});
afterEach(() => {
for (const [key, value] of previous) {
if (value === undefined) delete Bun.env[key];
else Bun.env[key] = value;
}
previous.clear();
});
}
+3
View File
@@ -1,5 +1,6 @@
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import { type Component, CURSOR_MARKER, type Focusable, type OverlayFocusOwner, TUI } from "@oh-my-pi/pi-tui";
import { withoutTerminalMultiplexer } from "./helpers/terminal-multiplexer";
import { VirtualTerminal } from "./virtual-terminal";
class LineComponent implements Component {
@@ -167,6 +168,8 @@ async function settleResize(term: VirtualTerminal): Promise<void> {
await flushRender(term);
}
withoutTerminalMultiplexer();
describe("TUI overlays", () => {
let savedTerminalEnv: Record<string, string | undefined> = {};
beforeEach(() => {