pi-mono/packages/coding-agent/test/config-selector-keybindings.test.ts
robopipi[bot] b0c64f33c8
fix(coding-agent): load user keybindings in pi config (#11788)
`pi config` ignored `~/.pi/agent/keybindings.json`. `selectConfig()`
never called `setKeybindings()`, so the config selector's
`getKeybindings()` fell back to the default TUI bindings. Remaps such as
`"tui.select.down": ["down", "ctrl+n"]` did nothing there, although they
work in `/settings` and the `--resume` picker.

`selectConfig()` now installs
`KeybindingsManager.create(options.agentDir)` before it builds the UI,
the same way the session picker and startup UI do.

**Testing**
- Added `test/config-selector-keybindings.test.ts`. It runs
`selectConfig()` with a stubbed terminal and component and an agent dir
whose `keybindings.json` remaps `tui.select.up` and `tui.select.down`,
then checks that the active keybindings include `ctrl+p` and `ctrl+n`.
It fails without the change and passes with it.
- Manual check: ran `pi config` in tmux with that `keybindings.json`.
Before the change, Ctrl+N did nothing. After it, Ctrl+N moves the
selection like Down.
- `npm run check` reports only two errors, both missing modules in
`packages/schema/test/bench/candidates.ts`. They occur on `main` without
this change.

Fixes #11651

Requested by @badlogic

Co-authored-by: triagent <triagent@localhost>
2026-11-04 09:59:49 +01:00

75 lines
2.2 KiB
TypeScript

import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { getKeybindings, setKeybindings } from "@earendil-works/pi-tui";
import { afterEach, describe, expect, it, vi } from "vitest";
vi.mock("@earendil-works/pi-tui", async (importOriginal) => {
const actual = await importOriginal<typeof import("@earendil-works/pi-tui")>();
class FakeTui {
terminal = { rows: 24 };
setClearOnShrink() {}
addChild() {}
setFocus() {}
start() {}
stop() {}
requestRender() {}
}
return { ...actual, ProcessTerminal: class {}, TuiMainScreen: FakeTui };
});
vi.mock("../src/modes/interactive/components/config-selector.ts", () => ({
ConfigSelectorComponent: class {
constructor(...args: unknown[]) {
const onClose = args[4] as () => void;
queueMicrotask(onClose);
}
getResourceList() {
return {};
}
},
}));
vi.mock("../src/modes/interactive/theme/theme.ts", () => ({
initTheme: () => {},
stopThemeWatcher: () => {},
}));
import { selectConfig } from "../src/cli/config-selector.ts";
import { KeybindingsManager } from "../src/core/keybindings.ts";
import type { SettingsManager } from "../src/core/settings-manager.ts";
describe("selectConfig keybindings", () => {
let agentDir: string | undefined;
afterEach(() => {
if (agentDir) rmSync(agentDir, { recursive: true, force: true });
agentDir = undefined;
setKeybindings(new KeybindingsManager({}));
});
it("uses the user's keybindings.json", async () => {
agentDir = mkdtempSync(join(tmpdir(), "pi-config-keys-"));
writeFileSync(
join(agentDir, "keybindings.json"),
JSON.stringify({ "tui.select.up": ["up", "ctrl+p"], "tui.select.down": ["down", "ctrl+n"] }),
);
const settingsManager = {
getTheme: () => undefined,
getShowHardwareCursor: () => false,
getClearOnShrink: () => false,
} as unknown as SettingsManager;
await selectConfig({
resolvedPaths: {} as never,
settingsManager,
cwd: agentDir,
agentDir,
writeScope: "global",
projectModeAvailable: false,
});
expect(getKeybindings().getKeys("tui.select.down")).toContain("ctrl+n");
expect(getKeybindings().getKeys("tui.select.up")).toContain("ctrl+p");
});
});