@uppy/utils
Version:
Shared utility functions for Uppy Core and plugins maintained by the Uppy team.
200 lines (199 loc) • 7.97 kB
JavaScript
import { describe, expect, it } from 'vitest';
import delay from './delay.js';
import { TaskQueue } from './TaskQueue.js';
describe('TaskQueue', () => {
it('runs queued tasks in FIFO order after resume', async () => {
const queue = new TaskQueue({ concurrency: 1 });
queue.pause();
const order = [];
const makeTask = (label) => async () => {
order.push(label);
await delay(5);
return label;
};
const first = queue.add(makeTask('first'));
const second = queue.add(makeTask('second'));
const third = queue.add(makeTask('third'));
expect(queue.pending).toBe(3);
queue.resume();
await Promise.all([first, second, third]);
expect(order).toEqual(['first', 'second', 'third']);
});
it('aborts a queued task without executing it', async () => {
const queue = new TaskQueue({ concurrency: 1 });
const runningBlocker = Promise.withResolvers();
const started = [];
const running = queue.add(async () => {
started.push('first');
await runningBlocker.promise;
});
const queued = queue.add(async () => {
started.push('second');
return 'second';
});
const reason = new Error('nope');
queued.abort(reason);
await expect(queued).rejects.toBe(reason);
runningBlocker.resolve();
await running;
expect(started).toEqual(['first']);
expect(queue.pending).toBe(0);
});
it('rejects a running task when aborted before it resolves', async () => {
const queue = new TaskQueue({ concurrency: 1 });
const deferred = Promise.withResolvers();
const promise = queue.add(async () => deferred.promise);
const reason = new Error('stop');
promise.abort(reason);
deferred.resolve('ok');
await expect(promise).rejects.toBe(reason);
});
it('clear rejects pending tasks but leaves running tasks alone', async () => {
const queue = new TaskQueue({ concurrency: 1 });
const runningDeferred = Promise.withResolvers();
const running = queue.add(async () => runningDeferred.promise);
const queued1 = queue.add(async () => 'queued1');
const queued2 = queue.add(async () => 'queued2');
const reason = new Error('cleared');
queue.clear(reason);
await expect(queued1).rejects.toBe(reason);
await expect(queued2).rejects.toBe(reason);
runningDeferred.resolve('ok');
await expect(running).resolves.toBe('ok');
expect(queue.pending).toBe(0);
});
it('wrapPromiseFunction queues work and preserves arguments', async () => {
const queue = new TaskQueue({ concurrency: 1 });
queue.pause();
const order = [];
const makeTask = (label) => async () => {
order.push(label);
return label;
};
const wrapped = queue.wrapPromiseFunction(async (value) => {
order.push(`wrapped:${value}`);
return value.toUpperCase();
});
const first = queue.add(makeTask('first'));
const wrappedPromise = wrapped('hello');
expect(typeof wrappedPromise.abort).toBe('function');
queue.resume();
await expect(first).resolves.toBe('first');
await expect(wrappedPromise).resolves.toBe('HELLO');
expect(order).toEqual(['first', 'wrapped:hello']);
});
it('updates concurrency via setter and starts additional tasks', async () => {
const queue = new TaskQueue({ concurrency: 1 });
const started1 = Promise.withResolvers();
const started2 = Promise.withResolvers();
const blocker1 = Promise.withResolvers();
const blocker2 = Promise.withResolvers();
const first = queue.add(async () => {
started1.resolve();
await blocker1.promise;
return 'first';
});
const second = queue.add(async () => {
started2.resolve();
await blocker2.promise;
return 'second';
});
await started1.promise;
expect(queue.running).toBe(1);
expect(queue.pending).toBe(1);
queue.concurrency = 2;
await started2.promise;
expect(queue.running).toBe(2);
expect(queue.pending).toBe(0);
blocker1.resolve();
blocker2.resolve();
await expect(first).resolves.toBe('first');
await expect(second).resolves.toBe('second');
});
it('aborts when abortOn signal fires while queued', async () => {
const queue = new TaskQueue({ concurrency: 1 });
queue.pause();
const controller = new AbortController();
const promise = queue.add(async () => 'ok');
const returned = promise.abortOn(controller.signal);
expect(returned).toBe(promise);
const reason = new Error('signal abort');
controller.abort(reason);
await expect(promise).rejects.toBe(reason);
expect(queue.pending).toBe(0);
});
it('runs tasks concurrently up to the concurrency limit', async () => {
const queue = new TaskQueue({ concurrency: 2 });
const started1 = Promise.withResolvers();
const started2 = Promise.withResolvers();
const started3 = Promise.withResolvers();
const blocker1 = Promise.withResolvers();
const blocker2 = Promise.withResolvers();
const blocker3 = Promise.withResolvers();
const first = queue.add(async () => {
started1.resolve();
await blocker1.promise;
return 'first';
});
const second = queue.add(async () => {
started2.resolve();
await blocker2.promise;
return 'second';
});
const third = queue.add(async () => {
started3.resolve();
await blocker3.promise;
return 'third';
});
await Promise.all([started1.promise, started2.promise]);
let thirdStarted = false;
started3.promise.then(() => {
thirdStarted = true;
});
await delay(1);
expect(thirdStarted).toBe(false);
expect(queue.running).toBe(2);
expect(queue.pending).toBe(1);
blocker1.resolve();
await started3.promise;
blocker2.resolve();
blocker3.resolve();
await expect(first).resolves.toBe('first');
await expect(second).resolves.toBe('second');
await expect(third).resolves.toBe('third');
});
it('continues processing after aborting a running task', async () => {
const queue = new TaskQueue({ concurrency: 1 });
const started1 = Promise.withResolvers();
const started2 = Promise.withResolvers();
const blocker1 = Promise.withResolvers();
const first = queue.add(async () => {
started1.resolve();
await blocker1.promise;
return 'first';
});
const second = queue.add(async () => {
started2.resolve();
return 'second';
});
await started1.promise;
const reason = new Error('abort running');
first.abort(reason);
blocker1.resolve();
await expect(first).rejects.toBe(reason);
await started2.promise;
await expect(second).resolves.toBe('second');
});
it('continues processing after a task throws synchronously', async () => {
const queue = new TaskQueue({ concurrency: 1 });
const reason = new Error('sync throw');
const first = queue.add(() => {
throw reason;
});
const second = queue.add(async () => 'second');
await expect(first).rejects.toBe(reason);
await expect(second).resolves.toBe('second');
expect(queue.running).toBe(0);
expect(queue.pending).toBe(0);
});
});