test: 新增测试代码文件

This commit is contained in:
claude-code-best
2026-04-02 14:44:56 +08:00
parent 9c3803d16b
commit 006ad97fbb
32 changed files with 1102 additions and 68 deletions

View File

@@ -83,4 +83,20 @@ describe("CircularBuffer", () => {
buf.add("c");
expect(buf.toArray()).toEqual(["b", "c"]);
});
test("capacity=1 keeps only the most recent item", () => {
const buf = new CircularBuffer<number>(1);
buf.add(10);
expect(buf.toArray()).toEqual([10]);
buf.add(20);
expect(buf.toArray()).toEqual([20]);
buf.add(30);
expect(buf.toArray()).toEqual([30]);
expect(buf.getRecent(1)).toEqual([30]);
});
test("getRecent on empty buffer returns empty array", () => {
const buf = new CircularBuffer<number>(5);
expect(buf.getRecent(3)).toEqual([]);
});
});

View File

@@ -29,6 +29,14 @@ describe("parseArguments", () => {
]);
});
test("handles escaped quotes inside quoted strings", () => {
expect(parseArguments('foo "hello \\"world\\"" baz')).toEqual([
"foo",
'hello "world"',
"baz",
]);
});
test("returns empty for empty string", () => {
expect(parseArguments("")).toEqual([]);
});
@@ -101,6 +109,16 @@ describe("substituteArguments", () => {
);
});
test("replaces out-of-range index with empty string", () => {
expect(substituteArguments("$5", "hello world")).toBe("");
});
test("reuses same placeholder multiple times", () => {
expect(substituteArguments("cmd $0 $1 $0", "alpha beta")).toBe(
"cmd alpha beta alpha"
);
});
test("replaces named arguments", () => {
expect(
substituteArguments("file: $name", "test.txt", true, ["name"])

View File

@@ -62,6 +62,19 @@ describe("stripHtmlComments", () => {
expect(result.content).toContain("<!-- inline -->");
expect(result.stripped).toBe(false);
});
test("leaves unclosed HTML comment unchanged", () => {
const result = stripHtmlComments("<!-- no close some text");
expect(result.content).toBe("<!-- no close some text");
expect(result.stripped).toBe(false);
});
test("strips comment and keeps same-line residual content", () => {
const result = stripHtmlComments("<!-- note -->some text");
expect(result.content).toContain("some text");
expect(result.content).not.toContain("<!--");
expect(result.stripped).toBe(true);
});
});
describe("isMemoryFilePath", () => {
@@ -88,6 +101,14 @@ describe("isMemoryFilePath", () => {
test("returns false for .claude directory non-rules file", () => {
expect(isMemoryFilePath("/project/.claude/settings.json")).toBe(false);
});
test("returns false for lowercase claude.md (case-sensitive match)", () => {
expect(isMemoryFilePath("/project/claude.md")).toBe(false);
});
test("returns false for non-.md file in .claude/rules/", () => {
expect(isMemoryFilePath(".claude/rules/foo.txt")).toBe(false);
});
});
describe("getLargeMemoryFiles", () => {
@@ -120,4 +141,8 @@ describe("getLargeMemoryFiles", () => {
const result = getLargeMemoryFiles(files);
expect(result).toHaveLength(1);
});
test("returns empty array for empty input", () => {
expect(getLargeMemoryFiles([])).toEqual([]);
});
});

View File

@@ -52,4 +52,21 @@ describe("insertBlockAfterToolResults", () => {
expect(content[0]).toEqual({ type: "text", text: "new" });
expect(content[1]).toEqual({ type: "text", text: "only" });
});
test("inserts after last tool_result with mixed interleaving", () => {
const content: any[] = [
{ type: "tool_result", content: "r1" },
{ type: "text", text: "mid1" },
{ type: "tool_result", content: "r2" },
{ type: "text", text: "mid2" },
{ type: "tool_result", content: "r3" },
{ type: "text", text: "end" },
];
insertBlockAfterToolResults(content, { type: "text", text: "inserted" });
// Inserted after r3 (index 4), so at index 5
expect(content[5]).toEqual({ type: "text", text: "inserted" });
// Original end text should shift to index 6
expect(content[6]).toEqual({ type: "text", text: "end" });
expect(content).toHaveLength(7);
});
});

View File

@@ -40,9 +40,10 @@ describe("getPatchFromContents", () => {
oldContent: "hello\nworld",
newContent: "hello\nplanet",
});
expect(hunks.length).toBeGreaterThan(0);
expect(hunks[0].lines.some((l: string) => l.startsWith("-"))).toBe(true);
expect(hunks[0].lines.some((l: string) => l.startsWith("+"))).toBe(true);
expect(hunks.length).toBe(1);
const allLines = hunks[0].lines;
expect(allLines).toContain("-world");
expect(allLines).toContain("+planet");
});
test("returns empty hunks for identical content", () => {
@@ -73,5 +74,44 @@ describe("getPatchFromContents", () => {
newContent: "new content",
});
expect(hunks.length).toBeGreaterThan(0);
const allLines = hunks.flatMap((h: any) => h.lines);
expect(allLines.some((l: string) => l.startsWith("+"))).toBe(true);
});
test("handles content with dollar signs", () => {
const hunks = getPatchFromContents({
filePath: "test.txt",
oldContent: "price: $5",
newContent: "price: $10",
});
expect(hunks.length).toBeGreaterThan(0);
const allLines = hunks.flatMap((h: any) => h.lines);
expect(allLines.some((l: string) => l.includes("$"))).toBe(true);
// Verify dollar signs are unescaped (not the token)
expect(allLines.some((l: string) => l.includes("<<:DOLLAR_TOKEN:>>"))).toBe(false);
});
test("handles deleting all content", () => {
const hunks = getPatchFromContents({
filePath: "test.txt",
oldContent: "line1\nline2\nline3",
newContent: "",
});
expect(hunks.length).toBeGreaterThan(0);
const allLines = hunks.flatMap((h: any) => h.lines);
expect(allLines.some((l: string) => l.startsWith("-"))).toBe(true);
expect(allLines.every((l: string) => l.startsWith("-") || l.startsWith(" ") || l.startsWith("\\"))).toBe(true);
});
test("ignoreWhitespace treats indentation changes as identical", () => {
const old = "function foo() {\n return 42;\n}\n";
const nw = "function foo() {\n\treturn 42;\n}\n";
const hunks = getPatchFromContents({
filePath: "test.txt",
oldContent: old,
newContent: nw,
ignoreWhitespace: true,
});
expect(hunks).toEqual([]);
});
});

View File

@@ -38,14 +38,14 @@ describe("validateBoundedIntEnvVar", () => {
const result = validateBoundedIntEnvVar("TEST_VAR", "2000", 100, 1000);
expect(result.effective).toBe(1000);
expect(result.status).toBe("capped");
expect(result.message).toContain("Capped from 2000 to 1000");
expect(result.message).toBe("Capped from 2000 to 1000");
});
test("returns default for non-numeric value", () => {
const result = validateBoundedIntEnvVar("TEST_VAR", "abc", 100, 1000);
expect(result.effective).toBe(100);
expect(result.status).toBe("invalid");
expect(result.message).toContain("Invalid value");
expect(result.message).toBe('Invalid value "abc" (using default: 100)');
});
test("returns default for zero", () => {
@@ -66,9 +66,21 @@ describe("validateBoundedIntEnvVar", () => {
expect(result.status).toBe("valid");
});
test("handles value of 1 (minimum valid)", () => {
test("handles value of 1 (no lower bound check, only parsed > 0)", () => {
const result = validateBoundedIntEnvVar("TEST_VAR", "1", 100, 1000);
expect(result.effective).toBe(1);
expect(result.status).toBe("valid");
});
test("truncates float input via parseInt", () => {
const result = validateBoundedIntEnvVar("TEST_VAR", "50.7", 100, 1000);
expect(result.effective).toBe(50);
expect(result.status).toBe("valid");
});
test("handles whitespace in value", () => {
const result = validateBoundedIntEnvVar("TEST_VAR", " 500 ", 100, 1000);
expect(result.effective).toBe(500);
expect(result.status).toBe("valid");
});
});

View File

@@ -34,10 +34,7 @@ describe("convertLeadingTabsToSpaces", () => {
describe("addLineNumbers", () => {
test("adds line numbers starting from 1", () => {
const result = addLineNumbers({ content: "a\nb\nc", startLine: 1 });
expect(result).toContain("1");
expect(result).toContain("a");
expect(result).toContain("b");
expect(result).toContain("c");
expect(result).toMatch(/^\s*1[→\t]a\n\s*2[→\t]b\n\s*3[→\t]c$/);
});
test("returns empty string for empty content", () => {
@@ -46,7 +43,7 @@ describe("addLineNumbers", () => {
test("respects startLine offset", () => {
const result = addLineNumbers({ content: "hello", startLine: 10 });
expect(result).toContain("10");
expect(result).toMatch(/^\s*10[→\t]hello$/);
});
});

View File

@@ -82,20 +82,17 @@ describe("formatNumber", () => {
});
test("formats thousands with k suffix", () => {
const result = formatNumber(1321);
expect(result).toContain("k");
expect(formatNumber(1321)).toBe("1.3k");
});
test("formats millions", () => {
const result = formatNumber(1500000);
expect(result).toContain("m");
expect(formatNumber(1500000)).toBe("1.5m");
});
});
describe("formatTokens", () => {
test("removes .0 from formatted number", () => {
const result = formatTokens(1000);
expect(result).not.toContain(".0");
expect(formatTokens(1000)).toBe("1k");
});
test("formats small numbers", () => {
@@ -108,26 +105,20 @@ describe("formatRelativeTime", () => {
test("formats seconds ago", () => {
const date = new Date("2026-01-15T11:59:30Z");
const result = formatRelativeTime(date, { now });
expect(result).toContain("30");
expect(result).toContain("ago");
expect(formatRelativeTime(date, { now })).toBe("30s ago");
});
test("formats minutes ago", () => {
const date = new Date("2026-01-15T11:55:00Z");
const result = formatRelativeTime(date, { now });
expect(result).toContain("5");
expect(result).toContain("ago");
expect(formatRelativeTime(date, { now })).toBe("5m ago");
});
test("formats future time", () => {
const date = new Date("2026-01-15T13:00:00Z");
const result = formatRelativeTime(date, { now });
expect(result).toContain("in");
expect(formatRelativeTime(date, { now })).toBe("in 1h");
});
test("handles zero difference", () => {
const result = formatRelativeTime(now, { now });
expect(result).toContain("0");
expect(formatRelativeTime(now, { now })).toBe("0s ago");
});
});

View File

@@ -20,7 +20,11 @@ describe("djb2Hash", () => {
test("returns 32-bit integer", () => {
const hash = djb2Hash("some long string to hash");
expect(hash).toBe(hash | 0); // bitwise OR with 0 preserves 32-bit int
expect(Number.isSafeInteger(hash)).toBe(true);
});
test("has known answer for 'hello'", () => {
expect(djb2Hash("hello")).toBe(99162322);
});
});
@@ -36,6 +40,14 @@ describe("hashContent", () => {
test("different strings produce different hashes", () => {
expect(hashContent("abc")).not.toBe(hashContent("def"));
});
test("returns numeric string for empty string", () => {
expect(hashContent("")).toMatch(/^\d+$/);
});
test("returns numeric string format", () => {
expect(hashContent("hello")).toMatch(/^\d+$/);
});
});
describe("hashPair", () => {
@@ -54,4 +66,11 @@ describe("hashPair", () => {
test("disambiguates different splits", () => {
expect(hashPair("ts", "code")).not.toBe(hashPair("tsc", "ode"));
});
test("handles empty strings", () => {
expect(hashPair("", "")).toMatch(/^\d+$/);
expect(hashPair("", "a")).toMatch(/^\d+$/);
expect(hashPair("a", "")).toMatch(/^\d+$/);
expect(hashPair("", "a")).not.toBe(hashPair("a", ""));
});
});

View File

@@ -375,6 +375,16 @@ describe("isNotEmptyMessage", () => {
};
expect(isNotEmptyMessage(msg)).toBe(true);
});
test("returns false for whitespace-only text block in content array", () => {
const msg: any = {
type: "user",
message: {
content: [{ type: "text", text: " " }],
},
};
expect(isNotEmptyMessage(msg)).toBe(false);
});
});
// ─── deriveUUID ─────────────────────────────────────────────────────────
@@ -458,6 +468,11 @@ describe("normalizeMessages", () => {
]);
const normalized = normalizeMessages([msg]);
expect(normalized.length).toBe(2);
// Verify each split message contains only one content block
expect(normalized[0].message.content).toHaveLength(1);
expect((normalized[0].message.content as any[])[0].text).toBe("first");
expect(normalized[1].message.content).toHaveLength(1);
expect((normalized[1].message.content as any[])[0].text).toBe("second");
});
test("handles empty array", () => {

View File

@@ -36,6 +36,14 @@ describe("parseCellId", () => {
// regex is /^cell-(\d+)$/ so trailing text should fail
expect(parseCellId("cell-0-extra")).toBeUndefined();
});
test("returns undefined for negative numbers", () => {
expect(parseCellId("cell--1")).toBeUndefined();
});
test("parses leading zeros correctly", () => {
expect(parseCellId("cell-007")).toBe(7);
});
});
// ─── mapNotebookCellsToToolResult ──────────────────────────────────────
@@ -72,7 +80,7 @@ describe("mapNotebookCellsToToolResult", () => {
const firstBlock = result.content![0] as { type: string; text: string };
expect(firstBlock.type).toBe("text");
expect(firstBlock.text).toContain("cell-0");
expect(firstBlock.text).toContain('cell id="cell-0"');
expect(firstBlock.text).toContain("x = 1");
});

View File

@@ -38,4 +38,18 @@ describe("objectGroupBy", () => {
expect(result.admin).toHaveLength(2);
expect(result.user).toHaveLength(1);
});
test("handles key function returning undefined", () => {
const result = objectGroupBy([1, 2, 3], () => undefined as any);
expect(result["undefined"]).toEqual([1, 2, 3]);
});
test("handles keys with special characters", () => {
const result = objectGroupBy(
[{ key: "a/b" }, { key: "a.b" }, { key: "a/b" }],
(item) => item.key
);
expect(result["a/b"]).toHaveLength(2);
expect(result["a.b"]).toHaveLength(1);
});
});

View File

@@ -1,5 +1,9 @@
import { describe, expect, test } from "bun:test";
import { containsPathTraversal, normalizePathForConfigKey } from "../path";
import {
containsPathTraversal,
expandPath,
normalizePathForConfigKey,
} from "../path";
// ─── containsPathTraversal ──────────────────────────────────────────────
@@ -43,6 +47,51 @@ describe("containsPathTraversal", () => {
test("returns false for dotdot in filename without separator", () => {
expect(containsPathTraversal("foo..bar")).toBe(false);
});
test("detects backslash traversal foo\\..\\bar", () => {
expect(containsPathTraversal("foo\\..\\bar")).toBe(true);
});
test("detects .. at end of absolute path", () => {
expect(containsPathTraversal("/path/to/..")).toBe(true);
});
});
// ─── expandPath ─────────────────────────────────────────────────────────
describe("expandPath", () => {
test("expands ~/ to home directory", () => {
const result = expandPath("~/Documents");
expect(result).not.toContain("~");
expect(result).toContain("Documents");
});
test("expands bare ~ to home directory", () => {
const result = expandPath("~");
expect(result).not.toContain("~");
// Should equal home directory
const { homedir } = require("os");
expect(result).toBe(homedir());
});
test("passes absolute paths through normalized", () => {
expect(expandPath("/usr/local/bin")).toBe("/usr/local/bin");
});
test("resolves relative path against baseDir", () => {
expect(expandPath("src", "/project")).toBe("/project/src");
});
test("returns baseDir for empty string", () => {
expect(expandPath("", "/project")).toBe("/project");
});
test("returns cwd-based path for empty string without baseDir", () => {
const result = expandPath("");
// Should be a valid absolute path (cwd normalized)
const { isAbsolute } = require("path");
expect(isAbsolute(result)).toBe(true);
});
});
// ─── normalizePathForConfigKey ──────────────────────────────────────────
@@ -69,4 +118,14 @@ describe("normalizePathForConfigKey", () => {
const result = normalizePathForConfigKey("foo\\bar\\baz");
expect(result).toBe("foo/bar/baz");
});
test("normalizes mixed separators foo/bar\\baz", () => {
const result = normalizePathForConfigKey("foo/bar\\baz");
expect(result).toBe("foo/bar/baz");
});
test("normalizes redundant separators foo//bar", () => {
const result = normalizePathForConfigKey("foo//bar");
expect(result).toBe("foo/bar");
});
});

View File

@@ -13,6 +13,14 @@ describe("gt", () => {
test("returns false when equal", () => {
expect(gt("1.0.0", "1.0.0")).toBe(false);
});
test("returns false for 0.0.0 vs 0.0.0", () => {
expect(gt("0.0.0", "0.0.0")).toBe(false);
});
test("release is greater than pre-release", () => {
expect(gt("1.0.0", "1.0.0-alpha")).toBe(true);
});
});
describe("gte", () => {
@@ -77,6 +85,14 @@ describe("satisfies", () => {
test("does not match major bump in caret", () => {
expect(satisfies("2.0.0", "^1.0.0")).toBe(false);
});
test("matches tilde range", () => {
expect(satisfies("1.2.5", "~1.2.3")).toBe(true);
});
test("matches wildcard range", () => {
expect(satisfies("2.0.0", "*")).toBe(true);
});
});
describe("order", () => {

View File

@@ -17,8 +17,7 @@ describe("truncateToWidth", () => {
test("truncates long string with ellipsis", () => {
const result = truncateToWidth("hello world", 8);
expect(result.endsWith("…")).toBe(true);
expect(result.length).toBeLessThanOrEqual(9); // 8 visible + ellipsis char
expect(result).toBe("hello w…");
});
test("returns ellipsis for maxWidth 1", () => {
@@ -28,6 +27,37 @@ describe("truncateToWidth", () => {
test("handles empty string", () => {
expect(truncateToWidth("", 10)).toBe("");
});
// ── CJK / wide-character tests ──
test("truncates CJK string at width boundary (2 per char)", () => {
expect(truncateToWidth("你好世界", 4)).toBe("你…");
});
test("truncates CJK string preserving full characters", () => {
expect(truncateToWidth("你好世界", 6)).toBe("你好…");
});
test("passes through CJK string when within limit", () => {
expect(truncateToWidth("你好", 4)).toBe("你好");
});
test("handles mixed ASCII + CJK", () => {
expect(truncateToWidth("hello你好", 8)).toBe("hello你…");
});
test("passes through mixed ASCII + CJK at exact limit", () => {
expect(truncateToWidth("hello你好", 9)).toBe("hello你好");
});
test("truncates string containing emoji", () => {
const result = truncateToWidth("hello 👋 world", 10);
expect(result).toBe("hello 👋 …");
});
test("passes through single emoji at sufficient width", () => {
expect(truncateToWidth("👋", 2)).toBe("👋");
});
});
// ─── truncateStartToWidth ───────────────────────────────────────────────
@@ -38,13 +68,20 @@ describe("truncateStartToWidth", () => {
});
test("truncates from start with ellipsis prefix", () => {
const result = truncateStartToWidth("hello world", 8);
expect(result.startsWith("…")).toBe(true);
expect(truncateStartToWidth("hello world", 8)).toBe("…o world");
});
test("returns ellipsis for maxWidth 1", () => {
expect(truncateStartToWidth("hello", 1)).toBe("…");
});
test("truncates CJK from start", () => {
expect(truncateStartToWidth("你好世界", 4)).toBe("…界");
});
test("truncates CJK from start preserving characters", () => {
expect(truncateStartToWidth("你好世界", 6)).toBe("…世界");
});
});
// ─── truncateToWidthNoEllipsis ──────────────────────────────────────────
@@ -63,6 +100,10 @@ describe("truncateToWidthNoEllipsis", () => {
test("returns empty for maxWidth 0", () => {
expect(truncateToWidthNoEllipsis("hello", 0)).toBe("");
});
test("truncates CJK without ellipsis", () => {
expect(truncateToWidthNoEllipsis("你好世界", 4)).toBe("你好");
});
});
// ─── truncatePathMiddle ─────────────────────────────────────────────────
@@ -89,8 +130,11 @@ describe("truncatePathMiddle", () => {
});
test("handles short maxLength < 5", () => {
const result = truncatePathMiddle("src/components/foo.ts", 4);
expect(result).toContain("…");
expect(truncatePathMiddle("src/components/foo.ts", 4)).toBe("src…");
});
test("handles very short maxLength 1", () => {
expect(truncatePathMiddle("/a/b", 1)).toBe("…");
});
});

View File

@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { validateUuid } from "../uuid";
import { validateUuid, createAgentId } from "../uuid";
describe("validateUuid", () => {
test("validates correct UUID", () => {
@@ -9,7 +9,7 @@ describe("validateUuid", () => {
test("validates uppercase UUID", () => {
const result = validateUuid("550E8400-E29B-41D4-A716-446655440000");
expect(result).not.toBeNull();
expect(result).toBe("550E8400-E29B-41D4-A716-446655440000");
});
test("returns null for non-string", () => {
@@ -31,4 +31,21 @@ describe("validateUuid", () => {
test("returns null for UUID with invalid chars", () => {
expect(validateUuid("550e8400-e29b-41d4-a716-44665544000g")).toBeNull();
});
test("returns null for UUID with leading/trailing whitespace", () => {
expect(validateUuid(" 550e8400-e29b-41d4-a716-446655440000")).toBeNull();
expect(validateUuid("550e8400-e29b-41d4-a716-446655440000 ")).toBeNull();
});
});
describe("createAgentId", () => {
test("generates id without label in correct format", () => {
const id = createAgentId();
expect(id).toMatch(/^a[0-9a-f]{16}$/);
});
test("generates id with label in correct format", () => {
const id = createAgentId("compact");
expect(id).toMatch(/^acompact-[0-9a-f]{16}$/);
});
});

View File

@@ -23,8 +23,8 @@ describe("zodToJsonSchema", () => {
const result = zodToJsonSchema(schema);
expect(result.type).toBe("object");
expect(result.properties).toBeDefined();
expect((result.properties as any).name).toBeDefined();
expect((result.properties as any).age).toBeDefined();
expect((result.properties as any).name).toEqual({ type: "string" });
expect((result.properties as any).age).toEqual({ type: "number" });
});
test("converts enum schema", () => {
@@ -39,7 +39,8 @@ describe("zodToJsonSchema", () => {
optional: z.string().optional(),
});
const result = zodToJsonSchema(schema);
expect(result.required).toContain("required");
expect(result.required).toEqual(["required"]);
expect(result.required).not.toContain("optional");
});
test("caches results for same schema reference", () => {