diff --git a/packages/opencode/test/command/hints.test.ts b/packages/opencode/test/command/hints.test.ts new file mode 100644 index 0000000000..43a855960a --- /dev/null +++ b/packages/opencode/test/command/hints.test.ts @@ -0,0 +1,28 @@ +import { describe, test, expect } from "bun:test" +import { Command } from "../../src/command/index" + +describe("Command.hints: template placeholder extraction", () => { + test("returns empty array for template with no placeholders", () => { + expect(Command.hints("Run the tests and report results")).toEqual([]) + expect(Command.hints("")).toEqual([]) + }) + + test("extracts numbered placeholders in sorted order", () => { + expect(Command.hints("Review $2 and compare with $1")).toEqual(["$1", "$2"]) + }) + + test("deduplicates repeated placeholder occurrences", () => { + expect(Command.hints("Use $1 then use $1 again and $2")).toEqual(["$1", "$2"]) + }) + + test("appends $ARGUMENTS when present", () => { + expect(Command.hints("Do something with $ARGUMENTS")).toEqual(["$ARGUMENTS"]) + }) + + test("multi-digit placeholders sort lexicographically", () => { + // $10 sorts before $2 in lexicographic order — this is the actual behavior + // since the code uses [...new Set(numbered)].sort() which is string sort + const result = Command.hints("Map $1 to $2 and also $10") + expect(result).toEqual(["$1", "$10", "$2"]) + }) +}) diff --git a/packages/opencode/test/skill/fmt.test.ts b/packages/opencode/test/skill/fmt.test.ts new file mode 100644 index 0000000000..5659b63182 --- /dev/null +++ b/packages/opencode/test/skill/fmt.test.ts @@ -0,0 +1,55 @@ +import { describe, test, expect } from "bun:test" +import { Skill } from "../../src/skill/skill" + +function skill(overrides: Partial = {}): Skill.Info { + return { + name: overrides.name ?? "test-skill", + description: overrides.description ?? "A test skill", + location: overrides.location ?? "/home/user/skills/test-skill/SKILL.md", + content: overrides.content ?? "# Test\nDo the thing.", + } +} + +describe("Skill.fmt: skill list formatting", () => { + test("returns 'No skills' message for empty list", () => { + expect(Skill.fmt([], { verbose: false })).toBe("No skills are currently available.") + expect(Skill.fmt([], { verbose: true })).toBe("No skills are currently available.") + }) + + test("verbose mode returns XML with skill tags", () => { + const skills = [ + skill({ name: "analyze", description: "Analyze code", location: "/path/to/analyze/SKILL.md" }), + skill({ name: "deploy", description: "Deploy app", location: "/path/to/deploy/SKILL.md" }), + ] + const output = Skill.fmt(skills, { verbose: true }) + expect(output).toContain("") + expect(output).toContain("") + expect(output).toContain("analyze") + expect(output).toContain("Analyze code") + expect(output).toContain("deploy") + expect(output).toContain("Deploy app") + // File paths get converted to file:// URLs + expect(output).toContain("file:///path/to/analyze/SKILL.md") + }) + + test("non-verbose returns markdown with bullet points", () => { + const skills = [ + skill({ name: "lint", description: "Lint the code" }), + skill({ name: "format", description: "Format files" }), + ] + const output = Skill.fmt(skills, { verbose: false }) + expect(output).toContain("## Available Skills") + expect(output).toContain("- **lint**: Lint the code") + expect(output).toContain("- **format**: Format files") + }) + + test("verbose mode preserves builtin: protocol without file:// conversion", () => { + const skills = [ + skill({ name: "builtin-skill", description: "Built in", location: "builtin:my-skill/SKILL.md" }), + ] + const output = Skill.fmt(skills, { verbose: true }) + expect(output).toContain("builtin:my-skill/SKILL.md") + // Should NOT contain file:// for builtin: paths + expect(output).not.toContain("file://") + }) +})