Files
Rune-Spec/tests/adapters/claude-code.test.ts

159 lines
5.2 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from "bun:test";
import { existsSync } from "node:fs";
import { mkdir, rm, readFile, readdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { injectClaudeCode, updateClaudeCode } from "../../src/adapters/claude-code.ts";
const TMP_DIR = join(import.meta.dir, "__tmp_claude_code_test__");
beforeEach(async () => {
await mkdir(TMP_DIR, { recursive: true });
});
afterEach(async () => {
await rm(TMP_DIR, { recursive: true, force: true });
});
describe("injectClaudeCode", () => {
it("生成 discuss、create、plan、build、archive 的 command 文件", async () => {
await injectClaudeCode(TMP_DIR);
const commands = await readdir(join(TMP_DIR, ".claude", "commands"));
for (const stage of ["discuss", "create", "plan", "build", "archive"]) {
expect(commands).toContain(`rune-${stage}.md`);
}
});
it("不生成 rune-status command", async () => {
await injectClaudeCode(TMP_DIR);
const commands = await readdir(join(TMP_DIR, ".claude", "commands"));
expect(commands).not.toContain("rune-status.md");
});
it("生成 rune-intro command", async () => {
await injectClaudeCode(TMP_DIR);
const commands = await readdir(join(TMP_DIR, ".claude", "commands"));
expect(commands).toContain("rune-intro.md");
});
it("command 文件包含 bash 命令", async () => {
await injectClaudeCode(TMP_DIR);
const content = await readFile(
join(TMP_DIR, ".claude", "commands", "rune-discuss.md"),
"utf-8",
);
expect(content).toContain("rune discuss");
expect(content).toContain("```bash");
});
it("create/plan/build/archive command 包含变更名智能识别引导", async () => {
await injectClaudeCode(TMP_DIR);
for (const stage of ["create", "plan", "build", "archive"]) {
const content = await readFile(
join(TMP_DIR, ".claude", "commands", `rune-${stage}.md`),
"utf-8",
);
expect(content).toContain("智能识别");
expect(content).toContain("rune status");
}
});
it("discuss command 不包含智能识别引导", async () => {
await injectClaudeCode(TMP_DIR);
const content = await readFile(
join(TMP_DIR, ".claude", "commands", "rune-discuss.md"),
"utf-8",
);
expect(content).not.toContain("智能识别");
});
it("重复注入时不覆盖已存在的文件", async () => {
await injectClaudeCode(TMP_DIR);
const originalContent = await readFile(
join(TMP_DIR, ".claude", "commands", "rune-discuss.md"),
"utf-8",
);
await injectClaudeCode(TMP_DIR);
const content = await readFile(
join(TMP_DIR, ".claude", "commands", "rune-discuss.md"),
"utf-8",
);
expect(content).toBe(originalContent);
});
});
describe("updateClaudeCode", () => {
it("文件不存在时创建", async () => {
await updateClaudeCode(TMP_DIR);
expect(existsSync(join(TMP_DIR, ".claude", "commands", "rune-discuss.md"))).toBe(true);
});
it("文件存在且内容一致时不覆盖", async () => {
await injectClaudeCode(TMP_DIR);
const originalContent = await readFile(
join(TMP_DIR, ".claude", "commands", "rune-discuss.md"),
"utf-8",
);
await updateClaudeCode(TMP_DIR);
const content = await readFile(
join(TMP_DIR, ".claude", "commands", "rune-discuss.md"),
"utf-8",
);
expect(content).toBe(originalContent);
});
it("文件存在但内容不一致时覆盖", async () => {
await injectClaudeCode(TMP_DIR);
await writeFile(join(TMP_DIR, ".claude", "commands", "rune-discuss.md"), "旧内容");
await updateClaudeCode(TMP_DIR);
const content = await readFile(
join(TMP_DIR, ".claude", "commands", "rune-discuss.md"),
"utf-8",
);
expect(content).not.toBe("旧内容");
expect(content).toContain("rune discuss");
});
it("更新时生成 rune-intro 命令", async () => {
await updateClaudeCode(TMP_DIR);
expect(existsSync(join(TMP_DIR, ".claude", "commands", "rune-intro.md"))).toBe(true);
});
it("不生成 rune-status 命令", async () => {
await updateClaudeCode(TMP_DIR);
expect(existsSync(join(TMP_DIR, ".claude", "commands", "rune-status.md"))).toBe(false);
});
});
describe("injectClaudeCode with command prefix", () => {
it("使用自定义前缀生成 command 文件", async () => {
await injectClaudeCode(TMP_DIR, "bunx @lanyuanxiaoyao/rune");
const content = await readFile(
join(TMP_DIR, ".claude", "commands", "rune-discuss.md"),
"utf-8",
);
expect(content).toContain("bunx @lanyuanxiaoyao/rune discuss");
});
it("不传前缀时使用默认 rune 前缀", async () => {
await injectClaudeCode(TMP_DIR);
const content = await readFile(
join(TMP_DIR, ".claude", "commands", "rune-discuss.md"),
"utf-8",
);
expect(content).toContain("rune discuss");
});
it("rune-intro command 使用自定义前缀", async () => {
await injectClaudeCode(TMP_DIR, "npx @lanyuanxiaoyao/rune");
const content = await readFile(join(TMP_DIR, ".claude", "commands", "rune-intro.md"), "utf-8");
expect(content).toContain("npx @lanyuanxiaoyao/rune status");
});
});