Files
oh-my-pi/packages/tui/test/input.test.ts
T
can1357 acf4b45062 fix(coding-agent): backported pi-mono changes (5133697..15e0957b0)
packages/ai:
- fix: improve GitHub Copilot OAuth polling and Codex stream recovery
- fix: allow google-vertex authentication via GOOGLE_CLOUD_API_KEY
- fix: send Gemini/Claude provider-specific thinking headers and thought-signature fallbacks correctly
- test: added coverage for Gemini CLI alignment, Codex streaming, and stream edge cases

packages/coding-agent:
- feat: add treeFilterMode setting for the session tree selector default
- fix: prefer later-loaded explicit extensions when commands conflict
- fix: truncate serialized tool results during compaction summarization to prevent overflow
- fix: normalize CRLF in write tool previews
- fix: use shell-based external editor launch on Windows
- test: added compaction serialization and extension runner precedence coverage

packages/tui:
- fix: chain slash-command argument autocomplete after tab-completing the command name
- fix: normalize pasted tabs in Input using configured indentation
- fix: render blockquote lists, tables, and fenced code blocks correctly
- fix: ignore unsupported Kitty CSI-u modifiers and enable modifyOtherKeys fallback cleanup
- test: added editor, input, keys, and markdown regression coverage
2026-03-10 07:27:30 +01:00

169 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from "bun:test";
import { CURSOR_MARKER } from "@oh-my-pi/pi-tui";
import { Input } from "@oh-my-pi/pi-tui/components/input";
import { setKittyProtocolActive } from "@oh-my-pi/pi-tui/keys";
import { visibleWidth } from "@oh-my-pi/pi-tui/utils";
import { getIndentation } from "@oh-my-pi/pi-utils";
function renderedWidth(input: Input, width: number): number {
const [line] = input.render(width);
// TUI strips this marker before its width verification; tests should mimic that.
return visibleWidth(line.replaceAll(CURSOR_MARKER, ""));
}
describe("Input component", () => {
const wordLeft = "\x1bb"; // ESC-b (alt+b)
const wordRight = "\x1bf"; // ESC-f (alt+f)
function setupAtEnd(text: string): Input {
const input = new Input();
input.focused = true;
input.setValue(text);
input.handleInput("\x05"); // Ctrl+E (end)
return input;
}
it("moves by CJK and punctuation blocks (backward)", () => {
const text = "天气不错,去散步吧!";
{
const input = setupAtEnd(text);
input.handleInput(wordLeft);
input.handleInput("|");
expect(input.getValue()).toBe("天气不错,去散步吧|!");
}
{
const input = setupAtEnd(text);
input.handleInput(wordLeft);
input.handleInput(wordLeft);
input.handleInput("|");
expect(input.getValue()).toBe("天气不错,|去散步吧!");
}
{
const input = setupAtEnd(text);
input.handleInput(wordLeft);
input.handleInput(wordLeft);
input.handleInput(wordLeft);
input.handleInput("|");
expect(input.getValue()).toBe("天气不错|,去散步吧!");
}
{
const input = setupAtEnd(text);
input.handleInput(wordLeft);
input.handleInput(wordLeft);
input.handleInput(wordLeft);
input.handleInput(wordLeft);
input.handleInput("|");
expect(input.getValue()).toBe("|天气不错,去散步吧!");
}
});
it("moves by CJK and punctuation blocks (forward)", () => {
const text = "天气不错,去散步吧!";
const input = new Input();
input.focused = true;
input.setValue(text);
input.handleInput("\x01"); // Ctrl+A (start)
input.handleInput(wordRight);
input.handleInput("|");
expect(input.getValue()).toBe("天气不错|,去散步吧!");
});
it("treats NBSP as whitespace for word navigation", () => {
const nbsp = "\u00A0";
const text = `Hola${nbsp}mundo`;
const input = setupAtEnd(text);
input.handleInput(wordLeft);
input.handleInput("|");
expect(input.getValue()).toBe(`Hola${nbsp}|mundo`);
});
it("keeps common joiners inside words", () => {
{
const text = "co-operate l’été";
const input = setupAtEnd(text);
input.handleInput(wordLeft);
input.handleInput("|");
expect(input.getValue()).toBe("co-operate |l’été");
}
{
const text = "co-operate l’été";
const input = setupAtEnd(text);
input.handleInput(wordLeft);
input.handleInput(wordLeft);
input.handleInput("|");
expect(input.getValue()).toBe("|co-operate l’été");
}
});
it("recognizes Unicode punctuation as delimiter blocks", () => {
{
const text = "¿Cómo estás? ¡Muy bien!";
const input = setupAtEnd(text);
input.handleInput(wordLeft);
input.handleInput("|");
expect(input.getValue()).toBe("¿Cómo estás? ¡Muy bien|!");
}
{
const text = "¿Cómo estás? ¡Muy bien!";
const input = setupAtEnd(text);
input.handleInput(wordLeft);
input.handleInput(wordLeft);
input.handleInput("|");
expect(input.getValue()).toBe("¿Cómo estás? ¡Muy |bien!");
}
});
it("does not delete twice when Kitty sends backspace press and release", () => {
setKittyProtocolActive(true);
const input = setupAtEnd("ab");
input.handleInput("\x1b[127u");
expect(input.getValue()).toBe("a");
input.handleInput("\x1b[127;1:3u");
expect(input.getValue()).toBe("a");
setKittyProtocolActive(false);
});
it("inserts NumLock keypad digits from Kitty CSI-u input", () => {
setKittyProtocolActive(true);
const input = setupAtEnd("a");
input.handleInput("\x1b[57407;129u");
expect(input.getValue()).toBe("a8");
setKittyProtocolActive(false);
});
it("normalizes tabs in buffered bracketed paste using configured indentation", () => {
const input = setupAtEnd("");
input.handleInput("\x1b[200~a\t");
expect(input.getValue()).toBe("");
input.handleInput("b\r\n");
expect(input.getValue()).toBe("");
input.handleInput("c\x1b[201~");
expect(input.getValue()).toBe(`a${getIndentation()}bc`);
});
it("never renders a line wider than the terminal width (wide chars)", () => {
const input = new Input();
input.focused = true;
// Long wide-script text: string length != terminal cell width.
input.setValue("天气不错,去散步吧!".repeat(50));
input.handleInput("\x05"); // Ctrl+E (end)
const width = 40;
expect(renderedWidth(input, width)).toBeLessThanOrEqual(width);
});
});