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) <noreply@anthropic.com>
This commit is contained in:
A 2026-02-11 08:25:15 -08:00 committed by GitHub
parent a2a304d1a0
commit 1f6133dc47
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 237 additions and 64 deletions

View file

@ -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<typeof createConsoleMocks>;
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");
});
});
});

View file

@ -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 () => {

View file

@ -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
});

View file

@ -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");
});