diff --git a/src/utils/createChainedFunction.js b/src/utils/createChainedFunction.js index 61064ebfde..f739b31b02 100644 --- a/src/utils/createChainedFunction.js +++ b/src/utils/createChainedFunction.js @@ -1,25 +1,31 @@ +import isFunction from 'lodash.isfunction'; + /** * Safe chained function * * Will only create a new function if needed, * otherwise will pass back existing functions or null. * - * @param {function} one - * @param {function} two + * @param {function} functions to chain * @returns {function|null} */ -function createChainedFunction(one, two) { - let hasOne = typeof one === 'function'; - let hasTwo = typeof two === 'function'; +function createChainedFunction(...funcs) { + return funcs + .filter(f => f != null) + .reduce((acc, f) => { + if (!isFunction(f)) { + throw new Error('Invalid Argument Type, must only provide functions, undefined, or null.'); + } - if (!hasOne && !hasTwo) { return null; } - if (!hasOne) { return two; } - if (!hasTwo) { return one; } + if (acc === null) { + return f; + } - return function chainedFunction() { - one.apply(this, arguments); - two.apply(this, arguments); - }; + return function chainedFunction(...args) { + acc.apply(this, args); + f.apply(this, args); + }; + }, null); } export default createChainedFunction; diff --git a/test/utils/createChainedFunctionSpec.js b/test/utils/createChainedFunctionSpec.js new file mode 100644 index 0000000000..4b022d16e8 --- /dev/null +++ b/test/utils/createChainedFunctionSpec.js @@ -0,0 +1,71 @@ +/* eslint no-new-func: 0 */ +import createChainedFunction from '../../src/utils/createChainedFunction'; + +describe('createChainedFunction', function() { + it('returns null with no arguments', function() { + expect(createChainedFunction()).to.equal(null); + }); + + it('returns original function when single function is provided', function() { + const func1 = sinon.stub(); + createChainedFunction(func1).should.equal(func1); + }); + + it('wraps two functions with another that invokes both when called', function() { + const func1 = sinon.stub(); + const func2 = sinon.stub(); + const chained = createChainedFunction(func1, func2); + + chained + .should.not.equal(func1) + .and.should.not.equal(func2); + + func1.should.not.have.been.called; + func2.should.not.have.been.called; + + chained(); + + func1.should.have.been.calledOnce; + func2.should.have.been.calledOnce; + }); + + it('wraps multiple functions and invokes them in the order provided', function() { + const results = []; + const func1 = () => results.push(1); + const func2 = () => results.push(2); + const func3 = () => results.push(3); + const chained = createChainedFunction(func1, func2, func3); + chained(); + results.should.eql([1, 2, 3]); + }); + + it('forwards arguments to all chained functions', function() { + const in1 = 'herpa derpa'; + const in2 = { + herpa: 'derpa' + }; + + const func = (arg1, arg2) => { + arg1.should.equal(in1); + arg2.should.equal(in2); + }; + + const chained = createChainedFunction(func, func, func); + chained(in1, in2); + }); + + it('throws when func is not provided', function() { + expect(() => { + createChainedFunction({ herpa: 'derpa' }); + }).to.throw(/Invalid Argument Type/); + }); + + it('works with new Function call', function() { + const results = []; + const func1 = new Function('results', 'results.push(1);'); + const func2 = new Function('results', 'results.push(2);'); + const chained = createChainedFunction(func1, func2); + chained(results); + results.should.eql([1, 2]); + }); +});