Files
oh-my-opencode/src/cli/tui-installer.test.ts
T

232 lines
8.3 KiB
TypeScript
Raw Normal View History

import { afterEach, beforeEach, describe, expect, it, spyOn } from "bun:test"
import * as p from "@clack/prompts"
import * as configManager from "./config-manager"
import * as tuiInstallPrompts from "./tui-install-prompts"
import { runTuiInstaller } from "./tui-installer"
function createMockSpinner(): ReturnType<typeof p.spinner> {
return {
start: () => undefined,
stop: () => undefined,
message: () => undefined,
2026-05-30 19:12:14 +09:00
cancel: () => undefined,
error: () => undefined,
clear: () => undefined,
isCancelled: false,
}
}
describe("runTuiInstaller", () => {
const originalIsStdinTty = process.stdin.isTTY
const originalIsStdoutTty = process.stdout.isTTY
2026-05-30 19:12:14 +09:00
const originalPublishLazycodex = process.env.OMO_PUBLISH_LAZYCODEX
beforeEach(() => {
Object.defineProperty(process.stdin, "isTTY", { configurable: true, value: true })
Object.defineProperty(process.stdout, "isTTY", { configurable: true, value: true })
})
afterEach(() => {
Object.defineProperty(process.stdin, "isTTY", { configurable: true, value: originalIsStdinTty })
Object.defineProperty(process.stdout, "isTTY", { configurable: true, value: originalIsStdoutTty })
2026-05-30 19:12:14 +09:00
if (originalPublishLazycodex === undefined) {
delete process.env.OMO_PUBLISH_LAZYCODEX
} else {
process.env.OMO_PUBLISH_LAZYCODEX = originalPublishLazycodex
}
})
it("blocks installation when OpenCode is below the minimum version", async () => {
// given
const restoreSpies = [
spyOn(p, "spinner").mockReturnValue(createMockSpinner()),
spyOn(p, "intro").mockImplementation(() => undefined),
spyOn(p.log, "warn").mockImplementation(() => undefined),
2026-05-30 19:12:14 +09:00
spyOn(tuiInstallPrompts, "promptInstallPlatform").mockResolvedValue("opencode"),
spyOn(configManager, "detectCurrentConfig").mockReturnValue({
isInstalled: false,
installedVersion: null,
hasClaude: false,
isMax20: false,
hasOpenAI: false,
hasGemini: false,
hasCopilot: false,
2026-05-30 19:12:14 +09:00
hasCodex: false,
hasOpencodeZen: false,
hasZaiCodingPlan: false,
hasKimiForCoding: false,
hasOpencodeGo: false,
hasVercelAiGateway: false,
2026-05-30 19:12:14 +09:00
codexAutonomous: false,
}),
spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true),
spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.3.9"),
]
const promptSpy = spyOn(tuiInstallPrompts, "promptInstallConfig")
const addPluginSpy = spyOn(configManager, "addPluginToOpenCodeConfig")
const outroSpy = spyOn(p, "outro").mockImplementation(() => undefined)
// when
const result = await runTuiInstaller({ tui: true }, "3.16.0")
// then
expect(result).toBe(1)
expect(promptSpy).not.toHaveBeenCalled()
expect(addPluginSpy).not.toHaveBeenCalled()
expect(outroSpy).toHaveBeenCalled()
for (const spy of restoreSpies) {
spy.mockRestore()
}
promptSpy.mockRestore()
addPluginSpy.mockRestore()
outroSpy.mockRestore()
})
2026-05-30 19:12:14 +09:00
it("blocks codex platform when lazycodex publishing is disabled", async () => {
// given
delete process.env.OMO_PUBLISH_LAZYCODEX
const platformSpy = spyOn(tuiInstallPrompts, "promptInstallPlatform").mockResolvedValue("codex")
const promptConfigSpy = spyOn(tuiInstallPrompts, "promptInstallConfig")
const logErrorSpy = spyOn(p.log, "error").mockImplementation(() => undefined)
const outroSpy = spyOn(p, "outro").mockImplementation(() => undefined)
// when
const result = await runTuiInstaller({ tui: true, platform: "codex" }, "3.16.0")
// then
expect(result).toBe(1)
expect(platformSpy).toHaveBeenCalled()
expect(promptConfigSpy).not.toHaveBeenCalled()
expect(logErrorSpy).toHaveBeenCalled()
platformSpy.mockRestore()
promptConfigSpy.mockRestore()
logErrorSpy.mockRestore()
outroSpy.mockRestore()
})
it("proceeds when OpenCode meets the minimum version", async () => {
// given
const restoreSpies = [
spyOn(p, "spinner").mockReturnValue(createMockSpinner()),
spyOn(p, "intro").mockImplementation(() => undefined),
spyOn(p.log, "info").mockImplementation(() => undefined),
spyOn(p.log, "warn").mockImplementation(() => undefined),
spyOn(p.log, "success").mockImplementation(() => undefined),
spyOn(p.log, "message").mockImplementation(() => undefined),
spyOn(p, "note").mockImplementation(() => undefined),
spyOn(p, "outro").mockImplementation(() => undefined),
2026-05-30 19:12:14 +09:00
spyOn(tuiInstallPrompts, "promptInstallPlatform").mockResolvedValue("opencode"),
spyOn(configManager, "detectCurrentConfig").mockReturnValue({
isInstalled: false,
installedVersion: null,
hasClaude: false,
isMax20: false,
hasOpenAI: false,
hasGemini: false,
hasCopilot: false,
2026-05-30 19:12:14 +09:00
hasCodex: false,
hasOpencodeZen: false,
hasZaiCodingPlan: false,
hasKimiForCoding: false,
hasOpencodeGo: false,
hasVercelAiGateway: false,
2026-05-30 19:12:14 +09:00
codexAutonomous: false,
}),
spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true),
spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.4.0"),
spyOn(tuiInstallPrompts, "promptInstallConfig").mockResolvedValue({
2026-05-30 19:12:14 +09:00
platform: "opencode",
hasOpenCode: true,
hasClaude: false,
isMax20: false,
hasOpenAI: false,
hasGemini: false,
hasCopilot: false,
2026-05-30 19:12:14 +09:00
hasCodex: false,
hasOpencodeZen: false,
hasZaiCodingPlan: false,
hasKimiForCoding: false,
hasOpencodeGo: false,
hasVercelAiGateway: false,
2026-05-30 19:12:14 +09:00
codexAutonomous: false,
}),
spyOn(configManager, "addPluginToOpenCodeConfig").mockResolvedValue({
success: true,
configPath: "/tmp/opencode.jsonc",
}),
spyOn(configManager, "writeOmoConfig").mockReturnValue({
success: true,
configPath: "/tmp/oh-my-opencode.jsonc",
}),
]
// when
const result = await runTuiInstaller({ tui: true }, "3.16.0")
// then
expect(result).toBe(0)
for (const spy of restoreSpies) {
spy.mockRestore()
}
})
2026-05-30 19:12:14 +09:00
it("skips OpenCode checks and writes when platform is codex", async () => {
// given
process.env.OMO_PUBLISH_LAZYCODEX = "true"
const restoreSpies = [
spyOn(p, "spinner").mockReturnValue(createMockSpinner()),
spyOn(p, "intro").mockImplementation(() => undefined),
spyOn(p.log, "info").mockImplementation(() => undefined),
spyOn(p.log, "warn").mockImplementation(() => undefined),
spyOn(p.log, "success").mockImplementation(() => undefined),
spyOn(p.log, "message").mockImplementation(() => undefined),
spyOn(p, "note").mockImplementation(() => undefined),
spyOn(p, "outro").mockImplementation(() => undefined),
spyOn(tuiInstallPrompts, "promptInstallPlatform").mockResolvedValue("codex"),
spyOn(tuiInstallPrompts, "promptInstallConfig").mockResolvedValue({
platform: "codex",
hasOpenCode: false,
hasClaude: false,
isMax20: false,
hasOpenAI: false,
hasGemini: false,
hasCopilot: false,
hasCodex: false,
hasOpencodeZen: false,
hasZaiCodingPlan: false,
hasKimiForCoding: false,
hasOpencodeGo: false,
hasVercelAiGateway: false,
}),
]
const detectConfigSpy = spyOn(configManager, "detectCurrentConfig")
const isInstalledSpy = spyOn(configManager, "isOpenCodeInstalled")
const getVersionSpy = spyOn(configManager, "getOpenCodeVersion")
const addPluginSpy = spyOn(configManager, "addPluginToOpenCodeConfig")
const writeConfigSpy = spyOn(configManager, "writeOmoConfig")
// when
const result = await runTuiInstaller({ tui: true, platform: "codex" }, "3.16.0")
// then
expect(result).toBe(0)
expect(detectConfigSpy).not.toHaveBeenCalled()
expect(isInstalledSpy).not.toHaveBeenCalled()
expect(getVersionSpy).not.toHaveBeenCalled()
expect(addPluginSpy).not.toHaveBeenCalled()
expect(writeConfigSpy).not.toHaveBeenCalled()
for (const spy of restoreSpies) {
spy.mockRestore()
}
detectConfigSpy.mockRestore()
isInstalledSpy.mockRestore()
getVersionSpy.mockRestore()
addPluginSpy.mockRestore()
writeConfigSpy.mockRestore()
})
})