Skip to content

Commit acfaac5

Browse files
test: Phase 1 — 添加 8 个纯函数测试文件 (+134 tests)
- errors.test.ts: 28 tests (isAbortError, toError, errorMessage, getErrnoCode, isFsInaccessible, classifyAxiosError 等) - shellRuleMatching.test.ts: 22 tests (permissionRuleExtractPrefix, hasWildcards, matchWildcardPattern, parsePermissionRule 等) - argumentSubstitution.test.ts: 18 tests (parseArguments, parseArgumentNames, generateProgressiveArgumentHint, substituteArguments) - CircularBuffer.test.ts: 12 tests (add, addAll, getRecent, toArray, clear, length) - sanitization.test.ts: 14 tests (partiallySanitizeUnicode, recursivelySanitizeUnicode) - slashCommandParsing.test.ts: 8 tests (parseSlashCommand) - contentArray.test.ts: 6 tests (insertBlockAfterToolResults) - objectGroupBy.test.ts: 5 tests (objectGroupBy) 总计:781 tests / 40 files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 91c5bea commit acfaac5

8 files changed

Lines changed: 876 additions & 0 deletions

File tree

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { CircularBuffer } from "../CircularBuffer";
3+
4+
describe("CircularBuffer", () => {
5+
test("starts empty", () => {
6+
const buf = new CircularBuffer<number>(5);
7+
expect(buf.length()).toBe(0);
8+
expect(buf.toArray()).toEqual([]);
9+
});
10+
11+
test("adds items up to capacity", () => {
12+
const buf = new CircularBuffer<number>(3);
13+
buf.add(1);
14+
buf.add(2);
15+
buf.add(3);
16+
expect(buf.length()).toBe(3);
17+
expect(buf.toArray()).toEqual([1, 2, 3]);
18+
});
19+
20+
test("evicts oldest when full", () => {
21+
const buf = new CircularBuffer<number>(3);
22+
buf.add(1);
23+
buf.add(2);
24+
buf.add(3);
25+
buf.add(4);
26+
expect(buf.length()).toBe(3);
27+
expect(buf.toArray()).toEqual([2, 3, 4]);
28+
});
29+
30+
test("evicts multiple oldest items", () => {
31+
const buf = new CircularBuffer<number>(2);
32+
buf.add(1);
33+
buf.add(2);
34+
buf.add(3);
35+
buf.add(4);
36+
buf.add(5);
37+
expect(buf.toArray()).toEqual([4, 5]);
38+
});
39+
40+
test("addAll adds multiple items", () => {
41+
const buf = new CircularBuffer<number>(5);
42+
buf.addAll([1, 2, 3]);
43+
expect(buf.toArray()).toEqual([1, 2, 3]);
44+
});
45+
46+
test("addAll with overflow", () => {
47+
const buf = new CircularBuffer<number>(3);
48+
buf.addAll([1, 2, 3, 4, 5]);
49+
expect(buf.toArray()).toEqual([3, 4, 5]);
50+
});
51+
52+
test("getRecent returns last N items", () => {
53+
const buf = new CircularBuffer<number>(5);
54+
buf.addAll([1, 2, 3, 4, 5]);
55+
expect(buf.getRecent(3)).toEqual([3, 4, 5]);
56+
});
57+
58+
test("getRecent returns fewer when not enough items", () => {
59+
const buf = new CircularBuffer<number>(5);
60+
buf.add(1);
61+
buf.add(2);
62+
expect(buf.getRecent(5)).toEqual([1, 2]);
63+
});
64+
65+
test("getRecent works after wraparound", () => {
66+
const buf = new CircularBuffer<number>(3);
67+
buf.addAll([1, 2, 3, 4, 5]);
68+
expect(buf.getRecent(2)).toEqual([4, 5]);
69+
});
70+
71+
test("clear resets buffer", () => {
72+
const buf = new CircularBuffer<number>(5);
73+
buf.addAll([1, 2, 3]);
74+
buf.clear();
75+
expect(buf.length()).toBe(0);
76+
expect(buf.toArray()).toEqual([]);
77+
});
78+
79+
test("works with string type", () => {
80+
const buf = new CircularBuffer<string>(2);
81+
buf.add("a");
82+
buf.add("b");
83+
buf.add("c");
84+
expect(buf.toArray()).toEqual(["b", "c"]);
85+
});
86+
});
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { describe, expect, test } from "bun:test";
2+
import {
3+
parseArguments,
4+
parseArgumentNames,
5+
generateProgressiveArgumentHint,
6+
substituteArguments,
7+
} from "../argumentSubstitution";
8+
9+
// ─── parseArguments ─────────────────────────────────────────────────────
10+
11+
describe("parseArguments", () => {
12+
test("splits simple arguments", () => {
13+
expect(parseArguments("foo bar baz")).toEqual(["foo", "bar", "baz"]);
14+
});
15+
16+
test("handles quoted strings", () => {
17+
expect(parseArguments('foo "hello world" baz')).toEqual([
18+
"foo",
19+
"hello world",
20+
"baz",
21+
]);
22+
});
23+
24+
test("handles single-quoted strings", () => {
25+
expect(parseArguments("foo 'hello world' baz")).toEqual([
26+
"foo",
27+
"hello world",
28+
"baz",
29+
]);
30+
});
31+
32+
test("returns empty for empty string", () => {
33+
expect(parseArguments("")).toEqual([]);
34+
});
35+
36+
test("returns empty for whitespace only", () => {
37+
expect(parseArguments(" ")).toEqual([]);
38+
});
39+
});
40+
41+
// ─── parseArgumentNames ─────────────────────────────────────────────────
42+
43+
describe("parseArgumentNames", () => {
44+
test("parses space-separated string", () => {
45+
expect(parseArgumentNames("foo bar baz")).toEqual(["foo", "bar", "baz"]);
46+
});
47+
48+
test("accepts array input", () => {
49+
expect(parseArgumentNames(["foo", "bar"])).toEqual(["foo", "bar"]);
50+
});
51+
52+
test("filters out numeric-only names", () => {
53+
expect(parseArgumentNames("foo 123 bar")).toEqual(["foo", "bar"]);
54+
});
55+
56+
test("filters out empty strings", () => {
57+
expect(parseArgumentNames(["foo", "", "bar"])).toEqual(["foo", "bar"]);
58+
});
59+
60+
test("returns empty for undefined", () => {
61+
expect(parseArgumentNames(undefined)).toEqual([]);
62+
});
63+
});
64+
65+
// ─── generateProgressiveArgumentHint ────────────────────────────────────
66+
67+
describe("generateProgressiveArgumentHint", () => {
68+
test("shows remaining arguments", () => {
69+
expect(generateProgressiveArgumentHint(["a", "b", "c"], ["x"])).toBe(
70+
"[b] [c]"
71+
);
72+
});
73+
74+
test("returns undefined when all filled", () => {
75+
expect(
76+
generateProgressiveArgumentHint(["a"], ["x"])
77+
).toBeUndefined();
78+
});
79+
80+
test("shows all when none typed", () => {
81+
expect(generateProgressiveArgumentHint(["a", "b"], [])).toBe("[a] [b]");
82+
});
83+
});
84+
85+
// ─── substituteArguments ────────────────────────────────────────────────
86+
87+
describe("substituteArguments", () => {
88+
test("replaces $ARGUMENTS with full args", () => {
89+
expect(substituteArguments("run $ARGUMENTS", "foo bar")).toBe(
90+
"run foo bar"
91+
);
92+
});
93+
94+
test("replaces indexed $ARGUMENTS[0]", () => {
95+
expect(substituteArguments("run $ARGUMENTS[0]", "foo bar")).toBe("run foo");
96+
});
97+
98+
test("replaces shorthand $0, $1", () => {
99+
expect(substituteArguments("$0 and $1", "hello world")).toBe(
100+
"hello and world"
101+
);
102+
});
103+
104+
test("replaces named arguments", () => {
105+
expect(
106+
substituteArguments("file: $name", "test.txt", true, ["name"])
107+
).toBe("file: test.txt");
108+
});
109+
110+
test("returns content unchanged for undefined args", () => {
111+
expect(substituteArguments("hello", undefined)).toBe("hello");
112+
});
113+
114+
test("appends ARGUMENTS when no placeholder found", () => {
115+
expect(substituteArguments("run this", "extra")).toBe(
116+
"run this\n\nARGUMENTS: extra"
117+
);
118+
});
119+
120+
test("does not append when appendIfNoPlaceholder is false", () => {
121+
expect(substituteArguments("run this", "extra", false)).toBe("run this");
122+
});
123+
124+
test("does not append for empty args string", () => {
125+
expect(substituteArguments("run this", "")).toBe("run this");
126+
});
127+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { insertBlockAfterToolResults } from "../contentArray";
3+
4+
describe("insertBlockAfterToolResults", () => {
5+
test("inserts after last tool_result", () => {
6+
const content: any[] = [
7+
{ type: "tool_result", content: "r1" },
8+
{ type: "text", text: "hello" },
9+
];
10+
insertBlockAfterToolResults(content, { type: "text", text: "inserted" });
11+
expect(content[1]).toEqual({ type: "text", text: "inserted" });
12+
expect(content).toHaveLength(3);
13+
});
14+
15+
test("inserts after last of multiple tool_results", () => {
16+
const content: any[] = [
17+
{ type: "tool_result", content: "r1" },
18+
{ type: "tool_result", content: "r2" },
19+
{ type: "text", text: "end" },
20+
];
21+
insertBlockAfterToolResults(content, { type: "text", text: "new" });
22+
expect(content[2]).toEqual({ type: "text", text: "new" });
23+
});
24+
25+
test("appends continuation when inserted block would be last", () => {
26+
const content: any[] = [{ type: "tool_result", content: "r1" }];
27+
insertBlockAfterToolResults(content, { type: "text", text: "new" });
28+
expect(content).toHaveLength(3); // original + inserted + continuation
29+
expect(content[2]).toEqual({ type: "text", text: "." });
30+
});
31+
32+
test("inserts before last block when no tool_results", () => {
33+
const content: any[] = [
34+
{ type: "text", text: "a" },
35+
{ type: "text", text: "b" },
36+
];
37+
insertBlockAfterToolResults(content, { type: "text", text: "new" });
38+
expect(content[1]).toEqual({ type: "text", text: "new" });
39+
expect(content).toHaveLength(3);
40+
});
41+
42+
test("handles empty array", () => {
43+
const content: any[] = [];
44+
insertBlockAfterToolResults(content, { type: "text", text: "new" });
45+
expect(content).toHaveLength(1);
46+
expect(content[0]).toEqual({ type: "text", text: "new" });
47+
});
48+
49+
test("handles single element array with no tool_result", () => {
50+
const content: any[] = [{ type: "text", text: "only" }];
51+
insertBlockAfterToolResults(content, { type: "text", text: "new" });
52+
expect(content[0]).toEqual({ type: "text", text: "new" });
53+
expect(content[1]).toEqual({ type: "text", text: "only" });
54+
});
55+
});

0 commit comments

Comments
 (0)