UNPKG

@uppy/utils

Version:

Shared utility functions for Uppy Core and plugins maintained by the Uppy team.

200 lines (199 loc) 7.97 kB
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); }); });