koa-lite/test/utils/compose.js

355 lines
7.2 KiB
JavaScript
Raw Permalink Normal View History

'use strict';
/* eslint-env jest */
const compose = require('../../lib/compose');
const assert = require('assert');
function wait(ms){
return new Promise(resolve => setTimeout(resolve, ms || 1));
}
function isPromise(x){
return x && typeof x.then === 'function';
}
describe('Koa Compose', () => {
it('should work', async() => {
const arr = [];
const stack = [];
stack.push(async(context, next) => {
arr.push(1);
await wait(1);
await next();
await wait(1);
arr.push(6);
});
stack.push(async(context, next) => {
arr.push(2);
await wait(1);
await next();
await wait(1);
arr.push(5);
});
stack.push(async(context, next) => {
arr.push(3);
await wait(1);
await next();
await wait(1);
arr.push(4);
});
await compose(stack)({});
assert.deepEqual(arr, [1, 2, 3, 4, 5, 6]);
});
it('should be able to be called twice', () => {
let stack = [];
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(() => {
assert.deepEqual(out, ctx1.arr);
return fn(ctx2);
}).then(() => {
assert.deepEqual(out, ctx2.arr);
});
});
it('should only accept an array', () => {
let err;
try {
compose();
throw new Error('should not be called');
} catch (e) {
err = e;
}
return assert(err instanceof TypeError);
});
it('should create next functions that return a Promise', () => {
const stack = [];
const arr = [];
for (let i = 0; i < 5; i++) {
stack.push((context, next) => {
arr.push(next());
});
}
compose(stack)({});
for (let next of arr) {
assert(isPromise(next), 'one of the functions next is not a Promise');
}
});
it('should work with 0 middleware', () => {
return compose([])({});
});
it('should only accept middleware as functions', () => {
let err;
try {
compose([{}]);
throw new Error('should not be called');
} catch (e) {
err = e;
}
return assert(err instanceof TypeError);
});
it('should work when yielding at the end of the stack', async() => {
let stack = [];
let called = false;
stack.push(async(ctx, next) => {
await next();
called = true;
});
await compose(stack)({});
assert(called);
});
it('should reject on errors in middleware', () => {
let stack = [];
stack.push(() => { throw new Error(); });
return compose(stack)({})
.then(() => {
throw new Error('promise was not rejected');
})
.catch(e => {
assert(e instanceof Error);
});
});
it('should work when yielding at the end of the stack with yield*', () => {
let stack = [];
stack.push(async(ctx, next) => {
await next;
});
return compose(stack)({});
});
it('should keep the context', () => {
const ctx = {};
const stack = [];
stack.push(async(ctx2, next) => {
await next();
assert.strictEqual(ctx2, ctx);
});
stack.push(async(ctx2, next) => {
await next();
assert.strictEqual(ctx2, ctx);
});
stack.push(async(ctx2, next) => {
await next();
assert.strictEqual(ctx2, ctx);
});
return compose(stack)(ctx);
});
it('should catch downstream errors', async() => {
const arr = [];
const stack = [];
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, next) => {
arr.push(4);
throw new Error();
});
await compose(stack)({});
assert.deepEqual(arr, [1, 6, 4, 2, 3]);
});
it('should compose w/ next', () => {
let called = false;
return compose([])({}, async() => {
called = true;
}).then(() => {
assert(called);
});
});
it('should handle errors in wrapped non-async functions', () => {
const stack = [];
stack.push(() => {
throw new Error();
});
return compose(stack)({}).then(() => {
throw new Error('promise was not rejected');
}).catch(e => {
assert(e instanceof Error);
});
});
// https://github.com/koajs/compose/pull/27#issuecomment-143109739
it('should compose w/ other compositions', () => {
let called = [];
return compose([
compose([
(ctx, next) => {
called.push(1);
return next();
},
(ctx, next) => {
called.push(2);
return next();
}
]),
(ctx, next) => {
called.push(3);
return next();
}
])({}).then(() => assert.deepEqual(called, [1, 2, 3]));
});
it('should throw if next() is called multiple times', () => {
return compose([
async(ctx, next) => {
await next();
await next();
}
])({}).then(() => {
throw new Error('boom');
}, err => {
assert(/multiple times/.test(err.message));
});
});
it('should return a valid middleware', () => {
let val = 0;
return compose([
compose([
(ctx, next) => {
val++;
return next();
},
(ctx, next) => {
val++;
return next();
}
]),
(ctx, next) => {
val++;
return next();
}
])({}).then(() => {
assert.strictEqual(val, 3);
});
});
it('should return last return value', () => {
const stack = [];
stack.push(async(context, next) => {
let val = await next();
assert.strictEqual(val, 2);
return 1;
});
stack.push(async(context, next) => {
const val = await next();
assert.strictEqual(val, 0);
return 2;
});
const next = () => 0;
return compose(stack)({}, next).then(val => {
assert.strictEqual(val, 1);
});
});
it('should not affect the original middleware array', () => {
const middleware = [];
const fn1 = (ctx, next) => {
return next();
};
middleware.push(fn1);
for (const fn of middleware) {
assert.equal(fn, fn1);
}
compose(middleware);
for (const fn of middleware) {
assert.equal(fn, fn1);
}
});
it('should not get stuck on the passed in next', () => {
const middleware = [(ctx, next) => {
ctx.middleware++;
return next();
}];
const ctx = {
middleware: 0,
next: 0
};
return compose(middleware)(ctx, (ctx, next) => {
ctx.next++;
return next();
}).then(() => {
assert.strictEqual(ctx.middleware, 1);
assert.strictEqual(ctx.next, 1);
});
});
});