pi-mono/packages/tui/test/mouse-components.test.ts
2026-08-14 15:26:27 +02:00

153 lines
4.4 KiB
TypeScript

import assert from "node:assert";
import { describe, it } from "node:test";
import { Editor, type EditorTheme } from "../src/components/editor.ts";
import { Input } from "../src/components/input.ts";
import { SelectList, type SelectListTheme } from "../src/components/select-list.ts";
import { SettingsList, type SettingsListTheme } from "../src/components/settings-list.ts";
import { Container, type TuiMouseEvent, type TuiMouseEventType } from "../src/tui.ts";
import { TuiAltScreen } from "../src/tui-alt-screen.ts";
import { VirtualTerminal } from "./virtual-terminal.ts";
function mouse(type: TuiMouseEventType, x: number, y: number, width = 80, height = 10): TuiMouseEvent {
return {
type,
button: "left",
x,
y,
screenX: x,
screenY: y,
width,
height,
shift: false,
alt: false,
ctrl: false,
...(type === "click" ? { clickCount: 1 } : {}),
};
}
const selectTheme: SelectListTheme = {
selectedPrefix: (text) => text,
selectedText: (text) => text,
description: (text) => text,
scrollInfo: (text) => text,
noMatch: (text) => text,
};
const settingsTheme: SettingsListTheme = {
label: (text) => text,
value: (text) => text,
description: (text) => text,
cursor: "> ",
hint: (text) => text,
};
const editorTheme: EditorTheme = {
borderColor: (text) => text,
selectList: selectTheme,
};
class InputOverlay extends Container {
readonly input = new Input();
constructor() {
super();
this.addChild(this.input);
}
handleInput(data: string): void {
this.input.handleInput(data);
}
}
describe("mouse-aware components", () => {
it("positions a single-line input cursor on press", () => {
const input = new Input();
input.setValue("hello");
input.render(20);
assert.strictEqual(input.handleMouse(mouse("press", 4, 0, 20, 1))?.handled, true);
input.handleInput("X");
assert.strictEqual(input.getValue(), "heXllo");
});
it("selects and activates list rows", () => {
const list = new SelectList(
[
{ value: "a", label: "A" },
{ value: "b", label: "B" },
{ value: "c", label: "C" },
{ value: "d", label: "D" },
{ value: "e", label: "E" },
],
3,
selectTheme,
);
let selected: string | undefined;
list.onSelect = (item) => {
selected = item.value;
};
assert.strictEqual(list.handleMouse(mouse("press", 1, 2, 40, 3))?.handled, true);
assert.strictEqual(list.getSelectedItem()?.value, "c");
assert.strictEqual(list.handleMouse(mouse("click", 1, 2, 40, 3))?.handled, true);
assert.strictEqual(selected, "c");
});
it("activates settings rows", () => {
const changes: Array<{ id: string; value: string }> = [];
const list = new SettingsList(
[
{ id: "mode", label: "Mode", currentValue: "one", values: ["one", "two"] },
{ id: "other", label: "Other", currentValue: "off", values: ["off", "on"] },
{ id: "third", label: "Third", currentValue: "low", values: ["low", "high"] },
{ id: "fourth", label: "Fourth", currentValue: "x", values: ["x", "y"] },
],
3,
settingsTheme,
(id, value) => changes.push({ id, value }),
() => {},
);
list.handleMouse(mouse("press", 1, 2, 40, 5));
list.handleMouse(mouse("click", 1, 2, 40, 5));
assert.deepStrictEqual(changes, [{ id: "third", value: "high" }]);
});
it("keeps a delegating overlay focused when its nested input is clicked", async () => {
const terminal = new VirtualTerminal(20, 4);
const tui = new TuiAltScreen(terminal);
const overlay = new InputOverlay();
overlay.input.setValue("hi");
tui.start();
tui.showOverlay(overlay, { anchor: "top-left", width: 20 });
await terminal.waitForRender();
terminal.sendInput("\x1b[<0;5;1M");
terminal.sendInput("\x1b[<0;5;1m");
terminal.sendInput("!");
await terminal.waitForRender();
assert.strictEqual(overlay.input.getValue(), "hi!");
assert.strictEqual(tui.getFocusedComponent(), overlay);
tui.stop();
});
it("positions and focuses the multiline editor through alternate-screen dispatch", async () => {
const terminal = new VirtualTerminal(20, 6);
const tui = new TuiAltScreen(terminal);
const editor = new Editor(tui, editorTheme);
editor.setText("hello");
tui.addChild(editor);
tui.start();
await terminal.waitForRender();
terminal.sendInput("\x1b[<0;3;2M");
terminal.sendInput("\x1b[<0;3;2m");
terminal.sendInput("X");
await terminal.waitForRender();
assert.strictEqual(editor.getText(), "heXllo");
assert.strictEqual(tui.getFocusedComponent(), editor);
tui.stop();
});
});