Преглед изворни кода

test: 添加 Context 构建单元测试 (测试计划 03)

覆盖 stripHtmlComments、isMemoryFilePath、getLargeMemoryFiles、
buildEffectiveSystemPrompt 等函数,共 25 个测试用例全部通过。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
claude-code-best пре 3 недеља
родитељ
комит
c4344c4df0
2 измењених фајлова са 211 додато и 0 уклоњено
  1. 123 0
      src/utils/__tests__/claudemd.test.ts
  2. 88 0
      src/utils/__tests__/systemPrompt.test.ts

+ 123 - 0
src/utils/__tests__/claudemd.test.ts

@@ -0,0 +1,123 @@
+import { describe, expect, test } from "bun:test";
+import {
+  stripHtmlComments,
+  isMemoryFilePath,
+  getLargeMemoryFiles,
+  MAX_MEMORY_CHARACTER_COUNT,
+  type MemoryFileInfo,
+} from "../claudemd";
+
+function mockMemoryFile(overrides: Partial<MemoryFileInfo> = {}): MemoryFileInfo {
+  return {
+    path: "/project/CLAUDE.md",
+    type: "Project",
+    content: "test content",
+    ...overrides,
+  };
+}
+
+describe("stripHtmlComments", () => {
+  test("strips block-level HTML comments (own line)", () => {
+    // CommonMark type-2 HTML blocks: comment must start at beginning of line
+    const result = stripHtmlComments("text\n<!-- block comment -->\nmore");
+    expect(result.content).not.toContain("block comment");
+    expect(result.stripped).toBe(true);
+  });
+
+  test("returns stripped: false when no comments", () => {
+    const result = stripHtmlComments("no comments here");
+    expect(result.stripped).toBe(false);
+    expect(result.content).toBe("no comments here");
+  });
+
+  test("returns stripped: true when block comments exist", () => {
+    const result = stripHtmlComments("hello\n<!-- world -->\nend");
+    expect(result.stripped).toBe(true);
+  });
+
+  test("handles empty string", () => {
+    const result = stripHtmlComments("");
+    expect(result.content).toBe("");
+    expect(result.stripped).toBe(false);
+  });
+
+  test("handles multiple block comments", () => {
+    const result = stripHtmlComments(
+      "a\n<!-- c1 -->\nb\n<!-- c2 -->\nc"
+    );
+    expect(result.content).not.toContain("c1");
+    expect(result.content).not.toContain("c2");
+    expect(result.stripped).toBe(true);
+  });
+
+  test("preserves code block content", () => {
+    const input = "text\n```html\n<!-- not stripped -->\n```\nmore";
+    const result = stripHtmlComments(input);
+    expect(result.content).toContain("<!-- not stripped -->");
+  });
+
+  test("preserves inline comments within paragraphs", () => {
+    // Inline comments are NOT stripped (CommonMark paragraph semantics)
+    const result = stripHtmlComments("text <!-- inline --> more");
+    expect(result.content).toContain("<!-- inline -->");
+    expect(result.stripped).toBe(false);
+  });
+});
+
+describe("isMemoryFilePath", () => {
+  test("returns true for CLAUDE.md path", () => {
+    expect(isMemoryFilePath("/project/CLAUDE.md")).toBe(true);
+  });
+
+  test("returns true for CLAUDE.local.md path", () => {
+    expect(isMemoryFilePath("/project/CLAUDE.local.md")).toBe(true);
+  });
+
+  test("returns true for .claude/rules/ path", () => {
+    expect(isMemoryFilePath("/project/.claude/rules/foo.md")).toBe(true);
+  });
+
+  test("returns false for regular file", () => {
+    expect(isMemoryFilePath("/project/src/main.ts")).toBe(false);
+  });
+
+  test("returns false for unrelated .md file", () => {
+    expect(isMemoryFilePath("/project/README.md")).toBe(false);
+  });
+
+  test("returns false for .claude directory non-rules file", () => {
+    expect(isMemoryFilePath("/project/.claude/settings.json")).toBe(false);
+  });
+});
+
+describe("getLargeMemoryFiles", () => {
+  test("returns files exceeding threshold", () => {
+    const largeContent = "x".repeat(MAX_MEMORY_CHARACTER_COUNT + 1);
+    const files = [
+      mockMemoryFile({ content: "small" }),
+      mockMemoryFile({ content: largeContent, path: "/big.md" }),
+    ];
+    const result = getLargeMemoryFiles(files);
+    expect(result).toHaveLength(1);
+    expect(result[0].path).toBe("/big.md");
+  });
+
+  test("returns empty array when all files are small", () => {
+    const files = [
+      mockMemoryFile({ content: "small" }),
+      mockMemoryFile({ content: "also small" }),
+    ];
+    expect(getLargeMemoryFiles(files)).toEqual([]);
+  });
+
+  test("correctly identifies threshold boundary", () => {
+    const atThreshold = "x".repeat(MAX_MEMORY_CHARACTER_COUNT);
+    const overThreshold = "x".repeat(MAX_MEMORY_CHARACTER_COUNT + 1);
+    const files = [
+      mockMemoryFile({ content: atThreshold }),
+      mockMemoryFile({ content: overThreshold }),
+    ];
+    const result = getLargeMemoryFiles(files);
+    expect(result).toHaveLength(1);
+  });
+});

+ 88 - 0
src/utils/__tests__/systemPrompt.test.ts

@@ -0,0 +1,88 @@
+import { describe, expect, test } from "bun:test";
+import { buildEffectiveSystemPrompt } from "../systemPrompt";
+
+const defaultPrompt = ["You are a helpful assistant.", "Follow instructions."];
+
+function buildPrompt(overrides: Record<string, unknown> = {}) {
+  return buildEffectiveSystemPrompt({
+    mainThreadAgentDefinition: undefined,
+    toolUseContext: { options: {} as any },
+    customSystemPrompt: undefined,
+    defaultSystemPrompt: defaultPrompt,
+    appendSystemPrompt: undefined,
+    ...overrides,
+  });
+}
+
+describe("buildEffectiveSystemPrompt", () => {
+  test("returns default system prompt when no overrides", () => {
+    const result = buildPrompt();
+    expect(Array.from(result)).toEqual(defaultPrompt);
+  });
+
+  test("overrideSystemPrompt replaces everything", () => {
+    const result = buildPrompt({ overrideSystemPrompt: "override" });
+    expect(Array.from(result)).toEqual(["override"]);
+  });
+
+  test("customSystemPrompt replaces default", () => {
+    const result = buildPrompt({ customSystemPrompt: "custom" });
+    expect(Array.from(result)).toEqual(["custom"]);
+  });
+
+  test("appendSystemPrompt is appended after main prompt", () => {
+    const result = buildPrompt({ appendSystemPrompt: "appended" });
+    expect(Array.from(result)).toEqual([...defaultPrompt, "appended"]);
+  });
+
+  test("agent definition replaces default prompt", () => {
+    const agentDef = {
+      getSystemPrompt: () => "agent prompt",
+      agentType: "custom",
+    } as any;
+    const result = buildPrompt({ mainThreadAgentDefinition: agentDef });
+    expect(Array.from(result)).toEqual(["agent prompt"]);
+  });
+
+  test("agent definition with append combines both", () => {
+    const agentDef = {
+      getSystemPrompt: () => "agent prompt",
+      agentType: "custom",
+    } as any;
+    const result = buildPrompt({
+      mainThreadAgentDefinition: agentDef,
+      appendSystemPrompt: "extra",
+    });
+    expect(Array.from(result)).toEqual(["agent prompt", "extra"]);
+  });
+
+  test("override takes precedence over agent and custom", () => {
+    const agentDef = {
+      getSystemPrompt: () => "agent prompt",
+      agentType: "custom",
+    } as any;
+    const result = buildPrompt({
+      mainThreadAgentDefinition: agentDef,
+      customSystemPrompt: "custom",
+      appendSystemPrompt: "extra",
+      overrideSystemPrompt: "override",
+    });
+    expect(Array.from(result)).toEqual(["override"]);
+  });
+
+  test("returns array of strings", () => {
+    const result = buildPrompt();
+    expect(Array.isArray(result)).toBe(true);
+    for (const item of result) {
+      expect(typeof item).toBe("string");
+    }
+  });
+
+  test("custom + append combines both", () => {
+    const result = buildPrompt({
+      customSystemPrompt: "custom",
+      appendSystemPrompt: "extra",
+    });
+    expect(Array.from(result)).toEqual(["custom", "extra"]);
+  });
+});