-
-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add dispatch Ref #66 * style: remove exclusive test * style(dispatch): use should sentences in tests * refactor(dispatch): avoid creating function during runtime * test(dispatch): add test for returning undefined * refactor(dispatch): make accumulator more sensible * test(dispach): add test for falsy function dispatches * refactor(dispatch): make tests pass * refactor(dispatch): do not handle side effects in any way * refactor(dispatch): simplify the composition * docs(dispatch): add documentation + typings * test(dispatch): correct the tests * test(dispatch): add test for implicit returns
- Loading branch information
1 parent
b2b904c
commit fb5aa08
Showing
4 changed files
with
182 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { | ||
sort, | ||
comparator, | ||
prop, | ||
pipe, | ||
head, | ||
curryN, | ||
reduce, | ||
reduced, | ||
curry, | ||
ifElse, | ||
} from 'ramda'; | ||
|
||
/** | ||
* Can be used as a way to compose multiple invokers together to form polymorphic functions, | ||
* or functions that exhibit different behaviors based on their argument(s). | ||
* Consumes dispatching functions and keep trying to invoke each in turn, until a non-nil value is returned. | ||
* | ||
* Accepts a list of dispatching functions and returns a new function. | ||
* When invoked, this new function is applied to some arguments, | ||
* each dispatching function is applied to those same arguments until one of the | ||
* dispatching functions returns a non-nil value. | ||
* | ||
* @func dispatch | ||
* @memberOf RA | ||
* @since {@link https://char0n.github.io/ramda-adjunct/2.6.0|v2.6.0} | ||
* @category Function | ||
* @sig [((a, b, ...) -> x1), ((a, b, ...) -> x2), ...] -> x1 | x2 | ... | ||
* @param {!Array} functions A list of functions | ||
* @return {*|undefined} Returns the first not-nil value, or undefined if either an empty list is provided or none of the dispatching functions returns a non-nil value | ||
* @see {@link RA.isNotNil} | ||
* @example | ||
* | ||
* // returns first non-nil value | ||
* const stubNil = () => null; | ||
* const stubUndefined = () => undefined; | ||
* const addOne = v => v + 1; | ||
* const addTwo = v => v + 2; | ||
* | ||
* RA.dispatch([stubNil, stubUndefined, addOne, addTwo])(1); //=> 2 | ||
* | ||
* // acts as a switch | ||
* const fnSwitch = RA.dispatch([ | ||
* R.ifElse(RA.isString, s => `${s}-join`, RA.stubUndefined), | ||
* R.ifElse(RA.isNumber, n => n + 1, RA.stubUndefined), | ||
* R.ifElse(RA.isDate, R.T, RA.stubUndefined), | ||
* ]); | ||
* fnSwitch(1); //=> 2 | ||
*/ | ||
import isNotNil from './isNotNil'; | ||
import isNonEmptyArray from './isNonEmptyArray'; | ||
import stubUndefined from './stubUndefined'; | ||
|
||
const byArity = comparator((a, b) => a.length > b.length); | ||
|
||
const getMaxArity = pipe(sort(byArity), head, prop('length')); | ||
|
||
const iteratorFn = curry((args, accumulator, fn) => { | ||
const result = fn(...args); | ||
|
||
return isNotNil(result) ? reduced(result) : accumulator; | ||
}); | ||
|
||
const dispatch = functions => { | ||
const arity = getMaxArity(functions); | ||
|
||
return curryN(arity, (...args) => | ||
reduce(iteratorFn(args), undefined, functions) | ||
); | ||
}; | ||
|
||
export default ifElse(isNonEmptyArray, dispatch, stubUndefined); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import * as R from 'ramda'; | ||
import { assert } from 'chai'; | ||
import sinon from 'sinon'; | ||
|
||
import * as RA from '../src'; | ||
import eq from './shared/eq'; | ||
|
||
describe('dispatch', function() { | ||
it('should return first non-nil value', function() { | ||
const nullStub = sinon.stub().returns(null); | ||
const undefinedStub = sinon.stub().returns(undefined); | ||
const zeroStub = sinon.stub().returns(0); | ||
const positiveNumberStub = sinon.stub().returns(1); | ||
|
||
const actual = RA.dispatch([ | ||
nullStub, | ||
undefinedStub, | ||
zeroStub, | ||
positiveNumberStub, | ||
])('test'); | ||
|
||
assert.strictEqual(actual, 0); | ||
assert.isTrue(nullStub.calledOnceWithExactly('test')); | ||
assert.isTrue(undefinedStub.calledOnceWithExactly('test')); | ||
assert.isTrue(zeroStub.calledOnceWithExactly('test')); | ||
assert.isTrue(positiveNumberStub.notCalled); | ||
}); | ||
|
||
it('should return curried function with max arity', function() { | ||
const fn = RA.dispatch([R.divide, R.identity]); | ||
|
||
eq(fn.length, 2); | ||
}); | ||
|
||
it('should act as switch', function() { | ||
const isString = sinon.stub().returns(false); | ||
const stringDispatch = sinon.stub().returns(undefined); | ||
const isNumber = sinon.stub().returns(true); | ||
const numberDispatch = sinon.stub().returns(true); | ||
const isDate = sinon.stub().returns(false); | ||
const dateDispatch = sinon.stub().returns(false); | ||
|
||
const fnSwitch = RA.dispatch([ | ||
R.ifElse(isString, stringDispatch, RA.stubUndefined), | ||
R.ifElse(isNumber, numberDispatch, RA.stubUndefined), | ||
R.ifElse(isDate, dateDispatch, RA.stubUndefined), | ||
]); | ||
fnSwitch(1); | ||
|
||
assert.isTrue(isString.calledOnceWithExactly(1)); | ||
assert.isTrue(stringDispatch.notCalled); | ||
assert.isTrue(isNumber.calledOnceWithExactly(1)); | ||
assert.isTrue(numberDispatch.calledOnceWithExactly(1)); | ||
assert.isTrue(isDate.notCalled); | ||
assert.isTrue(dateDispatch.notCalled); | ||
}); | ||
|
||
context('when dispatched function throws', function() { | ||
context('the error', function() { | ||
specify('should bubble up', function() { | ||
const configuredDispatch = RA.dispatch([ | ||
() => { | ||
throw new Error(); | ||
}, | ||
R.always(1), | ||
]); | ||
|
||
assert.throws(() => configuredDispatch('test')); | ||
}); | ||
}); | ||
}); | ||
|
||
context('when empty array provided as input', function() { | ||
specify('should return undefined', function() { | ||
eq(RA.dispatch([]), undefined); | ||
}); | ||
}); | ||
|
||
context('when all dispatched functions returns nil', function() { | ||
specify('should return undefined', function() { | ||
const configuredDispatch = RA.dispatch([RA.stubUndefined, RA.stubNull]); | ||
|
||
eq(configuredDispatch(), undefined); | ||
}); | ||
}); | ||
|
||
context( | ||
'when all dispatched functions have implicit return statement', | ||
function() { | ||
specify('should return undefined', function() { | ||
const configuredDispatch = RA.dispatch([() => {}, () => {}]); | ||
|
||
eq(configuredDispatch(), undefined); | ||
}); | ||
} | ||
); | ||
}); |