import { describe, it, expect, vi, beforeEach } from "vitest"; vi.mock("node:fs", () => ({ existsSync: vi.fn(), })); vi.mock("node:os", () => ({ homedir: () => "/home/test", platform: () => "linux", })); import { detectInstallTarget, detectAllTargets, resolveInstallPath, targetToPath, parseTargets, PLATFORM_REGISTRY, ALL_TARGETS, } from "./install-path.js"; import { existsSync } from "node:fs"; const mockExistsSync = vi.mocked(existsSync); beforeEach(() => { vi.clearAllMocks(); }); describe("detectInstallTarget", () => { it("returns claude-code when .claude exists", () => { mockExistsSync.mockImplementation((p) => String(p).endsWith("/.claude")); const result = detectInstallTarget(); expect(result.type).toBe("claude-code"); expect(result.path).toContain(".claude/skills"); }); it("returns cursor when only .cursor exists", () => { mockExistsSync.mockImplementation((p) => String(p).endsWith("/.cursor")); const result = detectInstallTarget(); expect(result.type).toBe("cursor"); expect(result.path).toContain(".cursor/skills"); }); it("returns codex when only .codex exists", () => { mockExistsSync.mockImplementation((p) => String(p).endsWith("/.codex")); const result = detectInstallTarget(); expect(result.type).toBe("codex"); }); it("returns gemini when only .gemini exists", () => { mockExistsSync.mockImplementation((p) => String(p).endsWith("/.gemini")); const result = detectInstallTarget(); expect(result.type).toBe("gemini"); expect(result.path).toContain(".gemini/skills"); }); it("returns roo when only .roo exists", () => { mockExistsSync.mockImplementation((p) => String(p).endsWith("/.roo")); const result = detectInstallTarget(); expect(result.type).toBe("roo"); }); it("returns continue when only .continue exists", () => { mockExistsSync.mockImplementation((p) => String(p).endsWith("/.continue")); const result = detectInstallTarget(); expect(result.type).toBe("continue"); }); it("defaults to claude-code when none exist", () => { mockExistsSync.mockReturnValue(false); const result = detectInstallTarget(); expect(result.type).toBe("claude-code"); }); }); describe("detectAllTargets", () => { it("returns all tools with existing config dirs", () => { mockExistsSync.mockImplementation((p) => { const path = String(p); return path.endsWith("/.claude") || path.endsWith("/.cursor"); }); const targets = detectAllTargets(); expect(targets.map((t) => t.type).sort()).toEqual( ["claude-code", "cursor"].sort(), ); }); it("returns empty array when no tools detected", () => { mockExistsSync.mockReturnValue(false); const targets = detectAllTargets(); expect(targets).toHaveLength(0); }); it("detects all registry tools when all dirs exist", () => { mockExistsSync.mockReturnValue(true); const targets = detectAllTargets(); expect(targets.length).toBe(PLATFORM_REGISTRY.length); }); }); describe("resolveInstallPath", () => { it("returns explicit target when provided", () => { mockExistsSync.mockReturnValue(false); const result = resolveInstallPath("windsurf"); expect(result.type).toBe("windsurf"); expect(result.path).toContain(".windsurf/skills"); }); it("falls back to auto-detect when no target provided", () => { mockExistsSync.mockImplementation((p) => String(p).endsWith("/.cursor")); const result = resolveInstallPath(undefined); expect(result.type).toBe("cursor"); }); it("falls back to auto-detect for unknown target", () => { mockExistsSync.mockReturnValue(false); const result = resolveInstallPath("nonexistent"); expect(result.type).toBe("claude-code"); }); it("resolves new platforms (gemini, roo, continue)", () => { mockExistsSync.mockReturnValue(false); expect(resolveInstallPath("gemini").type).toBe("gemini"); expect(resolveInstallPath("roo").type).toBe("roo"); expect(resolveInstallPath("continue").type).toBe("continue"); expect(resolveInstallPath("aide").type).toBe("aide"); expect(resolveInstallPath("augment").type).toBe("augment"); expect(resolveInstallPath("zed").type).toBe("zed"); }); }); describe("targetToPath", () => { it("returns correct path for each target", () => { expect(targetToPath("claude-code")).toContain(".claude/skills"); expect(targetToPath("cursor")).toContain(".cursor/skills"); expect(targetToPath("windsurf")).toContain(".windsurf/skills"); expect(targetToPath("cline")).toContain(".cline/skills"); expect(targetToPath("codex")).toContain(".codex/skills"); expect(targetToPath("copilot")).toContain(".github/copilot/skills"); expect(targetToPath("opencode")).toContain(".opencode/skills"); expect(targetToPath("gemini")).toContain(".gemini/skills"); expect(targetToPath("roo")).toContain(".roo/skills"); expect(targetToPath("aide")).toContain(".aide/skills"); expect(targetToPath("augment")).toContain(".augment/skills"); expect(targetToPath("zed")).toContain("zed/skills"); expect(targetToPath("continue")).toContain(".continue/skills"); }); }); describe("ALL_TARGETS", () => { it("includes all 13 supported targets", () => { expect(ALL_TARGETS).toContain("claude-code"); expect(ALL_TARGETS).toContain("cursor"); expect(ALL_TARGETS).toContain("windsurf"); expect(ALL_TARGETS).toContain("cline"); expect(ALL_TARGETS).toContain("codex"); expect(ALL_TARGETS).toContain("copilot"); expect(ALL_TARGETS).toContain("opencode"); expect(ALL_TARGETS).toContain("gemini"); expect(ALL_TARGETS).toContain("roo"); expect(ALL_TARGETS).toContain("aide"); expect(ALL_TARGETS).toContain("augment"); expect(ALL_TARGETS).toContain("zed"); expect(ALL_TARGETS).toContain("continue"); expect(ALL_TARGETS).toContain("kiro"); expect(ALL_TARGETS).toContain("junie"); expect(ALL_TARGETS).toHaveLength(15); }); }); describe("PLATFORM_REGISTRY", () => { it("every entry has id, label, paths, format, detect", () => { for (const entry of PLATFORM_REGISTRY) { expect(entry.id).toBeTruthy(); expect(entry.label).toBeTruthy(); expect(Array.isArray(entry.paths)).toBe(true); expect(entry.paths.length).toBeGreaterThan(0); expect(["md", "json", "yaml"]).toContain(entry.format); expect(typeof entry.detect).toBe("function"); } }); it("registry ids are unique", () => { const ids = PLATFORM_REGISTRY.map((p) => p.id); expect(new Set(ids).size).toBe(ids.length); }); }); describe("parseTargets", () => { it("parses comma-separated known targets", () => { const { known, unknown } = parseTargets("claude-code,cursor,zed"); expect(known).toEqual(["claude-code", "cursor", "zed"]); expect(unknown).toEqual([]); }); it("separates unknown ids", () => { const { known, unknown } = parseTargets("cursor,bogus,zed"); expect(known).toEqual(["cursor", "zed"]); expect(unknown).toEqual(["bogus"]); }); it("returns empty when value is undefined", () => { expect(parseTargets(undefined)).toEqual({ known: [], unknown: [] }); }); it("trims whitespace and ignores empty entries", () => { const { known } = parseTargets(" cursor , , zed "); expect(known).toEqual(["cursor", "zed"]); }); });