diff --git a/packages/cli/src/__tests__/check-entity-messages.test.ts b/packages/cli/src/__tests__/check-entity-messages.test.ts index 83b9021f..9fc6f9a0 100644 --- a/packages/cli/src/__tests__/check-entity-messages.test.ts +++ b/packages/cli/src/__tests__/check-entity-messages.test.ts @@ -1,4 +1,5 @@ -import { describe, it, expect, beforeEach, mock } from "bun:test"; +import { describe, it, expect, beforeEach } from "bun:test"; +import { mockClackPrompts } from "./test-helpers"; import type { Manifest } from "../manifest"; /** @@ -14,32 +15,7 @@ import type { Manifest } from "../manifest"; * 4. No match at all: just the listCmd hint (existing) */ -// ── Mock @clack/prompts ───────────────────────────────────────────────────── - -const mockLogError = mock(() => {}); -const mockLogInfo = mock(() => {}); - -mock.module("@clack/prompts", () => ({ - spinner: () => ({ - start: mock(() => {}), - stop: mock(() => {}), - message: mock(() => {}), - }), - log: { - step: mock(() => {}), - info: mockLogInfo, - warn: mock(() => {}), - error: mockLogError, - success: mock(() => {}), - }, - intro: mock(() => {}), - outro: mock(() => {}), - cancel: mock(() => {}), - select: mock(() => {}), - autocomplete: mock(async () => "claude"), - text: mock(async () => undefined), - isCancel: () => false, -})); +const { logError: mockLogError, logInfo: mockLogInfo } = mockClackPrompts(); // Import after mocking const { checkEntity } = await import("../commands.js"); diff --git a/packages/cli/src/__tests__/clear-history.test.ts b/packages/cli/src/__tests__/clear-history.test.ts index 292784d6..71717b20 100644 --- a/packages/cli/src/__tests__/clear-history.test.ts +++ b/packages/cli/src/__tests__/clear-history.test.ts @@ -1,7 +1,8 @@ -import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test"; +import { describe, it, expect, beforeEach, afterEach } from "bun:test"; import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { homedir } from "node:os"; +import { mockClackPrompts } from "./test-helpers"; import type { SpawnRecord } from "../history.js"; import { clearHistory, loadHistory, saveSpawnRecord, filterHistory, getHistoryPath } from "../history.js"; @@ -282,31 +283,7 @@ describe("clearHistory", () => { // ── cmdListClear via mock.module ───────────────────────────────────────────── -const mockLogInfo = mock(() => {}); -const mockLogSuccess = mock(() => {}); - -mock.module("@clack/prompts", () => ({ - spinner: () => ({ - start: mock(() => {}), - stop: mock(() => {}), - message: mock(() => {}), - }), - log: { - step: mock(() => {}), - info: mockLogInfo, - error: mock(() => {}), - warn: mock(() => {}), - success: mockLogSuccess, - }, - intro: mock(() => {}), - outro: mock(() => {}), - cancel: mock(() => {}), - select: mock(() => {}), - autocomplete: mock(async () => "claude"), - text: mock(async () => undefined), - isCancel: () => false, - confirm: mock(() => Promise.resolve(true)), -})); +const { logInfo: mockLogInfo, logSuccess: mockLogSuccess } = mockClackPrompts(); // Import after mock setup const { cmdListClear } = await import("../commands.js"); diff --git a/packages/cli/src/__tests__/cmd-interactive.test.ts b/packages/cli/src/__tests__/cmd-interactive.test.ts index 8f415301..a1247d26 100644 --- a/packages/cli/src/__tests__/cmd-interactive.test.ts +++ b/packages/cli/src/__tests__/cmd-interactive.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test"; -import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers"; +import { createMockManifest, createConsoleMocks, restoreMocks, mockClackPrompts } from "./test-helpers"; import { loadManifest } from "../manifest"; import { isString } from "@openrouter/spawn-shared"; @@ -24,36 +24,19 @@ let selectCallIndex = 0; let selectReturnValues: any[] = []; let isCancelValues: Set = new Set(); -// Mock @clack/prompts -const mockLogError = mock(() => {}); -const mockLogInfo = mock(() => {}); -const mockLogStep = mock(() => {}); -const mockLogWarn = mock(() => {}); -const mockIntro = mock(() => {}); -const mockOutro = mock(() => {}); -const mockCancel = mock(() => {}); -const mockConfirm = mock(async () => true); -const mockSpinnerStart = mock(() => {}); -const mockSpinnerStop = mock(() => {}); -const mockSpinnerMessage = mock(() => {}); - -mock.module("@clack/prompts", () => ({ - spinner: () => ({ - start: mockSpinnerStart, - stop: mockSpinnerStop, - message: mockSpinnerMessage, - }), - log: { - step: mockLogStep, - info: mockLogInfo, - error: mockLogError, - warn: mockLogWarn, - }, +const { + logError: mockLogError, + logInfo: mockLogInfo, + logStep: mockLogStep, + logWarn: mockLogWarn, intro: mockIntro, outro: mockOutro, cancel: mockCancel, confirm: mockConfirm, - text: mock(async () => undefined), + spinnerStart: mockSpinnerStart, + spinnerStop: mockSpinnerStop, + spinnerMessage: mockSpinnerMessage, +} = mockClackPrompts({ autocomplete: mock(async () => { const value = selectReturnValues[selectCallIndex] ?? "claude"; selectCallIndex++; @@ -64,8 +47,8 @@ mock.module("@clack/prompts", () => ({ selectCallIndex++; return value; }), - isCancel: (value: any) => isCancelValues.has(value), -})); + isCancel: (value: unknown) => isCancelValues.has(value), +}); // Import commands after mock setup const { cmdInteractive } = await import("../commands.js"); diff --git a/packages/cli/src/__tests__/cmd-listing-output.test.ts b/packages/cli/src/__tests__/cmd-listing-output.test.ts index 97654043..4c4906e5 100644 --- a/packages/cli/src/__tests__/cmd-listing-output.test.ts +++ b/packages/cli/src/__tests__/cmd-listing-output.test.ts @@ -2,7 +2,7 @@ import type { spyOn } from "bun:test"; import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test"; import type { Manifest } from "../manifest"; import { loadManifest } from "../manifest"; -import { createConsoleMocks, restoreMocks } from "./test-helpers"; +import { createConsoleMocks, restoreMocks, mockClackPrompts } from "./test-helpers"; /** * Tests for cmdMatrix, cmdAgents, and cmdClouds listing command output. @@ -130,32 +130,7 @@ const multiTypeManifest: Manifest = { }, }; -// ── Mock @clack/prompts ───────────────────────────────────────────────────── - -const mockSpinnerStart = mock(() => {}); -const mockSpinnerStop = mock(() => {}); - -mock.module("@clack/prompts", () => ({ - spinner: () => ({ - start: mockSpinnerStart, - stop: mockSpinnerStop, - message: mock(() => {}), - }), - log: { - step: mock(() => {}), - info: mock(() => {}), - warn: mock(() => {}), - error: mock(() => {}), - success: mock(() => {}), - }, - intro: mock(() => {}), - outro: mock(() => {}), - cancel: mock(() => {}), - select: mock(() => {}), - autocomplete: mock(async () => "claude"), - text: mock(async () => undefined), - isCancel: () => false, -})); +const { spinnerStart: mockSpinnerStart, spinnerStop: mockSpinnerStop } = mockClackPrompts(); const { cmdMatrix, cmdAgents, cmdClouds, getTerminalWidth } = await import("../commands.js"); diff --git a/packages/cli/src/__tests__/cmdlast.test.ts b/packages/cli/src/__tests__/cmdlast.test.ts index a4484c23..f088d70a 100644 --- a/packages/cli/src/__tests__/cmdlast.test.ts +++ b/packages/cli/src/__tests__/cmdlast.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:te import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { homedir } from "node:os"; -import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers"; +import { createMockManifest, createConsoleMocks, restoreMocks, mockClackPrompts } from "./test-helpers"; import type { SpawnRecord } from "../history"; /** @@ -20,33 +20,12 @@ import type { SpawnRecord } from "../history"; const mockManifest = createMockManifest(); -// Mock @clack/prompts -const mockLogInfo = mock(() => {}); -const mockLogStep = mock(() => {}); -const mockSpinnerStart = mock(() => {}); -const mockSpinnerStop = mock(() => {}); - -mock.module("@clack/prompts", () => ({ - spinner: () => ({ - start: mockSpinnerStart, - stop: mockSpinnerStop, - message: mock(() => {}), - }), - log: { - step: mockLogStep, - info: mockLogInfo, - error: mock(() => {}), - warn: mock(() => {}), - success: mock(() => {}), - }, - intro: mock(() => {}), - outro: mock(() => {}), - cancel: mock(() => {}), - select: mock(() => {}), - autocomplete: mock(async () => "claude"), - text: mock(async () => undefined), - isCancel: () => false, -})); +const { + logInfo: mockLogInfo, + logStep: mockLogStep, + spinnerStart: mockSpinnerStart, + spinnerStop: mockSpinnerStop, +} = mockClackPrompts(); // Import after mock setup const { cmdLast, buildRecordLabel, buildRecordSubtitle } = await import("../commands.js"); diff --git a/packages/cli/src/__tests__/cmdlist-integration.test.ts b/packages/cli/src/__tests__/cmdlist-integration.test.ts index b7fad483..934c072e 100644 --- a/packages/cli/src/__tests__/cmdlist-integration.test.ts +++ b/packages/cli/src/__tests__/cmdlist-integration.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:te import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { homedir } from "node:os"; -import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers"; +import { createMockManifest, createConsoleMocks, restoreMocks, mockClackPrompts } from "./test-helpers"; import type { SpawnRecord } from "../history"; /** @@ -27,35 +27,14 @@ import type { SpawnRecord } from "../history"; const mockManifest = createMockManifest(); -// Mock @clack/prompts -const mockLogError = mock(() => {}); -const mockLogInfo = mock(() => {}); -const mockLogStep = mock(() => {}); -const mockLogSuccess = mock(() => {}); -const mockSpinnerStart = mock(() => {}); -const mockSpinnerStop = mock(() => {}); - -mock.module("@clack/prompts", () => ({ - spinner: () => ({ - start: mockSpinnerStart, - stop: mockSpinnerStop, - message: mock(() => {}), - }), - log: { - step: mockLogStep, - info: mockLogInfo, - error: mockLogError, - warn: mock(() => {}), - success: mockLogSuccess, - }, - intro: mock(() => {}), - outro: mock(() => {}), - cancel: mock(() => {}), - select: mock(() => {}), - autocomplete: mock(async () => "claude"), - text: mock(async () => undefined), - isCancel: () => false, -})); +const { + logError: mockLogError, + logInfo: mockLogInfo, + logStep: mockLogStep, + logSuccess: mockLogSuccess, + spinnerStart: mockSpinnerStart, + spinnerStop: mockSpinnerStop, +} = mockClackPrompts(); // Import after mock setup const { cmdList, resolveDisplayName } = await import("../commands.js"); diff --git a/packages/cli/src/__tests__/cmdrun-duplicate-detection.test.ts b/packages/cli/src/__tests__/cmdrun-duplicate-detection.test.ts index e30e0d37..d2147d0e 100644 --- a/packages/cli/src/__tests__/cmdrun-duplicate-detection.test.ts +++ b/packages/cli/src/__tests__/cmdrun-duplicate-detection.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:te import { mkdirSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { homedir } from "node:os"; -import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers"; +import { createMockManifest, createConsoleMocks, restoreMocks, mockClackPrompts } from "./test-helpers"; import { loadManifest } from "../manifest"; import { isString } from "@openrouter/spawn-shared"; @@ -21,40 +21,20 @@ const mockManifest = createMockManifest(); // ── Clack mock refs ────────────────────────────────────────────────────────── -const mockLogWarn = mock(() => {}); -const mockLogStep = mock(() => {}); -const mockLogError = mock(() => {}); -const mockLogInfo = mock(() => {}); -const mockSpinnerStart = mock(() => {}); -const mockSpinnerStop = mock(() => {}); -const mockSpinnerMessage = mock(() => {}); - // select returns "rerun" to exercise the "Spawn a new VM" path const mockSelect = mock(async () => "rerun"); -mock.module("@clack/prompts", () => ({ - spinner: () => ({ - start: mockSpinnerStart, - stop: mockSpinnerStop, - message: mockSpinnerMessage, - }), - log: { - step: mockLogStep, - info: mockLogInfo, - warn: mockLogWarn, - error: mockLogError, - success: mock(() => {}), - }, - intro: mock(() => {}), - outro: mock(() => {}), - cancel: mock(() => {}), +const { + logWarn: mockLogWarn, + logStep: mockLogStep, + logError: mockLogError, + logInfo: mockLogInfo, + spinnerStart: mockSpinnerStart, + spinnerStop: mockSpinnerStop, + spinnerMessage: mockSpinnerMessage, +} = mockClackPrompts({ select: mockSelect, - autocomplete: mock(async () => "claude"), - // First call: returns SPAWN_NAME value (set by --name). - // Second call (after rerun clears SPAWN_NAME): returns undefined so no duplicate re-check. - text: mock(async () => undefined), - isCancel: () => false, -})); +}); const { cmdRun } = await import("../commands.js"); diff --git a/packages/cli/src/__tests__/cmdrun-happy-path.test.ts b/packages/cli/src/__tests__/cmdrun-happy-path.test.ts index d0f98a20..eb3c7da8 100644 --- a/packages/cli/src/__tests__/cmdrun-happy-path.test.ts +++ b/packages/cli/src/__tests__/cmdrun-happy-path.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:te import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync } from "node:fs"; import { join } from "node:path"; import { homedir } from "node:os"; -import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers"; +import { createMockManifest, createConsoleMocks, restoreMocks, mockClackPrompts } from "./test-helpers"; import { loadManifest } from "../manifest"; import { isString } from "@openrouter/spawn-shared"; @@ -29,36 +29,14 @@ import { isString } from "@openrouter/spawn-shared"; const mockManifest = createMockManifest(); -// ── Mock clack/prompts ─────────────────────────────────────────────────────── - -const mockLogError = mock(() => {}); -const mockLogInfo = mock(() => {}); -const mockLogStep = mock(() => {}); -const mockSpinnerStart = mock(() => {}); -const mockSpinnerStop = mock(() => {}); -const mockSpinnerMessage = mock(() => {}); - -mock.module("@clack/prompts", () => ({ - spinner: () => ({ - start: mockSpinnerStart, - stop: mockSpinnerStop, - message: mockSpinnerMessage, - }), - log: { - step: mockLogStep, - info: mockLogInfo, - warn: mock(() => {}), - error: mockLogError, - success: mock(() => {}), - }, - intro: mock(() => {}), - outro: mock(() => {}), - cancel: mock(() => {}), - select: mock(() => {}), - autocomplete: mock(async () => "claude"), - text: mock(async () => undefined), - isCancel: () => false, -})); +const { + logError: mockLogError, + logInfo: mockLogInfo, + logStep: mockLogStep, + spinnerStart: mockSpinnerStart, + spinnerStop: mockSpinnerStop, + spinnerMessage: mockSpinnerMessage, +} = mockClackPrompts(); const { cmdRun } = await import("../commands.js"); diff --git a/packages/cli/src/__tests__/commands-cloud-info.test.ts b/packages/cli/src/__tests__/commands-cloud-info.test.ts index 02068afe..e4b61a80 100644 --- a/packages/cli/src/__tests__/commands-cloud-info.test.ts +++ b/packages/cli/src/__tests__/commands-cloud-info.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test"; -import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers"; +import { createMockManifest, createConsoleMocks, restoreMocks, mockClackPrompts } from "./test-helpers"; import { loadManifest } from "../manifest"; /** @@ -48,34 +48,14 @@ const manifestWithCloudNotes = { }, }; -// Mock @clack/prompts -const mockLogError = mock(() => {}); -const mockLogInfo = mock(() => {}); -const mockLogStep = mock(() => {}); -const mockLogWarn = mock(() => {}); -const mockSpinnerStart = mock(() => {}); -const mockSpinnerStop = mock(() => {}); - -mock.module("@clack/prompts", () => ({ - spinner: () => ({ - start: mockSpinnerStart, - stop: mockSpinnerStop, - message: mock(() => {}), - }), - log: { - step: mockLogStep, - info: mockLogInfo, - error: mockLogError, - warn: mockLogWarn, - }, - intro: mock(() => {}), - outro: mock(() => {}), - cancel: mock(() => {}), - select: mock(() => {}), - autocomplete: mock(async () => "claude"), - text: mock(async () => undefined), - isCancel: () => false, -})); +const { + logError: mockLogError, + logInfo: mockLogInfo, + logStep: mockLogStep, + logWarn: mockLogWarn, + spinnerStart: mockSpinnerStart, + spinnerStop: mockSpinnerStop, +} = mockClackPrompts(); // Import commands after mock setup const { cmdCloudInfo } = await import("../commands.js"); diff --git a/packages/cli/src/__tests__/commands-display.test.ts b/packages/cli/src/__tests__/commands-display.test.ts index 22a7666c..b3538873 100644 --- a/packages/cli/src/__tests__/commands-display.test.ts +++ b/packages/cli/src/__tests__/commands-display.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test"; -import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers"; +import { createMockManifest, createConsoleMocks, restoreMocks, mockClackPrompts } from "./test-helpers"; import { loadManifest } from "../manifest"; /** @@ -77,35 +77,14 @@ const manyCloudManifest = { }, }; -// Mock @clack/prompts -const mockLogError = mock(() => {}); -const mockLogInfo = mock(() => {}); -const mockLogStep = mock(() => {}); -const mockLogWarn = mock(() => {}); -const mockSpinnerStart = mock(() => {}); -const mockSpinnerStop = mock(() => {}); - -mock.module("@clack/prompts", () => ({ - spinner: () => ({ - start: mockSpinnerStart, - stop: mockSpinnerStop, - message: mock(() => {}), - }), - log: { - step: mockLogStep, - info: mockLogInfo, - error: mockLogError, - warn: mockLogWarn, - success: mock(() => {}), - }, - intro: mock(() => {}), - outro: mock(() => {}), - cancel: mock(() => {}), - select: mock(() => {}), - autocomplete: mock(async () => "claude"), - text: mock(async () => undefined), - isCancel: () => false, -})); +const { + logError: mockLogError, + logInfo: mockLogInfo, + logStep: mockLogStep, + logWarn: mockLogWarn, + spinnerStart: mockSpinnerStart, + spinnerStop: mockSpinnerStop, +} = mockClackPrompts(); // Import commands after mock setup const { cmdAgentInfo, cmdHelp } = await import("../commands.js"); diff --git a/packages/cli/src/__tests__/commands-error-paths.test.ts b/packages/cli/src/__tests__/commands-error-paths.test.ts index ab250ee8..567bd4f1 100644 --- a/packages/cli/src/__tests__/commands-error-paths.test.ts +++ b/packages/cli/src/__tests__/commands-error-paths.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test"; -import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers"; +import { createMockManifest, createConsoleMocks, restoreMocks, mockClackPrompts } from "./test-helpers"; import { loadManifest } from "../manifest"; import { isString } from "@openrouter/spawn-shared"; @@ -18,34 +18,14 @@ import { isString } from "@openrouter/spawn-shared"; const mockManifest = createMockManifest(); -// Mock @clack/prompts to prevent TTY output and capture error/info messages -const mockLogError = mock(() => {}); -const mockLogInfo = mock(() => {}); -const mockLogStep = mock(() => {}); -const mockLogWarn = mock(() => {}); -const mockSpinnerStart = mock(() => {}); -const mockSpinnerStop = mock(() => {}); - -mock.module("@clack/prompts", () => ({ - spinner: () => ({ - start: mockSpinnerStart, - stop: mockSpinnerStop, - message: mock(() => {}), - }), - log: { - step: mockLogStep, - info: mockLogInfo, - error: mockLogError, - warn: mockLogWarn, - }, - intro: mock(() => {}), - outro: mock(() => {}), - cancel: mock(() => {}), - select: mock(() => {}), - autocomplete: mock(async () => "claude"), - text: mock(async () => undefined), - isCancel: () => false, -})); +const { + logError: mockLogError, + logInfo: mockLogInfo, + logStep: mockLogStep, + logWarn: mockLogWarn, + spinnerStart: mockSpinnerStart, + spinnerStop: mockSpinnerStop, +} = mockClackPrompts(); // Import commands after @clack/prompts mock is set up const { cmdRun, cmdAgentInfo } = await import("../commands.js"); diff --git a/packages/cli/src/__tests__/commands-name-suggestions.test.ts b/packages/cli/src/__tests__/commands-name-suggestions.test.ts index a52ce491..eda68a11 100644 --- a/packages/cli/src/__tests__/commands-name-suggestions.test.ts +++ b/packages/cli/src/__tests__/commands-name-suggestions.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test"; -import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers"; +import { createMockManifest, createConsoleMocks, restoreMocks, mockClackPrompts } from "./test-helpers"; import { loadManifest } from "../manifest"; /** @@ -101,35 +101,14 @@ const manifestWithDistinctNames = { }, }; -// Mock @clack/prompts -const mockLogError = mock(() => {}); -const mockLogInfo = mock(() => {}); -const mockLogStep = mock(() => {}); -const mockLogWarn = mock(() => {}); -const mockSpinnerStart = mock(() => {}); -const mockSpinnerStop = mock(() => {}); - -mock.module("@clack/prompts", () => ({ - spinner: () => ({ - start: mockSpinnerStart, - stop: mockSpinnerStop, - message: mock(() => {}), - }), - log: { - step: mockLogStep, - info: mockLogInfo, - error: mockLogError, - warn: mockLogWarn, - success: mock(() => {}), - }, - intro: mock(() => {}), - outro: mock(() => {}), - cancel: mock(() => {}), - select: mock(() => {}), - autocomplete: mock(async () => "claude"), - text: mock(async () => undefined), - isCancel: () => false, -})); +const { + logError: mockLogError, + logInfo: mockLogInfo, + logStep: mockLogStep, + logWarn: mockLogWarn, + spinnerStart: mockSpinnerStart, + spinnerStop: mockSpinnerStop, +} = mockClackPrompts(); // Import commands after mock setup const { cmdRun, cmdAgentInfo, cmdCloudInfo, findClosestMatch } = await import("../commands.js"); diff --git a/packages/cli/src/__tests__/commands-resolve-run.test.ts b/packages/cli/src/__tests__/commands-resolve-run.test.ts index 9269d6d5..a04c2433 100644 --- a/packages/cli/src/__tests__/commands-resolve-run.test.ts +++ b/packages/cli/src/__tests__/commands-resolve-run.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test"; -import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers"; +import { createMockManifest, createConsoleMocks, restoreMocks, mockClackPrompts } from "./test-helpers"; import { loadManifest } from "../manifest"; import { isString } from "@openrouter/spawn-shared"; @@ -124,35 +124,14 @@ const noCloudManifest = { }, }; -// Mock @clack/prompts -const mockLogError = mock(() => {}); -const mockLogInfo = mock(() => {}); -const mockLogStep = mock(() => {}); -const mockLogWarn = mock(() => {}); -const mockSpinnerStart = mock(() => {}); -const mockSpinnerStop = mock(() => {}); - -mock.module("@clack/prompts", () => ({ - spinner: () => ({ - start: mockSpinnerStart, - stop: mockSpinnerStop, - message: mock(() => {}), - }), - log: { - step: mockLogStep, - info: mockLogInfo, - error: mockLogError, - warn: mockLogWarn, - success: mock(() => {}), - }, - intro: mock(() => {}), - outro: mock(() => {}), - cancel: mock(() => {}), - select: mock(() => {}), - autocomplete: mock(async () => "claude"), - text: mock(async () => undefined), - isCancel: () => false, -})); +const { + logError: mockLogError, + logInfo: mockLogInfo, + logStep: mockLogStep, + logWarn: mockLogWarn, + spinnerStart: mockSpinnerStart, + spinnerStop: mockSpinnerStop, +} = mockClackPrompts(); // Import commands after mock setup const { cmdRun } = await import("../commands.js"); diff --git a/packages/cli/src/__tests__/commands-swap-resolve.test.ts b/packages/cli/src/__tests__/commands-swap-resolve.test.ts index 9a977bd3..b80d081d 100644 --- a/packages/cli/src/__tests__/commands-swap-resolve.test.ts +++ b/packages/cli/src/__tests__/commands-swap-resolve.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test"; -import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers"; +import { createMockManifest, createConsoleMocks, restoreMocks, mockClackPrompts } from "./test-helpers"; import { loadManifest } from "../manifest"; import { isString } from "@openrouter/spawn-shared"; @@ -21,35 +21,14 @@ import { isString } from "@openrouter/spawn-shared"; * - Edge case: resolution to a key that then fails validation */ -// Mock @clack/prompts -const mockLogError = mock(() => {}); -const mockLogInfo = mock(() => {}); -const mockLogStep = mock(() => {}); -const mockLogWarn = mock(() => {}); -const mockSpinnerStart = mock(() => {}); -const mockSpinnerStop = mock(() => {}); - -mock.module("@clack/prompts", () => ({ - spinner: () => ({ - start: mockSpinnerStart, - stop: mockSpinnerStop, - message: mock(() => {}), - }), - log: { - step: mockLogStep, - info: mockLogInfo, - error: mockLogError, - warn: mockLogWarn, - success: mock(() => {}), - }, - intro: mock(() => {}), - outro: mock(() => {}), - cancel: mock(() => {}), - select: mock(() => {}), - autocomplete: mock(async () => "claude"), - text: mock(async () => undefined), - isCancel: () => false, -})); +const { + logError: mockLogError, + logInfo: mockLogInfo, + logStep: mockLogStep, + logWarn: mockLogWarn, + spinnerStart: mockSpinnerStart, + spinnerStop: mockSpinnerStop, +} = mockClackPrompts(); // Import commands after mock setup const { cmdRun } = await import("../commands.js"); diff --git a/packages/cli/src/__tests__/commands-update-download.test.ts b/packages/cli/src/__tests__/commands-update-download.test.ts index 9533142f..e8851fa8 100644 --- a/packages/cli/src/__tests__/commands-update-download.test.ts +++ b/packages/cli/src/__tests__/commands-update-download.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test"; -import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers"; +import { createMockManifest, createConsoleMocks, restoreMocks, mockClackPrompts } from "./test-helpers"; import { loadManifest } from "../manifest"; import { isString } from "@openrouter/spawn-shared"; import pkg from "../../package.json" with { type: "json" }; @@ -21,34 +21,14 @@ const VERSION = pkg.version; const mockManifest = createMockManifest(); -// Mock @clack/prompts -const mockLogError = mock(() => {}); -const mockLogInfo = mock(() => {}); -const mockLogStep = mock(() => {}); -const mockSpinnerStart = mock(() => {}); -const mockSpinnerStop = mock(() => {}); -const mockSpinnerMessage = mock(() => {}); - -mock.module("@clack/prompts", () => ({ - spinner: () => ({ - start: mockSpinnerStart, - stop: mockSpinnerStop, - message: mockSpinnerMessage, - }), - log: { - step: mockLogStep, - info: mockLogInfo, - warn: mock(() => {}), - error: mockLogError, - }, - intro: mock(() => {}), - outro: mock(() => {}), - cancel: mock(() => {}), - select: mock(() => {}), - autocomplete: mock(async () => "claude"), - text: mock(async () => undefined), - isCancel: () => false, -})); +const { + logError: mockLogError, + logInfo: mockLogInfo, + logStep: mockLogStep, + spinnerStart: mockSpinnerStart, + spinnerStop: mockSpinnerStop, + spinnerMessage: mockSpinnerMessage, +} = mockClackPrompts(); // Mock node:child_process to prevent real subprocess calls in tests: // - execSync: used by performUpdate() to run curl|bash install — without this mock, diff --git a/packages/cli/src/__tests__/download-and-failure.test.ts b/packages/cli/src/__tests__/download-and-failure.test.ts index d376b42f..cd2d6957 100644 --- a/packages/cli/src/__tests__/download-and-failure.test.ts +++ b/packages/cli/src/__tests__/download-and-failure.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test"; -import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers"; +import { createMockManifest, createConsoleMocks, restoreMocks, mockClackPrompts } from "./test-helpers"; import { loadManifest } from "../manifest"; import { isString } from "@openrouter/spawn-shared"; @@ -22,36 +22,15 @@ import { isString } from "@openrouter/spawn-shared"; const mockManifest = createMockManifest(); -// Mock @clack/prompts -const mockLogError = mock(() => {}); -const mockLogInfo = mock(() => {}); -const mockLogStep = mock(() => {}); -const mockLogWarn = mock(() => {}); -const mockSpinnerStart = mock(() => {}); -const mockSpinnerStop = mock(() => {}); -const mockSpinnerMessage = mock(() => {}); - -mock.module("@clack/prompts", () => ({ - spinner: () => ({ - start: mockSpinnerStart, - stop: mockSpinnerStop, - message: mockSpinnerMessage, - }), - log: { - step: mockLogStep, - info: mockLogInfo, - error: mockLogError, - warn: mockLogWarn, - success: mock(() => {}), - }, - intro: mock(() => {}), - outro: mock(() => {}), - cancel: mock(() => {}), - select: mock(() => {}), - autocomplete: mock(async () => "claude"), - text: mock(async () => undefined), - isCancel: () => false, -})); +const { + logError: mockLogError, + logInfo: mockLogInfo, + logStep: mockLogStep, + logWarn: mockLogWarn, + spinnerStart: mockSpinnerStart, + spinnerStop: mockSpinnerStop, + spinnerMessage: mockSpinnerMessage, +} = mockClackPrompts(); // Import after mock setup const { cmdRun } = await import("../commands.js"); diff --git a/packages/cli/src/__tests__/preflight-credentials.test.ts b/packages/cli/src/__tests__/preflight-credentials.test.ts index 64545057..700d4bcd 100644 --- a/packages/cli/src/__tests__/preflight-credentials.test.ts +++ b/packages/cli/src/__tests__/preflight-credentials.test.ts @@ -1,30 +1,17 @@ import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test"; +import { mockClackPrompts } from "./test-helpers"; import { preflightCredentialCheck } from "../commands"; import type { Manifest } from "../manifest"; -// Mock @clack/prompts -const mockLog = { - warn: mock(() => {}), - info: mock(() => {}), -}; -const mockConfirm = mock(() => Promise.resolve(true)); const mockIsCancel = mock(() => false); -mock.module("@clack/prompts", () => ({ - log: mockLog, - confirm: mockConfirm, +const clackMocks = mockClackPrompts({ isCancel: mockIsCancel, - // Stubs for other imports commands.ts might use - intro: mock(() => {}), - outro: mock(() => {}), - select: mock(() => Promise.resolve("")), - autocomplete: mock(async () => "claude"), - text: mock(async () => undefined), - spinner: mock(() => ({ - start: mock(() => {}), - stop: mock(() => {}), - message: mock(() => {}), - })), -})); +}); +const mockLog = { + warn: clackMocks.logWarn, + info: clackMocks.logInfo, +}; +const mockConfirm = clackMocks.confirm; function makeManifest(cloudAuth: string): Manifest { const m: Manifest = { diff --git a/packages/cli/src/__tests__/run-path-credential-display.test.ts b/packages/cli/src/__tests__/run-path-credential-display.test.ts index 86e593c1..a42806bd 100644 --- a/packages/cli/src/__tests__/run-path-credential-display.test.ts +++ b/packages/cli/src/__tests__/run-path-credential-display.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test"; +import { mockClackPrompts } from "./test-helpers"; import type { Manifest } from "../manifest"; /** @@ -123,30 +124,9 @@ const mockExit = spyOn(process, "exit").mockImplementation(() => { throw new Error("process.exit called"); }); -const mockLog = { - step: mock(() => {}), - info: mock(() => {}), - error: mock(() => {}), - warn: mock(() => {}), - success: mock(() => {}), -}; - -mock.module("@clack/prompts", () => ({ - spinner: () => ({ - start: mock(() => {}), - stop: mock(() => {}), - message: mock(() => {}), - }), - log: mockLog, - intro: mock(() => {}), - outro: mock(() => {}), - cancel: mock(() => {}), +mockClackPrompts({ select: mock(() => Promise.resolve("hetzner")), - autocomplete: mock(async () => "claude"), - text: mock(async () => undefined), - confirm: mock(() => Promise.resolve(true)), - isCancel: () => false, -})); +}); // Import after mocks are set up const { diff --git a/packages/cli/src/__tests__/ssh-keys.test.ts b/packages/cli/src/__tests__/ssh-keys.test.ts index 8560e3e6..e770b659 100644 --- a/packages/cli/src/__tests__/ssh-keys.test.ts +++ b/packages/cli/src/__tests__/ssh-keys.test.ts @@ -8,26 +8,12 @@ import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test"; import { mkdirSync, writeFileSync, rmSync, existsSync } from "node:fs"; import { join } from "node:path"; +import { mockClackPrompts } from "./test-helpers"; -// ── Mock @clack/prompts ───────────────────────────────────────────────────── - -mock.module("@clack/prompts", () => ({ - multiselect: mock(() => Promise.resolve([])), - isCancel: () => false, - log: { - info: mock(() => {}), - warn: mock(() => {}), - error: mock(() => {}), - step: mock(() => {}), - message: mock(() => {}), - }, - spinner: () => ({ - start: mock(() => {}), - stop: mock(() => {}), - }), +mockClackPrompts({ select: mock(() => Promise.resolve("")), text: mock(() => Promise.resolve("")), -})); +}); // ── Import after @clack/prompts mock ──────────────────────────────────────── diff --git a/packages/cli/src/__tests__/test-helpers.ts b/packages/cli/src/__tests__/test-helpers.ts index 6f1ffc7e..5b4fc84e 100644 --- a/packages/cli/src/__tests__/test-helpers.ts +++ b/packages/cli/src/__tests__/test-helpers.ts @@ -87,6 +87,88 @@ export function restoreMocks( }); } +// ── @clack/prompts Mock ────────────────────────────────────────────────────── + +export interface ClackPromptsMock { + logStep: ReturnType; + logInfo: ReturnType; + logError: ReturnType; + logWarn: ReturnType; + logSuccess: ReturnType; + logMessage: ReturnType; + spinnerStart: ReturnType; + spinnerStop: ReturnType; + spinnerMessage: ReturnType; + intro: ReturnType; + outro: ReturnType; + cancel: ReturnType; + select: ReturnType; + autocomplete: ReturnType; + text: ReturnType; + confirm: ReturnType; + multiselect: ReturnType; + isCancel: (...args: unknown[]) => boolean; +} + +/** + * Creates a centralized @clack/prompts mock and registers it via mock.module(). + * + * Returns an object of individual mock refs that tests can use for assertions. + * Pass `overrides` to customize specific functions (e.g., custom `select` behavior). + * + * MUST be called at module top level (before dynamic imports of modules that use @clack/prompts). + */ +export function mockClackPrompts(overrides?: Partial): ClackPromptsMock { + const mocks: ClackPromptsMock = { + logStep: mock(() => {}), + logInfo: mock(() => {}), + logError: mock(() => {}), + logWarn: mock(() => {}), + logSuccess: mock(() => {}), + logMessage: mock(() => {}), + spinnerStart: mock(() => {}), + spinnerStop: mock(() => {}), + spinnerMessage: mock(() => {}), + intro: mock(() => {}), + outro: mock(() => {}), + cancel: mock(() => {}), + select: mock(() => {}), + autocomplete: mock(async () => "claude"), + text: mock(async () => undefined), + confirm: mock(async () => true), + multiselect: mock(() => Promise.resolve([])), + isCancel: () => false, + ...overrides, + }; + + mock.module("@clack/prompts", () => ({ + spinner: () => ({ + start: mocks.spinnerStart, + stop: mocks.spinnerStop, + message: mocks.spinnerMessage, + }), + log: { + step: mocks.logStep, + info: mocks.logInfo, + error: mocks.logError, + warn: mocks.logWarn, + success: mocks.logSuccess, + message: mocks.logMessage, + }, + intro: mocks.intro, + outro: mocks.outro, + cancel: mocks.cancel, + select: mocks.select, + autocomplete: mocks.autocomplete, + text: mocks.text, + confirm: mocks.confirm, + multiselect: mocks.multiselect, + isCancel: mocks.isCancel, + })); + + return mocks; +} + // ── Fetch Mocks ──────────────────────────────────────────────────────────────── export function mockSuccessfulFetch(data: any) {