refactor(agents): dedupe plugin hooks and test helpers

This commit is contained in:
Peter Steinberger
2026-02-22 07:38:24 +00:00
parent 75c1bfbae8
commit 185fba1d22
16 changed files with 661 additions and 579 deletions

View File

@@ -41,6 +41,22 @@ const testItems = [
];
describe("SearchableSelectList", () => {
function expectDescriptionVisibilityAtWidth(width: number, shouldContainDescription: boolean) {
const items = [
{ value: "one", label: "one", description: "desc" },
{ value: "two", label: "two", description: "desc" },
];
const list = new SearchableSelectList(items, 5, mockTheme);
// Ensure first row is non-selected so description styling path is exercised.
list.setSelectedIndex(1);
const output = list.render(width).join("\n");
if (shouldContainDescription) {
expect(output).toContain("(desc)");
} else {
expect(output).not.toContain("(desc)");
}
}
it("renders all items when no filter is applied", () => {
const list = new SearchableSelectList(testItems, 5, mockTheme);
const output = list.render(80);
@@ -61,27 +77,11 @@ describe("SearchableSelectList", () => {
});
it("does not show description layout at width 40 (boundary)", () => {
const items = [
{ value: "one", label: "one", description: "desc" },
{ value: "two", label: "two", description: "desc" },
];
const list = new SearchableSelectList(items, 5, mockTheme);
list.setSelectedIndex(1); // ensure first row is not selected so description styling is applied
const output = list.render(40).join("\n");
expect(output).not.toContain("(desc)");
expectDescriptionVisibilityAtWidth(40, false);
});
it("shows description layout at width 41 (boundary)", () => {
const items = [
{ value: "one", label: "one", description: "desc" },
{ value: "two", label: "two", description: "desc" },
];
const list = new SearchableSelectList(items, 5, mockTheme);
list.setSelectedIndex(1); // ensure first row is not selected so description styling is applied
const output = list.render(41).join("\n");
expect(output).toContain("(desc)");
expectDescriptionVisibilityAtWidth(41, true);
});
it("keeps ANSI-highlighted description rows within terminal width", () => {

View File

@@ -1,6 +1,57 @@
import { describe, expect, it, vi } from "vitest";
import { createCommandHandlers } from "./tui-command-handlers.js";
function createHarness(params?: {
sendChat?: ReturnType<typeof vi.fn>;
resetSession?: ReturnType<typeof vi.fn>;
loadHistory?: ReturnType<typeof vi.fn>;
setActivityStatus?: ReturnType<typeof vi.fn>;
}) {
const sendChat = params?.sendChat ?? vi.fn().mockResolvedValue({ runId: "r1" });
const resetSession = params?.resetSession ?? vi.fn().mockResolvedValue({ ok: true });
const addUser = vi.fn();
const addSystem = vi.fn();
const requestRender = vi.fn();
const loadHistory = params?.loadHistory ?? vi.fn().mockResolvedValue(undefined);
const setActivityStatus = params?.setActivityStatus ?? vi.fn();
const { handleCommand } = createCommandHandlers({
client: { sendChat, resetSession } as never,
chatLog: { addUser, addSystem } as never,
tui: { requestRender } as never,
opts: {},
state: {
currentSessionKey: "agent:main:main",
activeChatRunId: null,
sessionInfo: {},
} as never,
deliverDefault: false,
openOverlay: vi.fn(),
closeOverlay: vi.fn(),
refreshSessionInfo: vi.fn(),
loadHistory,
setSession: vi.fn(),
refreshAgents: vi.fn(),
abortActive: vi.fn(),
setActivityStatus,
formatSessionKey: vi.fn(),
applySessionInfoFromPatch: vi.fn(),
noteLocalRunId: vi.fn(),
forgetLocalRunId: vi.fn(),
});
return {
handleCommand,
sendChat,
resetSession,
addUser,
addSystem,
requestRender,
loadHistory,
setActivityStatus,
};
}
describe("tui command handlers", () => {
it("renders the sending indicator before chat.send resolves", async () => {
let resolveSend: ((value: { runId: string }) => void) | null = null;
@@ -55,35 +106,7 @@ describe("tui command handlers", () => {
});
it("forwards unknown slash commands to the gateway", async () => {
const sendChat = vi.fn().mockResolvedValue({ runId: "r1" });
const addUser = vi.fn();
const addSystem = vi.fn();
const requestRender = vi.fn();
const setActivityStatus = vi.fn();
const { handleCommand } = createCommandHandlers({
client: { sendChat } as never,
chatLog: { addUser, addSystem } as never,
tui: { requestRender } as never,
opts: {},
state: {
currentSessionKey: "agent:main:main",
activeChatRunId: null,
sessionInfo: {},
} as never,
deliverDefault: false,
openOverlay: vi.fn(),
closeOverlay: vi.fn(),
refreshSessionInfo: vi.fn(),
loadHistory: vi.fn(),
setSession: vi.fn(),
refreshAgents: vi.fn(),
abortActive: vi.fn(),
setActivityStatus,
formatSessionKey: vi.fn(),
applySessionInfoFromPatch: vi.fn(),
noteLocalRunId: vi.fn(),
});
const { handleCommand, sendChat, addUser, addSystem, requestRender } = createHarness();
await handleCommand("/context");
@@ -99,34 +122,8 @@ describe("tui command handlers", () => {
});
it("passes reset reason when handling /new and /reset", async () => {
const resetSession = vi.fn().mockResolvedValue({ ok: true });
const addSystem = vi.fn();
const requestRender = vi.fn();
const loadHistory = vi.fn().mockResolvedValue(undefined);
const { handleCommand } = createCommandHandlers({
client: { resetSession } as never,
chatLog: { addSystem } as never,
tui: { requestRender } as never,
opts: {},
state: {
currentSessionKey: "agent:main:main",
activeChatRunId: null,
sessionInfo: {},
} as never,
deliverDefault: false,
openOverlay: vi.fn(),
closeOverlay: vi.fn(),
refreshSessionInfo: vi.fn(),
loadHistory,
setSession: vi.fn(),
refreshAgents: vi.fn(),
abortActive: vi.fn(),
setActivityStatus: vi.fn(),
formatSessionKey: vi.fn(),
applySessionInfoFromPatch: vi.fn(),
noteLocalRunId: vi.fn(),
});
const { handleCommand, resetSession } = createHarness({ loadHistory });
await handleCommand("/new");
await handleCommand("/reset");
@@ -135,4 +132,17 @@ describe("tui command handlers", () => {
expect(resetSession).toHaveBeenNthCalledWith(2, "agent:main:main", "reset");
expect(loadHistory).toHaveBeenCalledTimes(2);
});
it("reports send failures and marks activity status as error", async () => {
const setActivityStatus = vi.fn();
const { handleCommand, addSystem } = createHarness({
sendChat: vi.fn().mockRejectedValue(new Error("gateway down")),
setActivityStatus,
});
await handleCommand("/context");
expect(addSystem).toHaveBeenCalledWith("send failed: Error: gateway down");
expect(setActivityStatus).toHaveBeenLastCalledWith("error");
});
});