UNPKG

@uppy/utils

Version:

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

255 lines (199 loc) 7.2 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: string[] = [] const makeTask = (label: string) => 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<void>() const started: string[] = [] 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<string>() 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<string>() 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: string[] = [] const makeTask = (label: string) => async () => { order.push(label) return label } const wrapped = queue.wrapPromiseFunction(async (value: string) => { 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<void>() const started2 = Promise.withResolvers<void>() const blocker1 = Promise.withResolvers<void>() const blocker2 = Promise.withResolvers<void>() 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<void>() const started2 = Promise.withResolvers<void>() const started3 = Promise.withResolvers<void>() const blocker1 = Promise.withResolvers<void>() const blocker2 = Promise.withResolvers<void>() const blocker3 = Promise.withResolvers<void>() 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<void>() const started2 = Promise.withResolvers<void>() const blocker1 = Promise.withResolvers<void>() 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) }) })