From 1f6133dc47cb9fed099f328d83a5cae85355e435 Mon Sep 17 00:00:00 2001 From: A <258483684+la14-1@users.noreply.github.com> Date: Wed, 11 Feb 2026 08:25:15 -0800 Subject: [PATCH] test: fix 5 failing tests, add 31 cmdHelp content tests, remove stale skips (#472) Fix download error tests that checked wrong mock target (consoleMocks.error vs mockLogError) and expected outdated "Troubleshooting" text instead of "How to fix". Remove 11 stale describe.skip blocks from commands.test.ts that have been superseded by dedicated test files using mock.module(). Add cmd-help-content.test.ts with 31 tests verifying help output includes all subcommands, flags, sections, and key content. Before: 4579 pass, 5 fail, 11 skip After: 4615 pass, 0 fail, 0 skip Agent: test-engineer Co-authored-by: A <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) --- cli/src/__tests__/cmd-help-content.test.ts | 222 ++++++++++++++++++ .../commands-update-download.test.ts | 6 +- cli/src/__tests__/commands.test.ts | 62 +---- .../__tests__/download-and-failure.test.ts | 11 +- 4 files changed, 237 insertions(+), 64 deletions(-) create mode 100644 cli/src/__tests__/cmd-help-content.test.ts diff --git a/cli/src/__tests__/cmd-help-content.test.ts b/cli/src/__tests__/cmd-help-content.test.ts new file mode 100644 index 00000000..dd9fbcca --- /dev/null +++ b/cli/src/__tests__/cmd-help-content.test.ts @@ -0,0 +1,222 @@ +import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test"; +import { createConsoleMocks, restoreMocks } from "./test-helpers"; + +/** + * Tests for cmdHelp output completeness in commands.ts. + * + * Existing tests only verify cmdHelp produces some output containing "USAGE" + * and "EXAMPLES" (commands.test.ts). This file verifies that all documented + * subcommands, flags, sections, and key content are present in the help text. + * + * This is important because: + * - Users rely on `spawn help` as the primary reference + * - Missing subcommands or flags lead to support requests + * - The help text must stay in sync with actual CLI capabilities + * + * Agent: test-engineer + */ + +// Mock @clack/prompts to prevent side effects +mock.module("@clack/prompts", () => ({ + spinner: () => ({ + start: mock(() => {}), + stop: mock(() => {}), + message: mock(() => {}), + }), + log: { + step: mock(() => {}), + info: mock(() => {}), + warn: mock(() => {}), + error: mock(() => {}), + success: mock(() => {}), + }, + intro: mock(() => {}), + outro: mock(() => {}), + cancel: mock(() => {}), + select: mock(() => {}), + isCancel: () => false, +})); + +const { cmdHelp } = await import("../commands.js"); + +describe("cmdHelp - content completeness", () => { + let consoleMocks: ReturnType; + + beforeEach(() => { + consoleMocks = createConsoleMocks(); + }); + + afterEach(() => { + restoreMocks(consoleMocks.log, consoleMocks.error); + }); + + function getHelpOutput(): string { + cmdHelp(); + return consoleMocks.log.mock.calls.map((c: any[]) => c.join(" ")).join("\n"); + } + + // ── Required sections ────────────────────────────────────────────── + + describe("required sections", () => { + it("should have a USAGE section", () => { + expect(getHelpOutput()).toContain("USAGE"); + }); + + it("should have an EXAMPLES section", () => { + expect(getHelpOutput()).toContain("EXAMPLES"); + }); + + it("should have an AUTHENTICATION section", () => { + expect(getHelpOutput()).toContain("AUTHENTICATION"); + }); + + it("should have an INSTALL section", () => { + expect(getHelpOutput()).toContain("INSTALL"); + }); + + it("should have a TROUBLESHOOTING section", () => { + expect(getHelpOutput()).toContain("TROUBLESHOOTING"); + }); + + it("should have a MORE INFO section", () => { + expect(getHelpOutput()).toContain("MORE INFO"); + }); + }); + + // ── Documented subcommands ───────────────────────────────────────── + + describe("subcommands in USAGE", () => { + it("should document spawn list subcommand", () => { + expect(getHelpOutput()).toContain("spawn list"); + }); + + it("should document spawn agents subcommand", () => { + expect(getHelpOutput()).toContain("spawn agents"); + }); + + it("should document spawn clouds subcommand", () => { + expect(getHelpOutput()).toContain("spawn clouds"); + }); + + it("should document spawn update subcommand", () => { + expect(getHelpOutput()).toContain("spawn update"); + }); + + it("should document spawn version subcommand", () => { + expect(getHelpOutput()).toContain("spawn version"); + }); + + it("should document spawn help subcommand", () => { + expect(getHelpOutput()).toContain("spawn help"); + }); + + it("should document the ls alias for list", () => { + const output = getHelpOutput(); + expect(output).toContain("ls"); + }); + }); + + // ── Documented flags ─────────────────────────────────────────────── + + describe("flags in USAGE", () => { + it("should document --prompt flag", () => { + expect(getHelpOutput()).toContain("--prompt"); + }); + + it("should document -p short form for --prompt", () => { + expect(getHelpOutput()).toContain("-p"); + }); + + it("should document --prompt-file flag", () => { + expect(getHelpOutput()).toContain("--prompt-file"); + }); + }); + + // ── Examples ─────────────────────────────────────────────────────── + + describe("examples", () => { + it("should show interactive usage example", () => { + const output = getHelpOutput(); + // "spawn" alone for interactive + expect(output).toMatch(/spawn\s+#.*[Ii]nteractive/); + }); + + it("should show direct launch example with agent and cloud", () => { + const output = getHelpOutput(); + expect(output).toContain("spawn claude sprite"); + }); + + it("should show --prompt example", () => { + const output = getHelpOutput(); + expect(output).toContain("--prompt"); + }); + + it("should show --prompt-file example", () => { + const output = getHelpOutput(); + expect(output).toContain("--prompt-file"); + }); + + it("should show agent info example", () => { + const output = getHelpOutput(); + // e.g., "spawn claude # Show which clouds support Claude" + expect(output).toMatch(/spawn claude\s+#/); + }); + }); + + // ── Authentication info ──────────────────────────────────────────── + + describe("authentication information", () => { + it("should mention OpenRouter", () => { + expect(getHelpOutput()).toContain("OpenRouter"); + }); + + it("should include OpenRouter API key URL", () => { + expect(getHelpOutput()).toContain("openrouter.ai/settings/keys"); + }); + + it("should mention OPENROUTER_API_KEY env var", () => { + expect(getHelpOutput()).toContain("OPENROUTER_API_KEY"); + }); + }); + + // ── Install section ──────────────────────────────────────────────── + + describe("install instructions", () => { + it("should include curl install command", () => { + expect(getHelpOutput()).toContain("curl -fsSL"); + }); + + it("should include install.sh path", () => { + expect(getHelpOutput()).toContain("install.sh"); + }); + }); + + // ── Troubleshooting ──────────────────────────────────────────────── + + describe("troubleshooting tips", () => { + it("should mention spawn list for script not found", () => { + const output = getHelpOutput(); + expect(output).toContain("spawn list"); + }); + + it("should mention SPAWN_NO_UNICODE for garbled output", () => { + expect(getHelpOutput()).toContain("SPAWN_NO_UNICODE"); + }); + + it("should mention SPAWN_NO_UPDATE_CHECK for slow startup", () => { + expect(getHelpOutput()).toContain("SPAWN_NO_UPDATE_CHECK"); + }); + }); + + // ── Links ────────────────────────────────────────────────────────── + + describe("repository links", () => { + it("should include GitHub repository URL", () => { + expect(getHelpOutput()).toContain("github.com"); + }); + + it("should include OpenRouter URL", () => { + expect(getHelpOutput()).toContain("openrouter.ai"); + }); + }); +}); diff --git a/cli/src/__tests__/commands-update-download.test.ts b/cli/src/__tests__/commands-update-download.test.ts index 01dae46c..5e3b3441 100644 --- a/cli/src/__tests__/commands-update-download.test.ts +++ b/cli/src/__tests__/commands-update-download.test.ts @@ -281,8 +281,8 @@ describe("Script download and execution", () => { await expect(cmdRun("claude", "sprite")).rejects.toThrow("process.exit"); expect(processExitSpy).toHaveBeenCalledWith(1); - const errorOutput = consoleMocks.error.mock.calls.map((c: any[]) => c.join(" ")).join("\n"); - expect(errorOutput).toContain("HTTP 500"); + const logErrorOutput = mockLogError.mock.calls.map((c: any[]) => c.join(" ")).join("\n"); + expect(logErrorOutput).toContain("HTTP 500"); }); it("should show troubleshooting info when download throws network error", async () => { @@ -307,7 +307,7 @@ describe("Script download and execution", () => { } const errorOutput = consoleMocks.error.mock.calls.map((c: any[]) => c.join(" ")).join("\n"); - expect(errorOutput).toContain("Troubleshooting"); + expect(errorOutput).toContain("How to fix"); }); it("should use fallback URL when primary returns non-OK status", async () => { diff --git a/cli/src/__tests__/commands.test.ts b/cli/src/__tests__/commands.test.ts index 6d41ab57..d2bb1a05 100644 --- a/cli/src/__tests__/commands.test.ts +++ b/cli/src/__tests__/commands.test.ts @@ -43,60 +43,10 @@ describe("commands", () => { }); }); - // TODO: These tests need refactoring - bun doesn't support module mocking - // Commands.ts should be refactored to use dependency injection for testability - - describe.skip("cmdList - needs dependency injection", () => { - it("should display matrix table with all agents and clouds", async () => { - // Skipped: requires module mocking unsupported by bun - }); - }); - - describe.skip("cmdAgents - needs dependency injection", () => { - it("should list all agents with descriptions", async () => { - // Skipped: requires module mocking unsupported by bun - }); - }); - - describe.skip("cmdClouds - needs dependency injection", () => { - it("should list all clouds with descriptions", async () => { - // Skipped: requires module mocking unsupported by bun - }); - }); - - describe.skip("cmdAgentInfo - needs dependency injection", () => { - it("should show info for a valid agent with implemented clouds", async () => { - // Skipped: requires module mocking unsupported by bun - }); - - it("should show no clouds message when agent has no implementations", async () => { - // Skipped: requires module mocking unsupported by bun - }); - - it("should exit with error for unknown agent", async () => { - // Skipped: requires module mocking unsupported by bun - }); - }); - - describe.skip("cmdRun - needs dependency injection", () => { - it("should launch script for valid agent and cloud", async () => { - // Skipped: requires module mocking unsupported by bun - }); - - it("should exit with error for unknown agent", async () => { - // Skipped: requires module mocking unsupported by bun - }); - - it("should exit with error for unknown cloud", async () => { - // Skipped: requires module mocking unsupported by bun - }); - - it("should exit with error for unimplemented combination", async () => { - // Skipped: requires module mocking unsupported by bun - }); - - it("should fallback to GitHub raw URL when primary URL fails", async () => { - // Skipped: requires module mocking unsupported by bun - }); - }); + // These functions are tested in dedicated files using mock.module(): + // - cmdList: commands-list-grid.test.ts, commands-compact-list.test.ts + // - cmdAgents/cmdClouds: commands-display.test.ts, commands-output.test.ts, commands-list-grid.test.ts + // - cmdAgentInfo/cmdCloudInfo: commands-info-details.test.ts, commands-display.test.ts, cloud-info.test.ts + // - cmdRun: commands-resolve-run.test.ts, commands-swap-resolve.test.ts, cmdrun-resolution.test.ts + // - Download/failure: download-and-failure.test.ts, commands-update-download.test.ts }); diff --git a/cli/src/__tests__/download-and-failure.test.ts b/cli/src/__tests__/download-and-failure.test.ts index 352caa88..3bbb726e 100644 --- a/cli/src/__tests__/download-and-failure.test.ts +++ b/cli/src/__tests__/download-and-failure.test.ts @@ -279,8 +279,8 @@ describe("Download and Failure Pipeline", () => { // Expected } - const errorOutput = consoleMocks.error.mock.calls.map((c: any[]) => c.join(" ")).join("\n"); - expect(errorOutput).toContain("HTTP 500"); + const logErrorOutput = mockLogError.mock.calls.map((c: any[]) => c.join(" ")).join("\n"); + expect(logErrorOutput).toContain("HTTP 500"); }); it("should mention temporary server issues on 500 errors", async () => { @@ -313,9 +313,10 @@ describe("Download and Failure Pipeline", () => { } // Should show HTTP error (not the "script not found" path) - const errorOutput = consoleMocks.error.mock.calls.map((c: any[]) => c.join(" ")).join("\n"); - expect(errorOutput).toContain("HTTP 404"); + const logErrorOutput = mockLogError.mock.calls.map((c: any[]) => c.join(" ")).join("\n"); + expect(logErrorOutput).toContain("HTTP 404"); // 500 from fallback should mention temporary issues + const errorOutput = consoleMocks.error.mock.calls.map((c: any[]) => c.join(" ")).join("\n"); expect(errorOutput).toContain("temporary issues"); }); }); @@ -352,7 +353,7 @@ describe("Download and Failure Pipeline", () => { } const errorOutput = consoleMocks.error.mock.calls.map((c: any[]) => c.join(" ")).join("\n"); - expect(errorOutput).toContain("Troubleshooting"); + expect(errorOutput).toContain("How to fix"); expect(errorOutput).toContain("internet connection"); });