Files
openclaw/src/cli/program/help.test.ts

126 lines
3.8 KiB
TypeScript

import { Command } from "commander";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { ProgramContext } from "./context.js";
const hasEmittedCliBannerMock = vi.fn(() => false);
const formatCliBannerLineMock = vi.fn(() => "BANNER-LINE");
const formatDocsLinkMock = vi.fn((_path: string, full: string) => `https://${full}`);
vi.mock("../../terminal/links.js", () => ({
formatDocsLink: formatDocsLinkMock,
}));
vi.mock("../../terminal/theme.js", () => ({
isRich: () => false,
theme: {
heading: (s: string) => s,
muted: (s: string) => s,
option: (s: string) => s,
command: (s: string) => s,
error: (s: string) => s,
},
}));
vi.mock("../banner.js", () => ({
formatCliBannerLine: formatCliBannerLineMock,
hasEmittedCliBanner: hasEmittedCliBannerMock,
}));
vi.mock("../cli-name.js", () => ({
resolveCliName: () => "openclaw",
replaceCliName: (cmd: string) => cmd,
}));
vi.mock("./command-registry.js", () => ({
getCoreCliCommandsWithSubcommands: () => ["models", "message"],
}));
vi.mock("./register.subclis.js", () => ({
getSubCliCommandsWithSubcommands: () => ["gateway"],
}));
const { configureProgramHelp } = await import("./help.js");
const testProgramContext: ProgramContext = {
programVersion: "9.9.9-test",
channelOptions: ["telegram"],
messageChannelOptions: "telegram",
agentChannelOptions: "last|telegram",
};
describe("configureProgramHelp", () => {
let originalArgv: string[];
beforeEach(() => {
vi.clearAllMocks();
originalArgv = [...process.argv];
hasEmittedCliBannerMock.mockReturnValue(false);
});
afterEach(() => {
process.argv = originalArgv;
});
function makeProgramWithCommands() {
const program = new Command();
program.command("models").description("models");
program.command("status").description("status");
return program;
}
function captureHelpOutput(program: Command): string {
let output = "";
const writeSpy = vi.spyOn(process.stdout, "write").mockImplementation(((
chunk: string | Uint8Array,
) => {
output += String(chunk);
return true;
}) as typeof process.stdout.write);
try {
program.outputHelp();
return output;
} finally {
writeSpy.mockRestore();
}
}
it("adds root help hint and marks commands with subcommands", () => {
process.argv = ["node", "openclaw", "--help"];
const program = makeProgramWithCommands();
configureProgramHelp(program, testProgramContext);
const help = captureHelpOutput(program);
expect(help).toContain("Hint: commands suffixed with * have subcommands");
expect(help).toContain("models *");
expect(help).toContain("status");
expect(help).not.toContain("status *");
});
it("includes banner and docs/examples in root help output", () => {
process.argv = ["node", "openclaw", "--help"];
const program = makeProgramWithCommands();
configureProgramHelp(program, testProgramContext);
const help = captureHelpOutput(program);
expect(help).toContain("BANNER-LINE");
expect(help).toContain("Examples:");
expect(help).toContain("https://docs.openclaw.ai/cli");
});
it("prints version and exits immediately when version flags are present", () => {
process.argv = ["node", "openclaw", "--version"];
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const exitSpy = vi.spyOn(process, "exit").mockImplementation(((code?: number) => {
throw new Error(`exit:${code ?? ""}`);
}) as typeof process.exit);
const program = makeProgramWithCommands();
expect(() => configureProgramHelp(program, testProgramContext)).toThrow("exit:0");
expect(logSpy).toHaveBeenCalledWith("9.9.9-test");
expect(exitSpy).toHaveBeenCalledWith(0);
logSpy.mockRestore();
exitSpy.mockRestore();
});
});