Radish alpha
r
rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5
Radicle web interface
Radicle
Git
radicle-explorer tests unit mutexExecutor.test.ts
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);
  });
});