mirror of
https://seed.radicle.dev/rad:z4D5UCArafTzTQpDZNQRuqswh3ury.git
synced 2026-10-24 14:00:36 +08:00
Covers the pure modules the rest of the app builds on, starting with the cheapest layer of the testing strategy.
121 lines
2.9 KiB
TypeScript
121 lines
2.9 KiB
TypeScript
import { describe, expect, test, vi } from "vitest";
|
||
|
||
import * as mutexExecutor from "@app/lib/mutexExecutor";
|
||
import { sleep } from "@app/lib/sleep";
|
||
|
||
describe("executor", () => {
|
||
test("cancels running task", async () => {
|
||
const e = mutexExecutor.create();
|
||
|
||
const first = e.run(async () => {
|
||
await sleep(10);
|
||
return "first";
|
||
});
|
||
const second = e.run(async () => {
|
||
return "second";
|
||
});
|
||
|
||
expect(await first).toBe(undefined);
|
||
expect(await second).toBe("second");
|
||
|
||
const third = e.run(async () => {
|
||
await sleep(10);
|
||
return "third";
|
||
});
|
||
const fourth = e.run(async () => {
|
||
return "fourth";
|
||
});
|
||
|
||
expect(await third).toBe(undefined);
|
||
expect(await fourth).toBe("fourth");
|
||
});
|
||
|
||
test("cancels multiple tasks", async () => {
|
||
const e = mutexExecutor.create();
|
||
|
||
const canceled1 = e.run(async () => {
|
||
await sleep(10);
|
||
return true;
|
||
});
|
||
const canceled2 = e.run(async () => {
|
||
await sleep(10);
|
||
return true;
|
||
});
|
||
const canceled3 = e.run(async () => {
|
||
await sleep(10);
|
||
return true;
|
||
});
|
||
const last = e.run(async () => {
|
||
return true;
|
||
});
|
||
|
||
expect(await canceled1).toBe(undefined);
|
||
expect(await canceled2).toBe(undefined);
|
||
expect(await canceled3).toBe(undefined);
|
||
expect(await last).toBe(true);
|
||
});
|
||
|
||
test("triggers abort signal event", async () => {
|
||
const e = mutexExecutor.create();
|
||
const abortListener = vi.fn();
|
||
|
||
void e.run(async abort => {
|
||
abort.addEventListener("abort", abortListener);
|
||
await sleep(10);
|
||
return "first";
|
||
});
|
||
expect(abortListener).not.toHaveBeenCalled();
|
||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||
void e.run(async () => {});
|
||
expect(abortListener).toHaveBeenCalled();
|
||
});
|
||
|
||
test("don’t throw error on aborted task", async () => {
|
||
const e = mutexExecutor.create();
|
||
|
||
const first = e.run(async () => {
|
||
await sleep(10);
|
||
throw new Error();
|
||
});
|
||
const second = e.run(async () => {
|
||
return "second";
|
||
});
|
||
|
||
expect(await first).toBe(undefined);
|
||
expect(await second).toBe("second");
|
||
});
|
||
});
|
||
|
||
describe("worker", () => {
|
||
test("sequential work", async () => {
|
||
const w = mutexExecutor.createWorker(async (value: number) => {
|
||
await sleep(10);
|
||
return value;
|
||
});
|
||
|
||
const outputs: number[] = [];
|
||
w.output.onValue(value => outputs.push(value));
|
||
|
||
await w.submit(1);
|
||
await w.submit(2);
|
||
await w.submit(3);
|
||
|
||
expect(outputs).toEqual([1, 2, 3]);
|
||
});
|
||
|
||
test("overlapping work cancels", async () => {
|
||
const w = mutexExecutor.createWorker(async (value: number) => {
|
||
await sleep(10);
|
||
return value;
|
||
});
|
||
|
||
const nextOutput = w.output.firstToPromise();
|
||
|
||
void w.submit(1);
|
||
void w.submit(2);
|
||
void w.submit(3);
|
||
|
||
expect(await nextOutput).toEqual(3);
|
||
});
|
||
});
|