| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- /**
- * Unit tests for ContextMapper
- *
- * Tests context file path mapping between OAC and other platforms.
- */
- import { describe, it, expect } from "vitest";
- import {
- mapContextPathFromOAC,
- mapContextPathToOAC,
- mapContextReferenceFromOAC,
- mapContextReferencesFromOAC,
- mapSkillsToClaudeFormat,
- mapSkillsFromClaudeFormat,
- getContextBaseDir,
- supportsExternalContext,
- supportsContextSubdirs,
- supportsContextPriority,
- createContextReference,
- normalizeContextPath,
- getRelativeContextPath,
- type ContextPlatform,
- } from "../../../src/mappers/ContextMapper";
- import type { ContextReference, SkillReference } from "../../../src/types";
- describe("ContextMapper", () => {
- // ==========================================================================
- // mapContextPathFromOAC - OAC to Platform Path Mapping
- // ==========================================================================
- describe("mapContextPathFromOAC()", () => {
- describe("Claude platform", () => {
- it("maps OAC context path to Claude skills path", () => {
- const result = mapContextPathFromOAC(
- ".opencode/context/core/standards.md",
- "claude"
- );
- expect(result.path).toBe(".claude/skills/core/standards.md");
- expect(result.exact).toBe(true);
- expect(result.warning).toBeUndefined();
- });
- it("preserves subdirectory structure for Claude", () => {
- const result = mapContextPathFromOAC(
- ".opencode/context/domain/auth/models.md",
- "claude"
- );
- expect(result.path).toBe(".claude/skills/domain/auth/models.md");
- expect(result.exact).toBe(true);
- });
- it("handles paths without base dir prefix", () => {
- const result = mapContextPathFromOAC("core/standards.md", "claude");
- expect(result.path).toBe(".claude/skills/core/standards.md");
- expect(result.exact).toBe(true);
- });
- });
- describe("Cursor platform (inline-only)", () => {
- it("returns empty path with warning for Cursor", () => {
- const result = mapContextPathFromOAC(
- ".opencode/context/core/standards.md",
- "cursor"
- );
- expect(result.path).toBe("");
- expect(result.exact).toBe(false);
- expect(result.warning).toContain("does not support external context files");
- });
- });
- describe("Windsurf platform", () => {
- it("maps OAC context path to Windsurf context path", () => {
- const result = mapContextPathFromOAC(
- ".opencode/context/core/standards.md",
- "windsurf"
- );
- expect(result.path).toBe(".windsurf/context/core/standards.md");
- expect(result.exact).toBe(true);
- });
- it("preserves subdirectory structure for Windsurf", () => {
- const result = mapContextPathFromOAC(
- ".opencode/context/guides/development.md",
- "windsurf"
- );
- expect(result.path).toBe(".windsurf/context/guides/development.md");
- expect(result.exact).toBe(true);
- });
- });
- });
- // ==========================================================================
- // mapContextPathToOAC - Platform to OAC Path Mapping
- // ==========================================================================
- describe("mapContextPathToOAC()", () => {
- describe("from Claude", () => {
- it("maps Claude skills path to OAC context path", () => {
- const result = mapContextPathToOAC(
- ".claude/skills/core/standards.md",
- "claude"
- );
- expect(result.path).toBe(".opencode/context/core/standards.md");
- expect(result.exact).toBe(true);
- });
- it("handles paths without base dir prefix", () => {
- const result = mapContextPathToOAC("core/standards.md", "claude");
- expect(result.path).toBe(".opencode/context/core/standards.md");
- expect(result.exact).toBe(true);
- });
- });
- describe("from Cursor (inline-only)", () => {
- it("returns empty path with warning for Cursor", () => {
- const result = mapContextPathToOAC("some-path.md", "cursor");
- expect(result.path).toBe("");
- expect(result.exact).toBe(false);
- expect(result.warning).toContain("uses inline context");
- });
- });
- describe("from Windsurf", () => {
- it("maps Windsurf context path to OAC context path", () => {
- const result = mapContextPathToOAC(
- ".windsurf/context/core/standards.md",
- "windsurf"
- );
- expect(result.path).toBe(".opencode/context/core/standards.md");
- expect(result.exact).toBe(true);
- });
- });
- });
- // ==========================================================================
- // mapContextReferenceFromOAC - Context Reference Mapping
- // ==========================================================================
- describe("mapContextReferenceFromOAC()", () => {
- it("maps context reference without priority", () => {
- const context: ContextReference = {
- path: ".opencode/context/core/standards.md",
- };
- const result = mapContextReferenceFromOAC(context, "claude");
- expect(result.path).toBe(".claude/skills/core/standards.md");
- expect(result.exact).toBe(true);
- expect(result.priority).toBeUndefined();
- });
- it("includes priority in result but warns if unsupported", () => {
- const context: ContextReference = {
- path: ".opencode/context/core/standards.md",
- priority: "critical",
- };
- const result = mapContextReferenceFromOAC(context, "claude");
- expect(result.path).toBe(".claude/skills/core/standards.md");
- expect(result.priority).toBe("critical");
- expect(result.exact).toBe(false);
- expect(result.warning).toContain("does not support priority metadata");
- });
- it("returns inline warning for Cursor", () => {
- const context: ContextReference = {
- path: ".opencode/context/core/standards.md",
- priority: "high",
- };
- const result = mapContextReferenceFromOAC(context, "cursor");
- expect(result.path).toBe("");
- expect(result.exact).toBe(false);
- expect(result.warning).toBeDefined();
- });
- });
- // ==========================================================================
- // mapContextReferencesFromOAC - Batch Context Reference Mapping
- // ==========================================================================
- describe("mapContextReferencesFromOAC()", () => {
- it("maps multiple context references", () => {
- const contexts: ContextReference[] = [
- { path: ".opencode/context/core/standards.md" },
- { path: ".opencode/context/guides/workflow.md" },
- ];
- const result = mapContextReferencesFromOAC(contexts, "claude");
- expect(result.paths).toHaveLength(2);
- expect(result.paths[0]).toBe(".claude/skills/core/standards.md");
- expect(result.paths[1]).toBe(".claude/skills/guides/workflow.md");
- expect(result.warnings).toHaveLength(0);
- });
- it("collects warnings from all references", () => {
- const contexts: ContextReference[] = [
- { path: ".opencode/context/core/standards.md", priority: "critical" },
- { path: ".opencode/context/guides/workflow.md", priority: "high" },
- ];
- const result = mapContextReferencesFromOAC(contexts, "claude");
- expect(result.paths).toHaveLength(2);
- expect(result.warnings.length).toBeGreaterThan(0);
- });
- it("filters out empty paths for Cursor", () => {
- const contexts: ContextReference[] = [
- { path: ".opencode/context/core/standards.md" },
- { path: ".opencode/context/guides/workflow.md" },
- ];
- const result = mapContextReferencesFromOAC(contexts, "cursor");
- expect(result.paths).toHaveLength(0);
- expect(result.warnings.length).toBeGreaterThan(0);
- });
- it("handles empty array", () => {
- const result = mapContextReferencesFromOAC([], "claude");
- expect(result.paths).toHaveLength(0);
- expect(result.warnings).toHaveLength(0);
- });
- });
- // ==========================================================================
- // mapSkillsToClaudeFormat - OAC Skills to Claude Format
- // ==========================================================================
- describe("mapSkillsToClaudeFormat()", () => {
- it("maps string skill references", () => {
- const skills: SkillReference[] = ["coding", "testing"];
- const result = mapSkillsToClaudeFormat(skills);
- expect(result.skills).toHaveLength(2);
- expect(result.skills[0]).toBe(".claude/skills/coding.md");
- expect(result.skills[1]).toBe(".claude/skills/testing.md");
- expect(result.warnings).toHaveLength(0);
- });
- it("maps object skill references", () => {
- const skills: SkillReference[] = [
- { name: "coding" },
- { name: "testing" },
- ];
- const result = mapSkillsToClaudeFormat(skills);
- expect(result.skills).toHaveLength(2);
- expect(result.skills[0]).toBe(".claude/skills/coding.md");
- expect(result.skills[1]).toBe(".claude/skills/testing.md");
- });
- it("warns when skill config will be ignored", () => {
- const skills: SkillReference[] = [
- { name: "coding", config: { theme: "dark" } },
- ];
- const result = mapSkillsToClaudeFormat(skills);
- expect(result.skills).toHaveLength(1);
- expect(result.warnings).toHaveLength(1);
- expect(result.warnings[0]).toContain("config for 'coding' will be ignored");
- });
- it("handles mixed string and object skills", () => {
- const skills: SkillReference[] = [
- "simple-skill",
- { name: "configured-skill", config: { option: true } },
- ];
- const result = mapSkillsToClaudeFormat(skills);
- expect(result.skills).toHaveLength(2);
- expect(result.skills[0]).toBe(".claude/skills/simple-skill.md");
- expect(result.skills[1]).toBe(".claude/skills/configured-skill.md");
- expect(result.warnings).toHaveLength(1);
- });
- it("handles empty skill config without warning", () => {
- const skills: SkillReference[] = [{ name: "coding", config: {} }];
- const result = mapSkillsToClaudeFormat(skills);
- expect(result.warnings).toHaveLength(0);
- });
- });
- // ==========================================================================
- // mapSkillsFromClaudeFormat - Claude Skills to OAC Format
- // ==========================================================================
- describe("mapSkillsFromClaudeFormat()", () => {
- it("extracts skill names from Claude paths", () => {
- const skillPaths = [".claude/skills/coding.md", ".claude/skills/testing.md"];
- const result = mapSkillsFromClaudeFormat(skillPaths);
- expect(result).toHaveLength(2);
- expect(result[0]).toBe("coding");
- expect(result[1]).toBe("testing");
- });
- it("handles nested skill paths", () => {
- const skillPaths = [".claude/skills/advanced/coding.md"];
- const result = mapSkillsFromClaudeFormat(skillPaths);
- expect(result).toHaveLength(1);
- expect(result[0]).toBe("coding");
- });
- it("handles paths without expected format", () => {
- const skillPaths = ["some/weird/path"];
- const result = mapSkillsFromClaudeFormat(skillPaths);
- expect(result).toHaveLength(1);
- // Falls back to full path when no match
- expect(result[0]).toBe("some/weird/path");
- });
- it("handles empty array", () => {
- const result = mapSkillsFromClaudeFormat([]);
- expect(result).toHaveLength(0);
- });
- });
- // ==========================================================================
- // Utility Functions
- // ==========================================================================
- describe("getContextBaseDir()", () => {
- it("returns correct base dir for OAC", () => {
- expect(getContextBaseDir("oac")).toBe(".opencode/context");
- });
- it("returns correct base dir for Claude", () => {
- expect(getContextBaseDir("claude")).toBe(".claude/skills");
- });
- it("returns empty string for Cursor (inline-only)", () => {
- expect(getContextBaseDir("cursor")).toBe("");
- });
- it("returns correct base dir for Windsurf", () => {
- expect(getContextBaseDir("windsurf")).toBe(".windsurf/context");
- });
- });
- describe("supportsExternalContext()", () => {
- it("returns true for OAC", () => {
- expect(supportsExternalContext("oac")).toBe(true);
- });
- it("returns true for Claude", () => {
- expect(supportsExternalContext("claude")).toBe(true);
- });
- it("returns false for Cursor", () => {
- expect(supportsExternalContext("cursor")).toBe(false);
- });
- it("returns true for Windsurf", () => {
- expect(supportsExternalContext("windsurf")).toBe(true);
- });
- });
- describe("supportsContextSubdirs()", () => {
- it("returns true for OAC", () => {
- expect(supportsContextSubdirs("oac")).toBe(true);
- });
- it("returns true for Claude", () => {
- expect(supportsContextSubdirs("claude")).toBe(true);
- });
- it("returns false for Cursor", () => {
- expect(supportsContextSubdirs("cursor")).toBe(false);
- });
- it("returns true for Windsurf", () => {
- expect(supportsContextSubdirs("windsurf")).toBe(true);
- });
- });
- describe("supportsContextPriority()", () => {
- it("returns true only for OAC", () => {
- expect(supportsContextPriority("oac")).toBe(true);
- expect(supportsContextPriority("claude")).toBe(false);
- expect(supportsContextPriority("cursor")).toBe(false);
- expect(supportsContextPriority("windsurf")).toBe(false);
- });
- });
- describe("createContextReference()", () => {
- it("creates reference with path only", () => {
- const ref = createContextReference(".opencode/context/core/standards.md");
- expect(ref.path).toBe(".opencode/context/core/standards.md");
- expect(ref.priority).toBeUndefined();
- expect(ref.description).toBeUndefined();
- });
- it("creates reference with priority", () => {
- const ref = createContextReference(
- ".opencode/context/core/standards.md",
- "critical"
- );
- expect(ref.path).toBe(".opencode/context/core/standards.md");
- expect(ref.priority).toBe("critical");
- });
- it("creates reference with all fields", () => {
- const ref = createContextReference(
- ".opencode/context/core/standards.md",
- "high",
- "Code quality standards"
- );
- expect(ref.path).toBe(".opencode/context/core/standards.md");
- expect(ref.priority).toBe("high");
- expect(ref.description).toBe("Code quality standards");
- });
- });
- describe("normalizeContextPath()", () => {
- it("removes duplicate slashes", () => {
- expect(normalizeContextPath("path//to///file.md")).toBe("path/to/file.md");
- });
- it("removes leading slash", () => {
- expect(normalizeContextPath("/path/to/file.md")).toBe("path/to/file.md");
- });
- it("removes trailing slash", () => {
- expect(normalizeContextPath("path/to/directory/")).toBe("path/to/directory");
- });
- it("handles combined issues", () => {
- expect(normalizeContextPath("//path//to///file/")).toBe("path/to/file");
- });
- it("returns unchanged normal path", () => {
- expect(normalizeContextPath("path/to/file.md")).toBe("path/to/file.md");
- });
- });
- describe("getRelativeContextPath()", () => {
- it("extracts relative path for OAC", () => {
- const result = getRelativeContextPath(
- ".opencode/context/core/standards.md",
- "oac"
- );
- expect(result).toBe("core/standards.md");
- });
- it("extracts relative path for Claude", () => {
- const result = getRelativeContextPath(
- ".claude/skills/coding/standards.md",
- "claude"
- );
- expect(result).toBe("coding/standards.md");
- });
- it("returns full path if base dir not found", () => {
- const result = getRelativeContextPath("some/other/path.md", "oac");
- expect(result).toBe("some/other/path.md");
- });
- it("handles Cursor (empty base dir)", () => {
- const result = getRelativeContextPath("any/path.md", "cursor");
- expect(result).toBe("any/path.md");
- });
- });
- });
|