UNPKG

onion-compose

Version:

Onion style function composition just like koa-compose, but with any number of arguments

372 lines (294 loc) 7.96 kB
/* eslint-env jest */ /* NOTE: These are original tests from from koa-compose updated to use Jest, await syntax and TypeScirpt */ import compose, { type NextFn } from '../index' function wait(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms || 1)) } function isPromise(x: any) { return x && typeof x.then === 'function' } describe('Onion compose', function () { it('should work', async () => { const arr: number[] = [] const stack: NextFn<[ctx: Record<string, any>]>[] = [] void stack.push(async (_ctx, next) => { arr.push(1) await wait(1) await next() await wait(1) arr.push(6) }) stack.push(async (_ctx, next) => { arr.push(2) await wait(1) await next() await wait(1) arr.push(5) }) stack.push(async (_ctx, next) => { arr.push(3) await wait(1) await next() await wait(1) arr.push(4) }) await compose(stack)([{}]) expect(arr).toEqual(expect.arrayContaining([1, 2, 3, 4, 5, 6])) }) it('should work with synchronous', async () => { const arr: number[] = [] const stack: NextFn<[ctx: Record<string, any>]>[] = [] void stack.push((_ctx, next) => { arr.push(1) next() arr.push(6) }) stack.push((_ctx, next) => { arr.push(2) next() arr.push(5) }) stack.push((_ctx, next) => { arr.push(3) next() arr.push(4) }) await compose(stack)([{}]) expect(arr).toEqual(expect.arrayContaining([1, 2, 3, 4, 5, 6])) }) it('should be able to be called twice', () => { const stack: NextFn<[ctx: { arr: number[] }]>[] = [] stack.push(async (context, next) => { context.arr.push(1) await wait(1) await next() await wait(1) context.arr.push(6) }) stack.push(async (context, next) => { context.arr.push(2) await wait(1) await next() await wait(1) context.arr.push(5) }) stack.push(async (context, next) => { context.arr.push(3) await wait(1) await next() await wait(1) context.arr.push(4) }) const fn = compose(stack) const ctx1 = { arr: [] } const ctx2 = { arr: [] } const out = [1, 2, 3, 4, 5, 6] return fn([ctx1]) .then(() => { expect(out).toEqual(ctx1.arr) return fn([ctx2]) }) .then(() => { expect(out).toEqual(ctx2.arr) }) }) it('should only accept an array', () => { // @ts-expect-error - error on purpose expect(() => compose()).toThrow(TypeError) }) it('should create next functions that return a Promise', function () { const stack: NextFn<[Record<string, any>]>[] = [] const arr: unknown[] = [] for (let i = 0; i < 5; i++) { stack.push((_context, next) => { arr.push(next()) }) } compose(stack)([{}]) for (const next of arr) { expect(isPromise(next)).toBe(true) } }) it('should work with 0 middleware', function () { expect(() => compose([])([{}])).not.toThrow() }) it('should only accept middleware as functions', () => { // @ts-expect-error type mismatch expect(() => compose([{}])).toThrow(TypeError) }) it('should work when yielding at the end of the stack', async () => { const stack: NextFn<[ctx: any]>[] = [] let called = false stack.push(async (_ctx, next) => { await next() called = true }) await compose(stack)([{}]) expect(called).toBe(true) }) it('should reject on errors in middleware', async () => { const stack: NextFn<[Record<string, any>]>[] = [] stack.push(() => { throw new Error() }) await expect(compose(stack)([{}])).rejects.toBeInstanceOf(Error) }) it('should keep the context', () => { const ctx = {} const stack: NextFn<[Record<string, any>]>[] = [] stack.push(async (ctx2, next) => { await next() expect(ctx2).toEqual(ctx) }) }) it('should keep all the arguments', async () => { const arg1 = {} const arg2 = {} const arg3 = {} const stack: NextFn< [ p1: Record<string, any>, p2: Record<string, any>, p3: Record<string, any> ] >[] = [] stack.push(async (p1, p2, p3, next) => { await next() expect(p1).toEqual(arg1) expect(p2).toEqual(arg2) expect(p3).toEqual(arg3) return [p1, p2, p3] }) stack.push(async (p1, p2, p3, next) => { await next() expect(p1).toEqual(arg1) expect(p2).toEqual(arg2) expect(p3).toEqual(arg3) }) stack.push(async (p1, p2, p3, next) => { await next() expect(p1).toEqual(arg1) expect(p2).toEqual(arg2) expect(p3).toEqual(arg3) }) const [p1Result, p2Result, p3Result] = await compose(stack)([ arg1, arg2, arg3 ]) expect(p1Result).toBe(arg1) expect(p2Result).toBe(arg2) expect(p3Result).toBe(arg3) }) it('should catch downstream errors', async () => { const arr: number[] = [] const stack: NextFn<[ctx: any]>[] = [] stack.push(async (_ctx, next) => { arr.push(1) try { arr.push(6) await next() arr.push(7) } catch (err) { arr.push(2) } arr.push(3) }) stack.push(async (_ctx) => { arr.push(4) throw new Error() }) await compose(stack)([{}]) expect(arr).toEqual([1, 6, 4, 2, 3]) }) it('should compose w/ next', async () => { let called = false const next = async () => { called = true } const run = compose([]) await run([1, 3], next) expect(called).toBe(true) }) it('should handle errors in wrapped non-async functions', async () => { const stack: NextFn<[ctx: Record<string, any>]>[] = [] stack.push(function () { throw new Error() }) await expect(compose(stack)([{}])).rejects.toBeInstanceOf(Error) }) it('should throw if next() is called multiple times', async () => { const fn = async (_ctx: any, next: () => Promise<void> | void) => { await next() await next() } expect.assertions(1) try { await compose<[ctx: any]>([fn])([{}]) } catch (e) { /* eslint-disable jest/no-conditional-expect */ expect((e as Error).message).toMatch(/multiple times/) } }) it('should return last return value', async () => { const stack: NextFn< [ctx: number], () => Promise<number>, Promise<number> >[] = [] stack.push(async (_ctx, next) => { const val = await next() expect(val).toEqual(2) return 1 }) stack.push(async (_ctx, next) => { const val = await next() expect(val).toEqual(0) return 2 }) const next: NextFn< [ctx: any], () => Promise<number>, Promise<number> > = async (_ctx: any) => 0 const result = await compose<[ctx: number], Promise<number>>(stack)( [2], next ) expect(result).toEqual(1) }) it('should not affect the original middleware array', () => { const middleware: NextFn<[ctx: string]>[] = [] const fn1: NextFn<[ctx: string]> = (_ctx, next) => { return next() } middleware.push(fn1) for (const fn of middleware) { expect(fn).toBe(fn1) } compose(middleware) for (const fn of middleware) { expect(fn).toBe(fn1) } }) it('should not get stuck on the passed in next', async () => { const ctx = { middleware: 0, next: 0 } const middleware = (ctx: { middleware: number }, next: () => unknown) => { ctx.middleware++ return next() } await compose<[ctx: { middleware: number; next: number }]>([middleware])( [ctx], (ctx, next) => { ctx.next++ return next() } ) expect(ctx).toEqual({ middleware: 1, next: 1 }) }) })