From 91fbe6d1cb8993dec41197a2d4e503cb56605f20 Mon Sep 17 00:00:00 2001 From: Wang Date: Sun, 9 Aug 2026 13:15:40 +0800 Subject: [PATCH] test(cli): tolerate transient incomplete TUI frames --- .../cli/src/__tests__/pi-tui-runner.test.ts | 10 ++++++---- .../src/__tests__/tui-terminal-mock.test.ts | 12 ++++++++++++ .../cli/src/__tests__/tui-terminal-mock.ts | 18 +++++++++++------- 3 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 packages/cli/src/__tests__/tui-terminal-mock.test.ts diff --git a/packages/cli/src/__tests__/pi-tui-runner.test.ts b/packages/cli/src/__tests__/pi-tui-runner.test.ts index 99b25a4954..6aa5c6a129 100644 --- a/packages/cli/src/__tests__/pi-tui-runner.test.ts +++ b/packages/cli/src/__tests__/pi-tui-runner.test.ts @@ -55,7 +55,7 @@ import { BUSY_SPINNER_FRAMES } from '../tui-attention.js'; import { assertBottomPickerPlacement, FakeTerminal, - inputSurfaceRows, + findInputSurfaceRows, latestPlainLineContaining, plainTerminalOutput, WAIT_BUDGET_MS, @@ -5476,7 +5476,7 @@ describe('Maka Pi TUI runner', () => { // Sentinel render: a wrongly-opened picker would be on screen by the time // the typed char paints. terminal.input('z'); - await waitFor(() => editorInputText(terminal).endsWith('z')); + await waitFor(() => editorInputText(terminal)?.endsWith('z') === true); assert.equal(plainTerminalOutput(terminal.screenOutput()).includes('回到选定轮次'), false); exitMaka(terminal); @@ -6489,9 +6489,11 @@ function bellCount(terminal: FakeTerminal): number { return terminal.writes.filter((write) => write === '\x07').length; } -function editorInputText(terminal: FakeTerminal): string { +function editorInputText(terminal: FakeTerminal): string | undefined { const lines = plainTerminalOutput(terminal.screenOutput()).split(/\r?\n/); - const [topEditorBorderIndex, bottomEditorBorderIndex] = inputSurfaceRows(lines); + const inputRows = findInputSurfaceRows(lines); + if (!inputRows) return undefined; + const [topEditorBorderIndex, bottomEditorBorderIndex] = inputRows; return lines .slice(topEditorBorderIndex + 1, bottomEditorBorderIndex) .join('\n') diff --git a/packages/cli/src/__tests__/tui-terminal-mock.test.ts b/packages/cli/src/__tests__/tui-terminal-mock.test.ts new file mode 100644 index 0000000000..95692dfc77 --- /dev/null +++ b/packages/cli/src/__tests__/tui-terminal-mock.test.ts @@ -0,0 +1,12 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; +import { findInputSurfaceRows, inputSurfaceRows } from './tui-terminal-mock.js'; + +test('input surface lookup distinguishes an intermediate frame from a settled frame', () => { + const intermediate = ['────────────────']; + const settled = ['────────────────', 'draft', '────────────────']; + + assert.equal(findInputSurfaceRows(intermediate), undefined); + assert.deepEqual(findInputSurfaceRows(settled), [0, 2]); + assert.throws(() => inputSurfaceRows(intermediate)); +}); diff --git a/packages/cli/src/__tests__/tui-terminal-mock.ts b/packages/cli/src/__tests__/tui-terminal-mock.ts index 04f4858791..9112680730 100644 --- a/packages/cli/src/__tests__/tui-terminal-mock.ts +++ b/packages/cli/src/__tests__/tui-terminal-mock.ts @@ -67,17 +67,23 @@ export function plainTerminalOutput(output: string): string { .replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, ''); } -export function inputSurfaceRows(lines: readonly string[]): [number, number] { +export function findInputSurfaceRows(lines: readonly string[]): [number, number] | undefined { const editorBorderIndexes = lines .map((line, index) => (/^─+$/.test(line) ? index : -1)) .filter((index) => index >= 0); - assert.ok(editorBorderIndexes.length >= 2); + if (editorBorderIndexes.length < 2) return undefined; return [ editorBorderIndexes[editorBorderIndexes.length - 2]!, editorBorderIndexes[editorBorderIndexes.length - 1]!, ]; } +export function inputSurfaceRows(lines: readonly string[]): [number, number] { + const rows = findInputSurfaceRows(lines); + assert.ok(rows, 'expected a settled input surface with top and bottom editor borders'); + return rows; +} + /** * The slash-autocomplete suggestion rows on the input surface: the contiguous * `/command` lines sitting immediately above the editor's top border. The @@ -89,11 +95,9 @@ export function inputSurfaceRows(lines: readonly string[]): [number, number] { */ export function autocompleteSuggestionLines(lines: readonly string[]): readonly string[] { if (!lines.some((line) => line.includes('→'))) return []; - const editorBorders = lines - .map((line, index) => (/^─+$/.test(line) ? index : -1)) - .filter((index) => index >= 0); - if (editorBorders.length < 2) return []; - const editorTopBorder = editorBorders[editorBorders.length - 2]!; + const inputRows = findInputSurfaceRows(lines); + if (!inputRows) return []; + const [editorTopBorder] = inputRows; let start = editorTopBorder; while (start > 0 && /\/\w/.test(lines[start - 1]!)) start -= 1; return lines.slice(start, editorTopBorder);