/// import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test" import * as p from "@clack/prompts" import * as prompts from "./tui-install-prompts" import type { DetectedConfig, InstallConfig, InstallPlatform } from "./types" function createDetectedConfig(): DetectedConfig { return { isInstalled: false, installedVersion: null, hasClaude: false, isMax20: false, hasOpenAI: false, hasGemini: false, hasCopilot: false, hasCodex: false, hasOpencodeZen: false, hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, hasVercelAiGateway: false, } } function withTty(): () => void { const originalIsStdinTty = process.stdin.isTTY const originalIsStdoutTty = process.stdout.isTTY Object.defineProperty(process.stdin, "isTTY", { configurable: true, value: true }) Object.defineProperty(process.stdout, "isTTY", { configurable: true, value: true }) return () => { Object.defineProperty(process.stdin, "isTTY", { configurable: true, value: originalIsStdinTty }) Object.defineProperty(process.stdout, "isTTY", { configurable: true, value: originalIsStdoutTty }) } } describe("promptInstallPlatform", () => { let restoreTty: () => void beforeEach(() => { restoreTty = withTty() }) afterEach(() => { restoreTty() mock.restore() }) test("offers OpenCode, Codex, and Both choices", async () => { // given const selectSpy = spyOn(p, "select").mockResolvedValue("opencode") // when const value = await prompts.promptInstallPlatform("opencode") // then expect(value).toBe("opencode") expect(selectSpy).toHaveBeenCalledTimes(1) expect(selectSpy.mock.calls[0]?.[0]).toMatchObject({ initialValue: "opencode", options: [ { value: "opencode" }, { value: "codex" }, { value: "both" }, ], }) }) test("preserves Codex as the initial platform", async () => { // given const selectSpy = spyOn(p, "select").mockResolvedValue("codex") // when const value = await prompts.promptInstallPlatform("codex") // then expect(value).toBe("codex") expect(selectSpy).toHaveBeenCalledTimes(1) expect(selectSpy.mock.calls[0]?.[0]).toMatchObject({ initialValue: "codex", options: [ { value: "opencode" }, { value: "codex" }, { value: "both" }, ], }) }) }) describe("promptInstallConfig platform branching", () => { let restoreTty: () => void beforeEach(() => { restoreTty = withTty() }) afterEach(() => { restoreTty() mock.restore() }) test("skips OpenCode questions when the user selects codex", async () => { // given const selectSpy = spyOn(p, "select").mockResolvedValue(true) // when const config = await prompts.promptInstallConfig(createDetectedConfig(), "codex") // then expect(config).toMatchObject({ platform: "codex", hasOpenCode: false, hasCodex: true, codexAutonomous: true, } satisfies Partial) expect(selectSpy).toHaveBeenCalledTimes(1) expect(selectSpy.mock.calls[0]?.[0]).toMatchObject({ initialValue: true, options: [{ value: true }, { value: false }], }) }) test.each([ ["opencode", false], ["both", true], ] satisfies readonly [InstallPlatform, boolean][])( "asks OpenCode questions when the user selects %s", async (platform, hasCodex) => { // given const selectSpy = spyOn(p, "select").mockResolvedValue("no") // when const config = await prompts.promptInstallConfig(createDetectedConfig(), platform) // then expect(config).toMatchObject({ platform, hasOpenCode: true, hasCodex } satisfies Partial) expect(selectSpy).toHaveBeenCalledTimes(hasCodex ? 10 : 9) }, ) test("uses explicit Codex autonomous override without asking", async () => { // given const selectSpy = spyOn(p, "select").mockResolvedValue("no") // when const config = await prompts.promptInstallConfig(createDetectedConfig(), "codex", false) // then expect(config).toMatchObject({ platform: "codex", hasCodex: true, codexAutonomous: false, } satisfies Partial) expect(selectSpy).not.toHaveBeenCalled() }) test("does not ask the old Codex adapter question", async () => { // given const selectSpy = spyOn(p, "select").mockResolvedValue("no") // when await prompts.promptInstallConfig(createDetectedConfig(), "both") // then const messages = selectSpy.mock.calls.map((call) => call[0].message) expect(messages).not.toContain("Install Codex harness adapter into ~/.codex?") }) })