diff --git a/src/argsPass.js b/src/argsPass.js new file mode 100644 index 0000000000..4f874b0436 --- /dev/null +++ b/src/argsPass.js @@ -0,0 +1,38 @@ +import { useWith, curry, compose } from 'ramda'; + +import list from './list'; +import isTruthy from './isTruthy'; + +/** + * Takes a combining predicate and a list of functions and returns a function which will map the + * arguments it receives to the list of functions and returns the result of passing the values + * returned from each function to the combining predicate. A combining predicate is a function that + * combines a list of Boolean values into a single Boolean value, such as `R.any` or `R.all`. It + * will test each value using `RA.isTruthy`, meaning the functions don't necessarily have to be + * predicates. + * + * The function returned is curried to the number of functions supplied, and if called with more + * arguments than functions, any remaining arguments are passed in to the combining predicate + * untouched. + * + * @func argsPass + * @memberOf RA + * @since {@link https://char0n.github.io/ramda-adjunct/2.7.0|v2.7.0} + * @category Logic + * @sig ((* -> Boolean) -> [*] -> Boolean) -> [(* -> Boolean), ...] -> (*...) -> Boolean + * @param {Function} combiningPredicate The predicate used to combine the values returned from the + * list of functions + * @param {Array} functions List of functions + * @return {Boolean} Returns the combined result of mapping arguments to functions + * @example + * + * RA.argsPass(R.all, [RA.isArray, RA.isBoolean, RA.isString])([], false, 'abc') //=> true + * RA.argsPass(R.all, [RA.isArray, RA.isBoolean, RA.isString])([], false, 1) //=> false + * RA.argsPass(R.any, [RA.isArray, RA.isBoolean, RA.isString])({}, 1, 'abc') //=> true + * RA.argsPass(R.any, [RA.isArray, RA.isBoolean, RA.isString])({}, 1, false) //=> false + * RA.argsPass(R.none, [RA.isArray, RA.isBoolean, RA.isString])({}, 1, false) //=> true + * RA.argsPass(R.none, [RA.isArray, RA.isBoolean, RA.isString])({}, 1, 'abc') //=> false + */ +export default curry((combiningPredicate, predicates) => + useWith(compose(combiningPredicate(isTruthy), list), predicates) +); diff --git a/src/index.d.ts b/src/index.d.ts index 0cb635eb35..6d279c5926 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -908,6 +908,21 @@ declare namespace RamdaAdjunct { */ nonePass(predicates: Function[]): Function; + /** + * Takes a combining predicate and a list of functions and returns a function which will map + * the arguments it receives to the list of functions and returns the result of passing the + * values returned from each function to the combining predicate. A combining predicate is a + * function that combines a list of Boolean values into a single Boolean value, such as + * `R.any` or `R.all`. It will test each value using `RA.isTruthy`, meaning the functions + * don't necessarily have to be predicates. + * + * The function returned is curried to the number of functions supplied, and if called with + * more arguments than functions, any remaining arguments are passed in to the combining + * predicate untouched. + */ + argsPass(combiningPredicate: Function, predicates: Function[]): Function; + argsPass(combiningPredicate: Function): (predicates: Function[]) => Function; + /** * Creates an array with all falsy values removed. * The values false, null, 0, "", undefined, and NaN are falsy. diff --git a/src/index.js b/src/index.js index 16e1960e17..f1e336f79a 100644 --- a/src/index.js +++ b/src/index.js @@ -143,5 +143,6 @@ export { default as notBoth } from './notBoth'; export { default as neither } from './neither'; export { default as notAllPass } from './notAllPass'; export { default as nonePass } from './nonePass'; +export { default as argsPass } from './argsPass'; // Types export { default as Identity } from './fantasy-land/Identity'; diff --git a/test/argsPass.js b/test/argsPass.js new file mode 100644 index 0000000000..9148a5abff --- /dev/null +++ b/test/argsPass.js @@ -0,0 +1,175 @@ +import { stub } from 'sinon'; +import { all, any, none } from 'ramda'; +import { assert } from 'chai'; + +import { argsPass } from '../src/index'; + +describe('argsPass', function() { + let p1False; + let p1True; + let p2True; + let p2False; + let p3True; + let p3False; + let c2True; + let c1True; + let c2False; + let c1False; + + beforeEach(function() { + p1False = stub().returns(false); + p1True = stub().returns(true); + p2False = stub().returns(false); + p2True = stub().returns(true); + p3False = stub().returns(false); + p3True = stub().returns(true); + c2True = stub().returns(true); + c1True = stub().returns(c2True); + c2False = stub().returns(false); + c1False = stub().returns(c2False); + }); + + it('should return a new curried function for each argument supplied', function() { + const f = argsPass(c1True, [p1True, p2True, p3True]); + assert.isTrue(f(1)(2)(3)); + assert.isTrue(p1True.calledWith(1)); + assert.isTrue(p2True.calledWith(2)); + assert.isTrue(p3True.calledWith(3)); + assert.isTrue(c2True.calledWith([true, true, true])); + assert.isTrue(p1True.calledOnce); + assert.isTrue(p1True.calledOnce); + assert.isTrue(p2True.calledOnce); + assert.isTrue(c1True.calledOnce); + assert.isTrue(c2True.calledOnce); + }); + + context('with same number of functions as arguments', function() { + context('with all functions returning truthy values', function() { + specify('should return `true`', function() { + const f = argsPass(c1True, [p1True, p2True, p3True]); + assert.isTrue(f(1, 2, 3)); + assert.isTrue(p1True.calledWith(1)); + assert.isTrue(p2True.calledWith(2)); + assert.isTrue(p3True.calledWith(3)); + assert.isTrue(c2True.calledWith([true, true, true])); + assert.isTrue(p1True.calledOnce); + assert.isTrue(p2True.calledOnce); + assert.isTrue(p3True.calledOnce); + assert.isTrue(c1True.calledOnce); + assert.isTrue(c2True.calledOnce); + }); + }); + + context('with one function returning a falsy value', function() { + specify('should return `false`', function() { + const f = argsPass(c1False, [p1True, p2True, p3False]); + assert.isFalse(f(1, 2, 3)); + assert.isTrue(p1True.calledWith(1)); + assert.isTrue(p2True.calledWith(2)); + assert.isTrue(p3False.calledWith(3)); + assert.isTrue(c2False.calledWith([true, true, false])); + assert.isTrue(p1True.calledOnce); + assert.isTrue(p2True.calledOnce); + assert.isTrue(p3False.calledOnce); + assert.isTrue(c1False.calledOnce); + assert.isTrue(c2False.calledOnce); + }); + }); + }); + + context('with more arguments than predicates', function() { + context('with all functions returning truthy values', function() { + specify('should return `true`', function() { + const f = argsPass(c1True, [p1True, p2True, p3True]); + assert.isTrue(f(1, 2, 3, 4)); + assert.isTrue(p1True.calledWith(1)); + assert.isTrue(p2True.calledWith(2)); + assert.isTrue(p3True.calledWith(3)); + assert.isTrue(c2True.calledWith([true, true, true, 4])); + assert.isTrue(p1True.calledOnce); + assert.isTrue(p2True.calledOnce); + assert.isTrue(p3True.calledOnce); + assert.isTrue(c1True.calledOnce); + assert.isTrue(c2True.calledOnce); + }); + }); + + context('with one predicate failing', function() { + specify('should return `false`', function() { + const f = argsPass(c1False, [p1True, p2True, p3False]); + assert.isFalse(f(1, 2, 3, 4)); + assert.isTrue(p1True.calledWith(1)); + assert.isTrue(p2True.calledWith(2)); + assert.isTrue(p3False.calledWith(3)); + assert.isTrue(c2False.calledWith([true, true, false, 4])); + assert.isTrue(p1True.calledOnce); + assert.isTrue(p2True.calledOnce); + assert.isTrue(p3False.calledOnce); + assert.isTrue(c1False.calledOnce); + assert.isTrue(c2False.calledOnce); + }); + }); + }); + + context('with `all` as the combinator', function() { + context('with all functions returning truthy values', function() { + specify('should return `true`', function() { + const f = argsPass(all, [p1True, p2True, p3True]); + assert.isTrue(f(1, 2, 3)); + }); + }); + + context('with one function returning a falsy value', function() { + specify('should return `true`', function() { + const f = argsPass(all, [p1True, p2True, p3False]); + assert.isFalse(f(1, 2, 3)); + }); + }); + }); + + context('with `any` as the combining predicate', function() { + context('with all functions returning truthy values', function() { + specify('should return `true`', function() { + const f = argsPass(any, [p1True, p2True, p3True]); + assert.isTrue(f(1, 2, 3)); + }); + }); + + context('with one function returning a truthy value', function() { + specify('should return `true`', function() { + const f = argsPass(any, [p1True, p2False, p3True]); + assert.isTrue(f(1, 2, 3)); + }); + }); + + context('with all functions returning falsy values', function() { + specify('should return `true`', function() { + const f = argsPass(any, [p1False, p2False, p3False]); + assert.isFalse(f(1, 2, 3)); + }); + }); + }); + + context('with `none` as the combining predicate', function() { + context('with all functions returning truthy values', function() { + specify('should return `true`', function() { + const f = argsPass(none, [p1False, p2False, p3False]); + assert.isTrue(f(1, 2, 3)); + }); + }); + + context('with all functions returning falsy values', function() { + specify('should return `true`', function() { + const f = argsPass(none, [p1False, p2False, p3False]); + assert.isTrue(f(1, 2, 3)); + }); + }); + + context('with one function returning a truthy value', function() { + specify('should return `true`', function() { + const f = argsPass(none, [p1False, p2False, p3True]); + assert.isFalse(f(1, 2, 3)); + }); + }); + }); +});