From d67597d9646f3a7b8c1f6c48d2a5df53304c9b5a Mon Sep 17 00:00:00 2001 From: Brent <67750428+eggpeat@users.noreply.github.com> Date: Sat, 1 Aug 2026 00:40:37 +0000 Subject: [PATCH] test(cli): compare rendered command help --- .../test/cli-command-metadata.test.ts | 65 +++++++++++++------ 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/packages/coding-agent/test/cli-command-metadata.test.ts b/packages/coding-agent/test/cli-command-metadata.test.ts index 221af0518..69524b6be 100644 --- a/packages/coding-agent/test/cli-command-metadata.test.ts +++ b/packages/coding-agent/test/cli-command-metadata.test.ts @@ -1,30 +1,53 @@ -import { describe, expect, it } from "bun:test"; -import type { CommandMetadata } from "@oh-my-pi/pi-utils/cli"; +import { describe, expect, it, spyOn } from "bun:test"; +import { + type CliConfig, + type CommandCtor, + type CommandMetadata, + renderCommandHelp, + renderRootHelp, +} from "@oh-my-pi/pi-utils/cli"; import { commands } from "../src/cli-commands"; -const METADATA_KEYS = [ - "description", - "hidden", - "flags", - "args", - "examples", -] as const satisfies readonly (keyof CommandMetadata)[]; +function captureStdout(render: () => void): string { + const chunks: string[] = []; + const stdoutSpy = spyOn(process.stdout, "write").mockImplementation(chunk => { + chunks.push(String(chunk)); + return true; + }); + try { + render(); + } finally { + stdoutSpy.mockRestore(); + } + return chunks.join(""); +} describe("CLI command help metadata", () => { - it("is complete and matches every loaded command", async () => { + it("renders the same root help as the loaded command classes", async () => { + const metadata = new Map(); + const constructors = new Map(); for (const entry of commands) { - const help = entry.help; - expect(help, `${entry.name} must provide static help metadata`).toBeDefined(); - if (!help) continue; + expect(entry.help, `${entry.name} must provide static help metadata`).toBeDefined(); + if (!entry.help) continue; + metadata.set(entry.name, entry.help); + constructors.set(entry.name, await entry.load()); + } - const Command = await entry.load(); - for (const key of METADATA_KEYS) { - if (help[key] !== undefined) { - const expected: unknown = help[key]; - const actual: unknown = Command[key]; - expect(expected, `${entry.name}.${key} drifted from its command class`).toEqual(actual); - } - } + const base = { bin: "omp", version: "test" }; + const metadataConfig: CliConfig = { ...base, commands: metadata }; + const constructorConfig: CliConfig = { ...base, commands: constructors }; + const metadataRoot = captureStdout(() => renderRootHelp(metadataConfig)); + const constructorRoot = captureStdout(() => renderRootHelp(constructorConfig)); + expect(metadataRoot).toBe(constructorRoot); + + const visibleNames = commands.filter(entry => !entry.help?.hidden).map(entry => entry.name); + const maxNameLength = Math.max(...visibleNames.map(name => name.length)); + for (const name of visibleNames) { + const Command = constructors.get(name); + if (!Command) throw new Error(`Missing loaded command: ${name}`); + const commandOutput = captureStdout(() => renderCommandHelp("omp", name, Command)); + const description = commandOutput.split("\n", 1)[0]; + expect(metadataRoot).toContain(` ${name.padEnd(maxNameLength + 2)}${description}`); } }); });