mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 06:04:40 +00:00
test(tui): cover style reset cases
This commit is contained in:
parent
ae134e7e02
commit
da6a2fb5ea
2 changed files with 120 additions and 0 deletions
|
|
@ -1,12 +1,25 @@
|
|||
import assert from "node:assert";
|
||||
import { describe, it } from "node:test";
|
||||
import type { Terminal as XtermTerminalType } from "@xterm/headless";
|
||||
import { Chalk } from "chalk";
|
||||
import { Markdown } from "../src/components/markdown.js";
|
||||
import { type Component, TUI } from "../src/tui.js";
|
||||
import { defaultMarkdownTheme } from "./test-themes.js";
|
||||
import { VirtualTerminal } from "./virtual-terminal.js";
|
||||
|
||||
// Force full color in CI so ANSI assertions are deterministic
|
||||
const chalk = new Chalk({ level: 3 });
|
||||
|
||||
function getCellItalic(terminal: VirtualTerminal, row: number, col: number): number {
|
||||
const xterm = (terminal as unknown as { xterm: XtermTerminalType }).xterm;
|
||||
const buffer = xterm.buffer.active;
|
||||
const line = buffer.getLine(buffer.viewportY + row);
|
||||
assert.ok(line, `Missing buffer line at row ${row}`);
|
||||
const cell = line.getCell(col);
|
||||
assert.ok(cell, `Missing cell at row ${row} col ${col}`);
|
||||
return cell.isItalic();
|
||||
}
|
||||
|
||||
describe("Markdown component", () => {
|
||||
describe("Nested lists", () => {
|
||||
it("should render simple nested list", () => {
|
||||
|
|
@ -437,6 +450,41 @@ describe("Markdown component", () => {
|
|||
// Should have bold codes (1 or 22 for bold on/off)
|
||||
assert.ok(joinedOutput.includes("\x1b[1m"), "Should have bold code");
|
||||
});
|
||||
|
||||
it("should not leak styles into following lines when rendered in TUI", async () => {
|
||||
class MarkdownWithInput implements Component {
|
||||
public markdownLineCount = 0;
|
||||
|
||||
constructor(private readonly markdown: Markdown) {}
|
||||
|
||||
render(width: number): string[] {
|
||||
const lines = this.markdown.render(width);
|
||||
this.markdownLineCount = lines.length;
|
||||
return [...lines, "INPUT"];
|
||||
}
|
||||
|
||||
invalidate(): void {
|
||||
this.markdown.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
const markdown = new Markdown("This is thinking with `inline code`", 1, 0, defaultMarkdownTheme, {
|
||||
color: (text) => chalk.gray(text),
|
||||
italic: true,
|
||||
});
|
||||
|
||||
const terminal = new VirtualTerminal(80, 6);
|
||||
const tui = new TUI(terminal);
|
||||
const component = new MarkdownWithInput(markdown);
|
||||
tui.addChild(component);
|
||||
tui.start();
|
||||
await terminal.flush();
|
||||
|
||||
assert.ok(component.markdownLineCount > 0);
|
||||
const inputRow = component.markdownLineCount;
|
||||
assert.strictEqual(getCellItalic(terminal, inputRow, 0), 0);
|
||||
tui.stop();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Spacing after code blocks", () => {
|
||||
|
|
|
|||
72
packages/tui/test/tui-overlay-style-leak.test.ts
Normal file
72
packages/tui/test/tui-overlay-style-leak.test.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import assert from "node:assert";
|
||||
import { describe, it } from "node:test";
|
||||
import type { Terminal as XtermTerminalType } from "@xterm/headless";
|
||||
import { type Component, TUI } from "../src/tui.js";
|
||||
import { VirtualTerminal } from "./virtual-terminal.js";
|
||||
|
||||
class StaticLines implements Component {
|
||||
constructor(private readonly lines: string[]) {}
|
||||
|
||||
render(): string[] {
|
||||
return this.lines;
|
||||
}
|
||||
|
||||
invalidate(): void {}
|
||||
}
|
||||
|
||||
class StaticOverlay implements Component {
|
||||
constructor(private readonly line: string) {}
|
||||
|
||||
render(): string[] {
|
||||
return [this.line];
|
||||
}
|
||||
|
||||
invalidate(): void {}
|
||||
}
|
||||
|
||||
function getCellItalic(terminal: VirtualTerminal, row: number, col: number): number {
|
||||
const xterm = (terminal as unknown as { xterm: XtermTerminalType }).xterm;
|
||||
const buffer = xterm.buffer.active;
|
||||
const line = buffer.getLine(buffer.viewportY + row);
|
||||
assert.ok(line, `Missing buffer line at row ${row}`);
|
||||
const cell = line.getCell(col);
|
||||
assert.ok(cell, `Missing cell at row ${row} col ${col}`);
|
||||
return cell.isItalic();
|
||||
}
|
||||
|
||||
async function renderAndFlush(tui: TUI, terminal: VirtualTerminal): Promise<void> {
|
||||
tui.requestRender(true);
|
||||
await new Promise<void>((resolve) => process.nextTick(resolve));
|
||||
await terminal.flush();
|
||||
}
|
||||
|
||||
describe("TUI overlay compositing", () => {
|
||||
it("should not leak styles when a trailing reset sits beyond the last visible column (no overlay)", async () => {
|
||||
const width = 20;
|
||||
const baseLine = `\x1b[3m${"X".repeat(width)}\x1b[23m`;
|
||||
|
||||
const terminal = new VirtualTerminal(width, 6);
|
||||
const tui = new TUI(terminal);
|
||||
tui.addChild(new StaticLines([baseLine, "INPUT"]));
|
||||
tui.start();
|
||||
await renderAndFlush(tui, terminal);
|
||||
assert.strictEqual(getCellItalic(terminal, 1, 0), 0);
|
||||
tui.stop();
|
||||
});
|
||||
|
||||
it("should not leak styles when overlay slicing drops trailing SGR resets", async () => {
|
||||
const width = 20;
|
||||
const baseLine = `\x1b[3m${"X".repeat(width)}\x1b[23m`;
|
||||
|
||||
const terminal = new VirtualTerminal(width, 6);
|
||||
const tui = new TUI(terminal);
|
||||
tui.addChild(new StaticLines([baseLine, "INPUT"]));
|
||||
|
||||
tui.showOverlay(new StaticOverlay("OVR"), { row: 0, col: 5, width: 3 });
|
||||
tui.start();
|
||||
await renderAndFlush(tui, terminal);
|
||||
|
||||
assert.strictEqual(getCellItalic(terminal, 1, 0), 0);
|
||||
tui.stop();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue