| ... | ... | @@ -64,6 +64,7 @@ let renderer: FileRenderer; |
| 64 | 64 | let tokenizer: Tokenizer; |
| 65 | 65 | let file: FileContents; |
| 66 | 66 | let document: TextDocument<unknown>; |
| 67 | const animationFrames = new Set<ReturnType<typeof setImmediate>>(); |
| 67 | 68 | |
| 68 | 69 | function nextResponse(): Promise<HeldResponse> { |
| 69 | 70 | const response = responses.shift(); |
| ... | ... | @@ -147,10 +148,18 @@ beforeEach(async () => { |
| 147 | 148 | responses = []; |
| 148 | 149 | responseWaiters = []; |
| 149 | 150 | terminationPromises = []; |
| 150 | | vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) => |
| 151 | | setImmediate(() => callback(0)), |
| 152 | | ); |
| 153 | | vi.stubGlobal("cancelAnimationFrame", clearImmediate); |
| 151 | vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) => { |
| 152 | const frame = setImmediate(() => { |
| 153 | animationFrames.delete(frame); |
| 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 | 163 | vi.stubGlobal("window", { matchMedia: () => ({ matches: true }) }); |
| 155 | 164 | pool = new WorkerPoolManager( |
| 156 | 165 | // Adapt browser transport only; Pierre's real worker produces each response. |
| ... | ... | @@ -176,15 +185,41 @@ beforeEach(async () => { |
| 176 | 185 | renderContents(); |
| 177 | 186 | }); |
| 178 | 187 | |
| 179 | | afterEach(async () => { |
| 188 | async function cleanUpFixture() { |
| 180 | 189 | tokenizer?.cleanUp(); |
| 181 | 190 | renderer?.cleanUp(); |
| 182 | 191 | pool?.terminate(); |
| 183 | 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 | 196 | vi.unstubAllGlobals(); |
| 185 | | }); |
| 197 | } |
| 198 | |
| 199 | afterEach(cleanUpFixture); |
| 186 | 200 | |
| 187 | 201 | describe("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 | 223 | it("still accepts an asynchronous highlight when the file has not changed", async () => { |
| 189 | 224 | expect(renderContents()).not.toContain('style="color:'); |
| 190 | 225 | (await nextResponse()).deliver(); |