mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 03:28:29 +00:00
refactor(agents): dedupe plugin hooks and test helpers
This commit is contained in:
@@ -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", () => {
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user