test: 添加 Context 构建单元测试 (测试计划 03)
覆盖 stripHtmlComments、isMemoryFilePath、getLargeMemoryFiles、 buildEffectiveSystemPrompt 等函数,共 25 个测试用例全部通过。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
cad6409bfe
commit
c4344c4df0
123
src/utils/__tests__/claudemd.test.ts
Normal file
123
src/utils/__tests__/claudemd.test.ts
Normal file
@ -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
src/utils/__tests__/systemPrompt.test.ts
Normal file
88
src/utils/__tests__/systemPrompt.test.ts
Normal file
@ -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"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user