From bb13e28b367b024f4c0fc9019ec4ee2590c21468 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Mar 2026 03:07:00 +0000 Subject: [PATCH] =?UTF-8?q?test:=20command=20hints=20+=20skill=20formattin?= =?UTF-8?q?g=20=E2=80=94=20template=20parsing=20and=20output=20rendering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Command.hints() and Skill.fmt() are user-facing pure functions with zero test coverage. These 9 tests prevent regressions in command argument hint extraction and skill listing output (both verbose XML and markdown formats). Co-Authored-By: Claude Opus 4.6 (1M context) https://claude.ai/code/session_011MEvdFUnKtEEi18HBQcJz5 --- packages/opencode/test/command/hints.test.ts | 28 ++++++++++ packages/opencode/test/skill/fmt.test.ts | 55 ++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 packages/opencode/test/command/hints.test.ts create mode 100644 packages/opencode/test/skill/fmt.test.ts 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://") + }) +})