apple2js/test/components/util/promises.spec.ts

107 lines
3.6 KiB
TypeScript
Raw Normal View History

/** @jest-environment jsdom */
import { Ready, spawn } from 'js/components/util/promises';
describe('promises', () => {
describe('spawn', () => {
it('returns an AbortController', () => {
const controller = spawn(() => Promise.resolve(1));
expect(controller).not.toBeNull();
expect(controller).toBeInstanceOf(AbortController);
});
it('passes an AbortSignal to the target function', () => {
let signalCapture: AbortSignal | null = null;
spawn((signal) => {
signalCapture = signal;
return Promise.resolve(1);
});
expect(signalCapture).not.toBeNull();
expect(signalCapture).toBeInstanceOf(AbortSignal);
});
it('has the controller hooked up to the signal', async () => {
let isAborted = false;
const controllerIsAborted = new Ready();
const spawnHasRecorded = new Ready();
const controller = spawn(async (signal) => {
await controllerIsAborted.ready;
isAborted = signal.aborted;
spawnHasRecorded.onReady();
});
controller.abort();
controllerIsAborted.onReady();
await spawnHasRecorded.ready;
expect(isAborted).toBe(true);
});
it('allows long-running tasks to be stopped', async () => {
let isFinished = false;
const innerReady = new Ready();
const innerFinished = new Ready();
const controller = spawn(async (signal) => {
innerReady.onReady();
let i = 0;
while (!signal.aborted) {
i++;
await tick();
}
expect(i).toBe(2);
isFinished = true;
innerFinished.onReady();
});
await innerReady.ready;
await tick();
controller.abort();
await innerFinished.ready;
expect(isFinished).toBe(true);
});
it('allows nesting via listeners', async () => {
let isInnerAborted = false;
const innerReady = new Ready();
const outerReady = new Ready();
const abortRecorded = new Ready();
const controller = spawn(async (signal) => {
const innerController = spawn(async (innerSignal) => {
await innerReady.ready;
isInnerAborted = innerSignal.aborted;
abortRecorded.onReady();
});
// TODO(flan): Chain signal.reason when calling innerController.abort()
// once jsdom 19 has wider adoption (currently on 16.6.0). Likewise there
// are some subtle problems with signal.addEventListener that should be
// addressed by https://github.com/jsdom/jsdom/pull/3347.
// signal.addEventListener('abort', () => innerController.abort(signal.reason));
signal.addEventListener('abort', () => innerController.abort());
await outerReady.ready;
});
// Abort the outer controller, but don't let the outer block run.
controller.abort();
// Let the inner block run.
innerReady.onReady();
await abortRecorded.ready;
// Inner block is aborted.
expect(isInnerAborted).toBe(true);
// Let outer block finish.
outerReady.onReady();
});
});
});
function tick() {
return new Promise(resolve => setTimeout(resolve, 0));
}