Skip to content
Source
Sign inSign up

nativeReviewDiffSource.test.ts

189 lines6.9 KB

1import { expect, it, vi } from "vite-plus/test";
2​
3import { createNativeReviewDiffSourceLoader } from "./nativeReviewDiffSource";
4import * as DiffHighlightTrace from "./nativeReviewDiffTrace";
5import type { NativeReviewDiffSource } from "./nativeReviewDiffTypes";
6​
7const SOURCE = { oldContents: "const before = true;", newContents: "const after = false;" };
8​
9vi.mock("expo-file-system", () => ({
10 Paths: { document: "test-documents" },
11 File: class {
12 create() {}
13 async write() {}
14 },
15}));
16​
17it("retains fetched stress sources above 500k characters within the aggregate budget", async () => {
18 const source = { oldContents: "", newContents: "x".repeat(3_270_000) };
19 const fetch = vi.fn(async () => source);
20 const load = createNativeReviewDiffSourceLoader(fetch);
21 expect(await load("generated.c")).toBe(source);
22 expect(await load("generated.c")).toBe(source);
23 expect(fetch).toHaveBeenCalledTimes(1);
24});
25​
26it("evicts source text by aggregate size and returns an over-budget response uncached", async () => {
27 const source = { oldContents: "", newContents: "x".repeat(4_500_000) };
28 const huge = { oldContents: "", newContents: "x".repeat(9_000_000) };
29 const fetch = vi.fn(async (id: string) => (id === "huge" ? huge : source));
30 const load = createNativeReviewDiffSourceLoader(fetch);
31 expect(await load("first")).toBe(source);
32 expect(await load("second")).toBe(source);
33 expect(await load("first")).toBe(source);
34 expect(fetch).toHaveBeenCalledTimes(3);
35 expect(await load("huge")).toBe(huge);
36 expect(await load("huge")).toBe(huge);
37 expect(fetch).toHaveBeenCalledTimes(5);
38 expect(await load("first")).toBe(source);
39 expect(fetch).toHaveBeenCalledTimes(5);
40});
41​
42const failureKeys = [
43 ["string", () => "retry.ts"],
44 ["number", () => 1],
45 ["boolean", () => false],
46 ["bigint", () => 1n],
47 ["symbol", () => Symbol("retry.ts")],
48 ["null", () => null],
49 ["undefined", () => undefined],
50 ["object", () => ({ id: "retry.ts" })],
51 ["function", () => () => SOURCE],
52] as const;
53​
54it.each(failureKeys)(
55 "recovers after two transient failures on a later load for a %s key",
56 async (_name, createKey) => {
57 const key = createKey();
58 const fetch = vi
59 .fn()
60 .mockRejectedValueOnce(new Error("Offline"))
61 .mockRejectedValueOnce(new Error("Still offline"))
62 .mockResolvedValue(SOURCE);
63 const load = createNativeReviewDiffSourceLoader(fetch);
64 await expect(load(key)).rejects.toThrow("Offline");
65 await expect(load(key)).rejects.toThrow("Still offline");
66 expect(fetch).toHaveBeenCalledTimes(2);
67 expect(await load(key)).toBe(SOURCE);
68 expect(await load(key)).toBe(SOURCE);
69 expect(fetch).toHaveBeenCalledTimes(3);
70 },
71);
72​
73it.each(failureKeys)(
74 "remembers permanent unavailability for a %s key",
75 async (_name, createKey) => {
76 const key = createKey();
77 const fetch = vi.fn(async () => {
78 throw { reason: "unsupported" };
79 });
80 const load = createNativeReviewDiffSourceLoader(fetch);
81 expect(await load(key)).toBeNull();
82 expect(await load(key)).toBeNull();
83 expect(fetch).toHaveBeenCalledOnce();
84 },
85);
86​
87it.each(failureKeys)(
88 "resets retries after success and eviction for a %s key",
89 async (_name, createKey) => {
90 const key = createKey();
91 const fetch = vi
92 .fn()
93 .mockRejectedValueOnce(new Error("Offline"))
94 .mockResolvedValueOnce(SOURCE)
95 .mockResolvedValueOnce({ oldContents: "", newContents: "x".repeat(8_000_000) })
96 .mockRejectedValueOnce(new Error("Offline again"))
97 .mockResolvedValueOnce(SOURCE);
98 const load = createNativeReviewDiffSourceLoader(fetch);
99 await expect(load(key)).rejects.toThrow("Offline");
100 expect(await load(key)).toBe(SOURCE);
101 await load({ id: "evict" });
102 await expect(load(key)).rejects.toThrow("Offline again");
103 expect(await load(key)).toBe(SOURCE);
104 expect(fetch).toHaveBeenCalledTimes(5);
105 },
106);
107​
108it("shares an in-flight function request while one subscriber cancels", async () => {
109 const started = Promise.withResolvers<void>();
110 const response = Promise.withResolvers<NativeReviewDiffSource>();
111 let calls = 0;
112 const request = () => {
113 calls++;
114 started.resolve();
115 return response.promise;
116 };
117 const load = createNativeReviewDiffSourceLoader((sourceRequest: typeof request) =>
118 sourceRequest(),
119 );
120 const controller = new AbortController();
121 const first = load(request, controller.signal);
122 await started.promise;
123 const second = load(request);
124 controller.abort();
125 expect(await first).toBeNull();
126 response.resolve(SOURCE);
127 expect(await second).toBe(SOURCE);
128 expect(await load(request)).toBe(SOURCE);
129 expect(calls).toBe(1);
130});
131​
132it("serializes RPCs and drops cancelled queued files", async () => {
133 const started = Promise.withResolvers<void>();
134 const response = Promise.withResolvers<NativeReviewDiffSource>();
135 const fetch = vi.fn((file: string) => {
136 if (file !== "active.ts") return Promise.resolve(SOURCE);
137 started.resolve();
138 return response.promise;
139 });
140 const load = createNativeReviewDiffSourceLoader(fetch);
141 const active = load("active.ts");
142 await started.promise;
143 const controller = new AbortController();
144 const queued = load("cancelled.ts", controller.signal);
145 controller.abort();
146 expect(await queued).toBeNull();
147 expect(fetch.mock.calls).toEqual([["active.ts"]]);
148 response.resolve(SOURCE);
149 await active;
150 await load("next.ts");
151 expect(fetch.mock.calls).toEqual([["active.ts"], ["next.ts"]]);
152});
153​
154it("starts a shared fresh request for a file whose cancelled request was dropped", async () => {
155 const started = Promise.withResolvers<void>();
156 const response = Promise.withResolvers<NativeReviewDiffSource>();
157 const dropped = Promise.withResolvers<void>();
158 const replacementStarted = Promise.withResolvers<void>();
159 const replacementResponse = Promise.withResolvers<NativeReviewDiffSource>();
160 using _trace = vi.spyOn(DiffHighlightTrace, "traceDiffHighlight").mockImplementation((event) => {
161 if (event === "source-dropped") dropped.resolve();
162 });
163 const fetch = vi.fn((file: string) => {
164 if (file === "active.ts") {
165 started.resolve();
166 return response.promise;
167 }
168 replacementStarted.resolve();
169 return replacementResponse.promise;
170 });
171 const load = createNativeReviewDiffSourceLoader(fetch);
172 const active = load("active.ts");
173 await started.promise;
174 const controller = new AbortController();
175 const queued = load("cancelled.ts", controller.signal);
176 controller.abort();
177 expect(await queued).toBeNull();
178 response.resolve(SOURCE);
179 await dropped.promise;
180 const fresh = load("cancelled.ts");
181 // Settling first means the fresh caller joined the dropped request instead of fetching.
182 await Promise.race([replacementStarted.promise, fresh]);
183 const shared = load("cancelled.ts");
184 replacementResponse.resolve(SOURCE);
185 expect(await fresh).toBe(SOURCE);
186 expect(await shared).toBe(SOURCE);
187 expect(await active).toBe(SOURCE);
188 expect(fetch.mock.calls).toEqual([["active.ts"], ["cancelled.ts"]]);
189});