authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-05 12:33:27-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-05 12:33:27-07:00
logf8b4c464b4760d73e0ece7e68011c738803d8b69
treef602b7de646088943fe02db386ed4ab1f2b39fe5
parentd92dca74eb7b7c6068619752f5b9a55c0e36f352
signature Signed by PGP key B5690EEEBB952194

test(web): cancel pending highlight fixture frames during cleanup (#10188)


1 files changed, 41 insertions(+), 6 deletions(-)

apps/web/src/components/files/fileEditorHighlight.test.ts+41-6
...@@ -64,6 +64,7 @@ let renderer: FileRenderer;...@@ -64,6 +64,7 @@ let renderer: FileRenderer;
64let tokenizer: Tokenizer;64let tokenizer: Tokenizer;
65let file: FileContents;65let file: FileContents;
66let document: TextDocument<unknown>;66let document: TextDocument<unknown>;
67const animationFrames = new Set<ReturnType<typeof setImmediate>>();
6768
68function nextResponse(): Promise<HeldResponse> {69function nextResponse(): Promise<HeldResponse> {
69 const response = responses.shift();70 const response = responses.shift();
...@@ -147,10 +148,18 @@ beforeEach(async () => {...@@ -147,10 +148,18 @@ beforeEach(async () => {
147 responses = [];148 responses = [];
148 responseWaiters = [];149 responseWaiters = [];
149 terminationPromises = [];150 terminationPromises = [];
150 vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) =>151 vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) => {
151 setImmediate(() => callback(0)),152 const frame = setImmediate(() => {
152 );153 animationFrames.delete(frame);
153 vi.stubGlobal("cancelAnimationFrame", clearImmediate);154 callback(0);
155 });
156 animationFrames.add(frame);
157 return frame;
158 });
159 vi.stubGlobal("cancelAnimationFrame", (frame: ReturnType<typeof setImmediate>) => {
160 animationFrames.delete(frame);
161 clearImmediate(frame);
162 });
154 vi.stubGlobal("window", { matchMedia: () => ({ matches: true }) });163 vi.stubGlobal("window", { matchMedia: () => ({ matches: true }) });
155 pool = new WorkerPoolManager(164 pool = new WorkerPoolManager(
156 // Adapt browser transport only; Pierre's real worker produces each response.165 // Adapt browser transport only; Pierre's real worker produces each response.
...@@ -176,15 +185,41 @@ beforeEach(async () => {...@@ -176,15 +185,41 @@ beforeEach(async () => {
176 renderContents();185 renderContents();
177});186});
178187
179afterEach(async () => {188async function cleanUpFixture() {
180 tokenizer?.cleanUp();189 tokenizer?.cleanUp();
181 renderer?.cleanUp();190 renderer?.cleanUp();
182 pool?.terminate();191 pool?.terminate();
183 await Promise.all(terminationPromises);192 await Promise.all(terminationPromises);
193 // Pool termination can queue a final broadcast after its workers have exited.
194 for (const frame of animationFrames) clearImmediate(frame);
195 animationFrames.clear();
184 vi.unstubAllGlobals();196 vi.unstubAllGlobals();
185});197}
198
199afterEach(cleanUpFixture);
186200
187describe("editable file highlighting", () => {201describe("editable file highlighting", () => {
202 it("cleans up an already terminated worker pool", async () => {
203 (await nextResponse()).deliver();
204 expect(pool.getStats().totalWorkers).toBe(1);
205 pool.terminate();
206 await Promise.all(terminationPromises);
207 expect(pool.getStats().totalWorkers).toBe(0);
208
209 const animationFrame = globalThis.requestAnimationFrame;
210 const cancelFrame = globalThis.cancelAnimationFrame;
211 const window = globalThis.window;
212 try {
213 await cleanUpFixture();
214 // Deliver the real Immediate queue after cleanup has removed the browser globals.
215 await new Promise<void>((resolve) => setImmediate(resolve));
216 } finally {
217 vi.stubGlobal("requestAnimationFrame", animationFrame);
218 vi.stubGlobal("cancelAnimationFrame", cancelFrame);
219 vi.stubGlobal("window", window);
220 }
221 });
222
188 it("still accepts an asynchronous highlight when the file has not changed", async () => {223 it("still accepts an asynchronous highlight when the file has not changed", async () => {
189 expect(renderContents()).not.toContain('style="color:');224 expect(renderContents()).not.toContain('style="color:');
190 (await nextResponse()).deliver();225 (await nextResponse()).deliver();