1import { afterEach, assert, it } from "vite-plus/test";
2
3import { appAtomRegistry } from "../../state/atom-registry";
4import { getCachedNativeReviewDiffData } from "./nativeReviewDiffAdapter";
5import {
6 getCachedReviewParsedDiff,
7 getReviewAsyncStateSnapshot,
8 setReviewAsyncError,
9 setReviewTurnDiffLoading,
10} from "./reviewState";
11
12const reviewInput = {
13 threadKey: "env-local:thread-review",
14 sectionId: "turn:1",
15 diff: [
16 "diff --git a/src/a.ts b/src/a.ts",
17 "new file mode 100644",
18 "--- /dev/null",
19 "+++ b/src/a.ts",
20 "@@ -0,0 +1 @@",
21 "+export const value = 1;",
22 ].join("\n"),
23};
24
25afterEach(() => {
26 appAtomRegistry.reset();
27});
28
29it("stores review async loading and error state in atoms", () => {
30 const threadKey = `env-local:thread-review-state-${Date.now()}`;
31
32 setReviewTurnDiffLoading(threadKey, "turn-1", true);
33 setReviewAsyncError(threadKey, "load failed");
34
35 assert.deepStrictEqual(getReviewAsyncStateSnapshot(threadKey), {
36 loadingTurnIds: { "turn-1": true },
37 error: "load failed",
38 });
39
40 setReviewTurnDiffLoading(threadKey, "turn-1", false);
41 setReviewAsyncError(threadKey, null);
42
43 assert.deepStrictEqual(getReviewAsyncStateSnapshot(threadKey), {
44 loadingTurnIds: {},
45 error: null,
46 });
47});
48
49it("reuses unchanged parsed diffs and replaces changed sections", () => {
50 const parsed = getCachedReviewParsedDiff(reviewInput);
51
52 assert.strictEqual(
53 getCachedReviewParsedDiff({ ...reviewInput, diff: `\n${reviewInput.diff}\n` }),
54 parsed,
55 );
56
57 const changed = { ...reviewInput, diff: reviewInput.diff.replace("value = 1", "value = 2") };
58 const updated = getCachedReviewParsedDiff(changed);
59 assert.notStrictEqual(updated, parsed);
60 assert.strictEqual(getCachedReviewParsedDiff(changed), updated);
61});
62
63it("evicts the least recently used section across threads without changing live results or IDs", () => {
64 const recentInput = { ...reviewInput, threadKey: "env-local:thread-recent" };
65 const oldestInput = { ...reviewInput, threadKey: "env-remote:thread-oldest" };
66 const recent = getCachedReviewParsedDiff(recentInput);
67 const oldest = getCachedReviewParsedDiff(oldestInput);
68 const native = getCachedNativeReviewDiffData({ parsedDiff: oldest });
69 for (let index = 0; index < 6; index += 1) {
70 getCachedReviewParsedDiff({ ...reviewInput, threadKey: `env-local:thread-${index}` });
71 }
72
73 assert.strictEqual(getCachedReviewParsedDiff(recentInput), recent);
74 getCachedReviewParsedDiff({ ...reviewInput, threadKey: "env-local:thread-new" });
75
76 assert.strictEqual(getCachedReviewParsedDiff(recentInput), recent);
77 const rebuilt = getCachedReviewParsedDiff(oldestInput);
78 assert.notStrictEqual(rebuilt, oldest);
79 assert.deepStrictEqual(rebuilt, oldest);
80 assert.strictEqual(getCachedNativeReviewDiffData({ parsedDiff: oldest }), native);
81 assert.deepStrictEqual(getCachedNativeReviewDiffData({ parsedDiff: rebuilt }), native);
82});
83
84it("limits cached diffs to 4 Mi source characters before the entry limit", () => {
85 const firstInput = { ...reviewInput, diff: "x".repeat(2 * 1024 * 1024) };
86 const secondInput = { ...firstInput, sectionId: "turn:2" };
87 const first = getCachedReviewParsedDiff(firstInput);
88 const second = getCachedReviewParsedDiff(secondInput);
89
90 getCachedReviewParsedDiff({ ...firstInput, sectionId: "turn:3" });
91
92 assert.strictEqual(getCachedReviewParsedDiff(secondInput), second);
93 assert.notStrictEqual(getCachedReviewParsedDiff(firstInput), first);
94});
95
96it("reclaims the source budget when a cached section changes", () => {
97 const firstInput = { ...reviewInput, diff: "x".repeat(2 * 1024 * 1024) };
98 const secondInput = { ...firstInput, sectionId: "turn:2" };
99 getCachedReviewParsedDiff(firstInput);
100 const second = getCachedReviewParsedDiff(secondInput);
101
102 getCachedReviewParsedDiff({ ...firstInput, diff: null });
103 getCachedReviewParsedDiff({ ...firstInput, sectionId: "turn:3" });
104
105 assert.strictEqual(getCachedReviewParsedDiff(secondInput), second);
106});
107
108it("counts the full source and skips oversized diffs without evicting cached sections", () => {
109 const cached = getCachedReviewParsedDiff(reviewInput);
110 const oversizedInput = {
111 ...reviewInput,
112 sectionId: "turn:oversized",
113 diff: `${reviewInput.diff}${" ".repeat(4 * 1024 * 1024)}`,
114 };
115 const first = getCachedReviewParsedDiff(oversizedInput);
116 const second = getCachedReviewParsedDiff(oversizedInput);
117
118 assert.notStrictEqual(second, first);
119 assert.deepStrictEqual(second, first);
120 assert.strictEqual(getCachedReviewParsedDiff(reviewInput), cached);
121});
122
123it("drops parsed diffs when the app registry resets", () => {
124 const parsed = getCachedReviewParsedDiff(reviewInput);
125
126 appAtomRegistry.reset();
127
128 const rebuilt = getCachedReviewParsedDiff(reviewInput);
129 assert.notStrictEqual(rebuilt, parsed);
130 assert.deepStrictEqual(rebuilt, parsed);
131});