Files
oh-my-pi/packages/coding-agent/test/keybindings-escape-components.test.ts
T
can1357 cac628ccce feat(coding-agent): added git isolation and keybinding utilities for autoresearch
- Added git branch isolation for autoresearch sessions with automatic branch creation, reuse, and worktree safety checks.
- Added scope definition sections (Files in Scope, Off Limits, Constraints) to autoresearch template for explicit session boundaries.
- Added keybinding matcher utilities for consistent escape/cancel key handling across interactive components.
- Added ASI metadata validation (hypothesis and rollback context) in log_experiment tool for experiment tracking.
- Refactored keybinding logic across 13 components to use centralized matcher functions instead of inline key checks.
2026-03-22 21:47:48 +01:00

105 lines
2.9 KiB
TypeScript

import { afterEach, beforeAll, describe, expect, it, vi } from "bun:test";
import { getBundledModel } from "@oh-my-pi/pi-ai";
import { KeybindingsManager } from "@oh-my-pi/pi-coding-agent/config/keybindings";
import type { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import { ModelSelectorComponent } from "@oh-my-pi/pi-coding-agent/modes/components/model-selector";
import { SessionSelectorComponent } from "@oh-my-pi/pi-coding-agent/modes/components/session-selector";
import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
import type { SessionInfo } from "@oh-my-pi/pi-coding-agent/session/session-manager";
import { setKeybindings, type TUI } from "@oh-my-pi/pi-tui";
beforeAll(() => {
initTheme();
});
afterEach(() => {
setKeybindings(KeybindingsManager.inMemory());
vi.restoreAllMocks();
});
function createSession(id: string, title: string): SessionInfo {
return {
path: `/tmp/${id}.jsonl`,
id,
cwd: "/tmp",
title,
created: new Date("2024-01-01T00:00:00Z"),
modified: new Date("2024-01-02T00:00:00Z"),
messageCount: 1,
firstMessage: `${title} first message`,
allMessagesText: `${title} first message`,
};
}
describe("component escape bindings", () => {
it("uses app.interrupt for session selector cancel without changing Ctrl+C exit", () => {
const keybindings = KeybindingsManager.inMemory({
"app.interrupt": "alt+x",
});
setKeybindings(keybindings);
const onCancel = vi.fn();
const onExit = vi.fn();
const selector = new SessionSelectorComponent(
[createSession("session-a", "Alpha"), createSession("session-b", "Beta")],
() => {},
onCancel,
onExit,
);
selector.handleInput("\x1b");
expect(onCancel).not.toHaveBeenCalled();
selector.handleInput("\x1bx");
expect(onCancel).toHaveBeenCalledTimes(1);
selector.handleInput("\x03");
expect(onExit).toHaveBeenCalledTimes(1);
});
it("uses tui.select.cancel for model selector cancellation", async () => {
const keybindings = KeybindingsManager.inMemory({
"tui.select.cancel": "ctrl+g",
});
setKeybindings(keybindings);
const model = getBundledModel("anthropic", "claude-sonnet-4-5");
if (!model) {
throw new Error("Expected bundled model anthropic/claude-sonnet-4-5");
}
const settings = Settings.isolated({
modelRoles: {
default: `${model.provider}/${model.id}`,
},
});
const modelRegistry = {
getAll: () => [model],
getDiscoverableProviders: () => [],
} as unknown as ModelRegistry;
const ui = {
requestRender: vi.fn(),
} as unknown as TUI;
const onCancel = vi.fn();
const selector = new ModelSelectorComponent(
ui,
model,
settings,
modelRegistry,
[{ model, thinkingLevel: "off" }],
() => {},
onCancel,
);
await Bun.sleep(0);
selector.handleInput("\x1b");
expect(onCancel).not.toHaveBeenCalled();
selector.handleInput("\x07");
expect(onCancel).toHaveBeenCalledTimes(1);
});
});