You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
427 lines
10 KiB
427 lines
10 KiB
2 months ago
|
var assert = require('assert');
|
||
|
var Kareem = require('../');
|
||
|
|
||
|
/* Much like [hooks](https://npmjs.org/package/hooks), kareem lets you define
|
||
|
* pre and post hooks: pre hooks are called before a given function executes.
|
||
|
* Unlike hooks, kareem stores hooks and other internal state in a separate
|
||
|
* object, rather than relying on inheritance. Furthermore, kareem exposes
|
||
|
* an `execPre()` function that allows you to execute your pre hooks when
|
||
|
* appropriate, giving you more fine-grained control over your function hooks.
|
||
|
*/
|
||
|
describe('pre hooks', function() {
|
||
|
var hooks;
|
||
|
|
||
|
beforeEach(function() {
|
||
|
hooks = new Kareem();
|
||
|
});
|
||
|
|
||
|
it('runs without any hooks specified', function(done) {
|
||
|
hooks.execPre('cook', null, function() {
|
||
|
// ...
|
||
|
// acquit:ignore:start
|
||
|
done();
|
||
|
// acquit:ignore:end
|
||
|
});
|
||
|
});
|
||
|
|
||
|
/* pre hook functions take one parameter, a "done" function that you execute
|
||
|
* when your pre hook is finished.
|
||
|
*/
|
||
|
it('runs basic serial pre hooks', function(done) {
|
||
|
var count = 0;
|
||
|
|
||
|
hooks.pre('cook', function(done) {
|
||
|
++count;
|
||
|
done();
|
||
|
});
|
||
|
|
||
|
hooks.execPre('cook', null, function() {
|
||
|
assert.equal(1, count);
|
||
|
// acquit:ignore:start
|
||
|
done();
|
||
|
// acquit:ignore:end
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('can run multipe pre hooks', function(done) {
|
||
|
var count1 = 0;
|
||
|
var count2 = 0;
|
||
|
|
||
|
hooks.pre('cook', function(done) {
|
||
|
++count1;
|
||
|
done();
|
||
|
});
|
||
|
|
||
|
hooks.pre('cook', function(done) {
|
||
|
++count2;
|
||
|
done();
|
||
|
});
|
||
|
|
||
|
hooks.execPre('cook', null, function() {
|
||
|
assert.equal(1, count1);
|
||
|
assert.equal(1, count2);
|
||
|
// acquit:ignore:start
|
||
|
done();
|
||
|
// acquit:ignore:end
|
||
|
});
|
||
|
});
|
||
|
|
||
|
/* If your pre hook function takes no parameters, its assumed to be
|
||
|
* fully synchronous.
|
||
|
*/
|
||
|
it('can run fully synchronous pre hooks', function(done) {
|
||
|
var count1 = 0;
|
||
|
var count2 = 0;
|
||
|
|
||
|
hooks.pre('cook', function() {
|
||
|
++count1;
|
||
|
});
|
||
|
|
||
|
hooks.pre('cook', function() {
|
||
|
++count2;
|
||
|
});
|
||
|
|
||
|
hooks.execPre('cook', null, function(error) {
|
||
|
assert.equal(null, error);
|
||
|
assert.equal(1, count1);
|
||
|
assert.equal(1, count2);
|
||
|
// acquit:ignore:start
|
||
|
done();
|
||
|
// acquit:ignore:end
|
||
|
});
|
||
|
});
|
||
|
|
||
|
/* Pre save hook functions are bound to the second parameter to `execPre()`
|
||
|
*/
|
||
|
it('properly attaches context to pre hooks', function(done) {
|
||
|
hooks.pre('cook', function(done) {
|
||
|
this.bacon = 3;
|
||
|
done();
|
||
|
});
|
||
|
|
||
|
hooks.pre('cook', function(done) {
|
||
|
this.eggs = 4;
|
||
|
done();
|
||
|
});
|
||
|
|
||
|
var obj = { bacon: 0, eggs: 0 };
|
||
|
|
||
|
// In the pre hooks, `this` will refer to `obj`
|
||
|
hooks.execPre('cook', obj, function(error) {
|
||
|
assert.equal(null, error);
|
||
|
assert.equal(3, obj.bacon);
|
||
|
assert.equal(4, obj.eggs);
|
||
|
// acquit:ignore:start
|
||
|
done();
|
||
|
// acquit:ignore:end
|
||
|
});
|
||
|
});
|
||
|
|
||
|
/* Like the hooks module, you can declare "async" pre hooks - these take two
|
||
|
* parameters, the functions `next()` and `done()`. `next()` passes control to
|
||
|
* the next pre hook, but the underlying function won't be called until all
|
||
|
* async pre hooks have called `done()`.
|
||
|
*/
|
||
|
it('can execute parallel (async) pre hooks', function(done) {
|
||
|
hooks.pre('cook', true, function(next, done) {
|
||
|
this.bacon = 3;
|
||
|
next();
|
||
|
setTimeout(function() {
|
||
|
done();
|
||
|
}, 5);
|
||
|
});
|
||
|
|
||
|
hooks.pre('cook', true, function(next, done) {
|
||
|
next();
|
||
|
var _this = this;
|
||
|
setTimeout(function() {
|
||
|
_this.eggs = 4;
|
||
|
done();
|
||
|
}, 10);
|
||
|
});
|
||
|
|
||
|
hooks.pre('cook', function(next) {
|
||
|
this.waffles = false;
|
||
|
next();
|
||
|
});
|
||
|
|
||
|
var obj = { bacon: 0, eggs: 0 };
|
||
|
|
||
|
hooks.execPre('cook', obj, function() {
|
||
|
assert.equal(3, obj.bacon);
|
||
|
assert.equal(4, obj.eggs);
|
||
|
assert.equal(false, obj.waffles);
|
||
|
// acquit:ignore:start
|
||
|
done();
|
||
|
// acquit:ignore:end
|
||
|
});
|
||
|
});
|
||
|
|
||
|
/* You can also return a promise from your pre hooks instead of calling
|
||
|
* `next()`. When the returned promise resolves, kareem will kick off the
|
||
|
* next middleware.
|
||
|
*/
|
||
|
it('supports returning a promise', function(done) {
|
||
|
hooks.pre('cook', function() {
|
||
|
return new Promise(resolve => {
|
||
|
setTimeout(() => {
|
||
|
this.bacon = 3;
|
||
|
resolve();
|
||
|
}, 100);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
var obj = { bacon: 0 };
|
||
|
|
||
|
hooks.execPre('cook', obj, function() {
|
||
|
assert.equal(3, obj.bacon);
|
||
|
// acquit:ignore:start
|
||
|
done();
|
||
|
// acquit:ignore:end
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('post hooks', function() {
|
||
|
var hooks;
|
||
|
|
||
|
beforeEach(function() {
|
||
|
hooks = new Kareem();
|
||
|
});
|
||
|
|
||
|
it('runs without any hooks specified', function(done) {
|
||
|
hooks.execPost('cook', null, [1], function(error, eggs) {
|
||
|
assert.ifError(error);
|
||
|
assert.equal(1, eggs);
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('executes with parameters passed in', function(done) {
|
||
|
hooks.post('cook', function(eggs, bacon, callback) {
|
||
|
assert.equal(1, eggs);
|
||
|
assert.equal(2, bacon);
|
||
|
callback();
|
||
|
});
|
||
|
|
||
|
hooks.execPost('cook', null, [1, 2], function(error, eggs, bacon) {
|
||
|
assert.ifError(error);
|
||
|
assert.equal(1, eggs);
|
||
|
assert.equal(2, bacon);
|
||
|
// acquit:ignore:start
|
||
|
done();
|
||
|
// acquit:ignore:end
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('can use synchronous post hooks', function(done) {
|
||
|
var execed = {};
|
||
|
|
||
|
hooks.post('cook', function(eggs, bacon) {
|
||
|
execed.first = true;
|
||
|
assert.equal(1, eggs);
|
||
|
assert.equal(2, bacon);
|
||
|
});
|
||
|
|
||
|
hooks.post('cook', function(eggs, bacon, callback) {
|
||
|
execed.second = true;
|
||
|
assert.equal(1, eggs);
|
||
|
assert.equal(2, bacon);
|
||
|
callback();
|
||
|
});
|
||
|
|
||
|
hooks.execPost('cook', null, [1, 2], function(error, eggs, bacon) {
|
||
|
assert.ifError(error);
|
||
|
assert.equal(2, Object.keys(execed).length);
|
||
|
assert.ok(execed.first);
|
||
|
assert.ok(execed.second);
|
||
|
assert.equal(1, eggs);
|
||
|
assert.equal(2, bacon);
|
||
|
// acquit:ignore:start
|
||
|
done();
|
||
|
// acquit:ignore:end
|
||
|
});
|
||
|
});
|
||
|
|
||
|
/* You can also return a promise from your post hooks instead of calling
|
||
|
* `next()`. When the returned promise resolves, kareem will kick off the
|
||
|
* next middleware.
|
||
|
*/
|
||
|
it('supports returning a promise', function(done) {
|
||
|
hooks.post('cook', function(bacon) {
|
||
|
return new Promise(resolve => {
|
||
|
setTimeout(() => {
|
||
|
this.bacon = 3;
|
||
|
resolve();
|
||
|
}, 100);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
var obj = { bacon: 0 };
|
||
|
|
||
|
hooks.execPost('cook', obj, obj, function() {
|
||
|
assert.equal(obj.bacon, 3);
|
||
|
// acquit:ignore:start
|
||
|
done();
|
||
|
// acquit:ignore:end
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('wrap()', function() {
|
||
|
var hooks;
|
||
|
|
||
|
beforeEach(function() {
|
||
|
hooks = new Kareem();
|
||
|
});
|
||
|
|
||
|
it('wraps pre and post calls into one call', function(done) {
|
||
|
hooks.pre('cook', true, function(next, done) {
|
||
|
this.bacon = 3;
|
||
|
next();
|
||
|
setTimeout(function() {
|
||
|
done();
|
||
|
}, 5);
|
||
|
});
|
||
|
|
||
|
hooks.pre('cook', true, function(next, done) {
|
||
|
next();
|
||
|
var _this = this;
|
||
|
setTimeout(function() {
|
||
|
_this.eggs = 4;
|
||
|
done();
|
||
|
}, 10);
|
||
|
});
|
||
|
|
||
|
hooks.pre('cook', function(next) {
|
||
|
this.waffles = false;
|
||
|
next();
|
||
|
});
|
||
|
|
||
|
hooks.post('cook', function(obj) {
|
||
|
obj.tofu = 'no';
|
||
|
});
|
||
|
|
||
|
var obj = { bacon: 0, eggs: 0 };
|
||
|
|
||
|
var args = [obj];
|
||
|
args.push(function(error, result) {
|
||
|
assert.ifError(error);
|
||
|
assert.equal(null, error);
|
||
|
assert.equal(3, obj.bacon);
|
||
|
assert.equal(4, obj.eggs);
|
||
|
assert.equal(false, obj.waffles);
|
||
|
assert.equal('no', obj.tofu);
|
||
|
|
||
|
assert.equal(obj, result);
|
||
|
// acquit:ignore:start
|
||
|
done();
|
||
|
// acquit:ignore:end
|
||
|
});
|
||
|
|
||
|
hooks.wrap(
|
||
|
'cook',
|
||
|
function(o, callback) {
|
||
|
assert.equal(3, obj.bacon);
|
||
|
assert.equal(4, obj.eggs);
|
||
|
assert.equal(false, obj.waffles);
|
||
|
assert.equal(undefined, obj.tofu);
|
||
|
callback(null, o);
|
||
|
},
|
||
|
obj,
|
||
|
args);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('createWrapper()', function() {
|
||
|
var hooks;
|
||
|
|
||
|
beforeEach(function() {
|
||
|
hooks = new Kareem();
|
||
|
});
|
||
|
|
||
|
it('wraps wrap() into a callable function', function(done) {
|
||
|
hooks.pre('cook', true, function(next, done) {
|
||
|
this.bacon = 3;
|
||
|
next();
|
||
|
setTimeout(function() {
|
||
|
done();
|
||
|
}, 5);
|
||
|
});
|
||
|
|
||
|
hooks.pre('cook', true, function(next, done) {
|
||
|
next();
|
||
|
var _this = this;
|
||
|
setTimeout(function() {
|
||
|
_this.eggs = 4;
|
||
|
done();
|
||
|
}, 10);
|
||
|
});
|
||
|
|
||
|
hooks.pre('cook', function(next) {
|
||
|
this.waffles = false;
|
||
|
next();
|
||
|
});
|
||
|
|
||
|
hooks.post('cook', function(obj) {
|
||
|
obj.tofu = 'no';
|
||
|
});
|
||
|
|
||
|
var obj = { bacon: 0, eggs: 0 };
|
||
|
|
||
|
var cook = hooks.createWrapper(
|
||
|
'cook',
|
||
|
function(o, callback) {
|
||
|
assert.equal(3, obj.bacon);
|
||
|
assert.equal(4, obj.eggs);
|
||
|
assert.equal(false, obj.waffles);
|
||
|
assert.equal(undefined, obj.tofu);
|
||
|
callback(null, o);
|
||
|
},
|
||
|
obj);
|
||
|
|
||
|
cook(obj, function(error, result) {
|
||
|
assert.ifError(error);
|
||
|
assert.equal(3, obj.bacon);
|
||
|
assert.equal(4, obj.eggs);
|
||
|
assert.equal(false, obj.waffles);
|
||
|
assert.equal('no', obj.tofu);
|
||
|
|
||
|
assert.equal(obj, result);
|
||
|
// acquit:ignore:start
|
||
|
done();
|
||
|
// acquit:ignore:end
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('clone()', function() {
|
||
|
it('clones a Kareem object', function() {
|
||
|
var k1 = new Kareem();
|
||
|
k1.pre('cook', function() {});
|
||
|
k1.post('cook', function() {});
|
||
|
|
||
|
var k2 = k1.clone();
|
||
|
assert.deepEqual(Array.from(k2._pres.keys()), ['cook']);
|
||
|
assert.deepEqual(Array.from(k2._posts.keys()), ['cook']);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('merge()', function() {
|
||
|
it('pulls hooks from another Kareem object', function() {
|
||
|
var k1 = new Kareem();
|
||
|
var test1 = function() {};
|
||
|
k1.pre('cook', test1);
|
||
|
k1.post('cook', function() {});
|
||
|
|
||
|
var k2 = new Kareem();
|
||
|
var test2 = function() {};
|
||
|
k2.pre('cook', test2);
|
||
|
var k3 = k2.merge(k1);
|
||
|
assert.equal(k3._pres.get('cook').length, 2);
|
||
|
assert.equal(k3._pres.get('cook')[0].fn, test2);
|
||
|
assert.equal(k3._pres.get('cook')[1].fn, test1);
|
||
|
assert.equal(k3._posts.get('cook').length, 1);
|
||
|
});
|
||
|
});
|