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() { done(); }); }); /* 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); done(); }); }); 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); done(); }); }); /* 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); done(); }); }); /* 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); done(); }); }); /* 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); done(); }); }); /* 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); done(); }); }); }); 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); done(); }); }); 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); done(); }); }); }); 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); done(); }); 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); done(); }); }); }); 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); }); });