mutexExecutor.test.ts
1 import { describe, expect, test, vi } from "vitest"; 2 3 import * as mutexExecutor from "@app/lib/mutexExecutor"; 4 import { sleep } from "@app/lib/sleep"; 5 6 describe("executor", () => { 7 test("cancels running task", async () => { 8 const e = mutexExecutor.create(); 9 10 const first = e.run(async () => { 11 await sleep(10); 12 return "first"; 13 }); 14 const second = e.run(async () => { 15 return "second"; 16 }); 17 18 expect(await first).toBe(undefined); 19 expect(await second).toBe("second"); 20 21 const third = e.run(async () => { 22 await sleep(10); 23 return "third"; 24 }); 25 const fourth = e.run(async () => { 26 return "fourth"; 27 }); 28 29 expect(await third).toBe(undefined); 30 expect(await fourth).toBe("fourth"); 31 }); 32 33 test("cancels multiple tasks", async () => { 34 const e = mutexExecutor.create(); 35 36 const canceled1 = e.run(async () => { 37 await sleep(10); 38 return true; 39 }); 40 const canceled2 = e.run(async () => { 41 await sleep(10); 42 return true; 43 }); 44 const canceled3 = e.run(async () => { 45 await sleep(10); 46 return true; 47 }); 48 const last = e.run(async () => { 49 return true; 50 }); 51 52 expect(await canceled1).toBe(undefined); 53 expect(await canceled2).toBe(undefined); 54 expect(await canceled3).toBe(undefined); 55 expect(await last).toBe(true); 56 }); 57 58 test("triggers abort signal event", async () => { 59 const e = mutexExecutor.create(); 60 const abortListener = vi.fn(); 61 62 void e.run(async abort => { 63 abort.addEventListener("abort", abortListener); 64 await sleep(10); 65 return "first"; 66 }); 67 expect(abortListener).not.toHaveBeenCalled(); 68 // eslint-disable-next-line @typescript-eslint/no-empty-function 69 void e.run(async () => {}); 70 expect(abortListener).toHaveBeenCalled(); 71 }); 72 73 test("don’t throw error on aborted task", async () => { 74 const e = mutexExecutor.create(); 75 76 const first = e.run(async () => { 77 await sleep(10); 78 throw new Error(); 79 }); 80 const second = e.run(async () => { 81 return "second"; 82 }); 83 84 expect(await first).toBe(undefined); 85 expect(await second).toBe("second"); 86 }); 87 }); 88 89 describe("worker", () => { 90 test("sequential work", async () => { 91 const w = mutexExecutor.createWorker(async (value: number) => { 92 await sleep(10); 93 return value; 94 }); 95 96 const outputs: number[] = []; 97 w.output.onValue(value => outputs.push(value)); 98 99 await w.submit(1); 100 await w.submit(2); 101 await w.submit(3); 102 103 expect(outputs).toEqual([1, 2, 3]); 104 }); 105 106 test("overlapping work cancels", async () => { 107 const w = mutexExecutor.createWorker(async (value: number) => { 108 await sleep(10); 109 return value; 110 }); 111 112 const nextOutput = w.output.firstToPromise(); 113 114 void w.submit(1); 115 void w.submit(2); 116 void w.submit(3); 117 118 expect(await nextOutput).toEqual(3); 119 }); 120 });