├── .npmrc ├── .gitignore ├── src ├── findKey.js ├── findLastKey.js ├── _utils │ ├── isArray.js │ ├── isNAN.js │ ├── isNull.js │ ├── isString.js │ ├── isUndefined.js │ ├── isFunction.js │ ├── isObject.js │ ├── getKey.js │ ├── isNumber.js │ ├── isInteger.js │ ├── isPlaceholder.js │ ├── coalesceToArray.js │ ├── isPositiveInteger.js │ ├── getObjectClass.js │ ├── findInArray.js │ ├── isEmpty.js │ ├── reduceArray.js │ ├── findInObject.js │ ├── reduceObject.js │ ├── reverse.js │ ├── getPath.js │ └── isEquivalent.js ├── identity.js ├── not.js ├── __.js ├── compose.js ├── typeOf.js ├── pipe.js ├── add.js ├── divide.js ├── always.js ├── sort.js ├── subtract.js ├── tap.js ├── multiply.js ├── partial.js ├── unique.js ├── partialRight.js ├── max.js ├── min.js ├── lt.js ├── gt.js ├── append.js ├── startsWith.js ├── instanceOf.js ├── lte.js ├── apply.js ├── gte.js ├── prepend.js ├── notBy.js ├── remainder.js ├── bind.js ├── equals.js ├── findLast.js ├── empty.js ├── reject.js ├── findLastIndex.js ├── equalsBy.js ├── tryCatch.js ├── ascend.js ├── sortBy.js ├── descend.js ├── sortWith.js ├── uncurry.js ├── includes.js ├── uniqueBy.js ├── reduceRight.js ├── modulo.js ├── get.js ├── find.js ├── findIndex.js ├── insert.js ├── pluck.js ├── is.js ├── reduce.js ├── arity.js ├── endsWith.js ├── entries.js ├── set.js ├── flatten.js ├── has.js ├── merge.js ├── pick.js ├── forEach.js ├── take.js ├── map.js ├── rest.js ├── curry.js ├── some.js ├── every.js ├── filter.js ├── partition.js └── mergeDeep.js ├── .npmignore ├── test ├── _utils │ ├── isArray.js │ ├── getKey.js │ ├── coalesceToArray.js │ ├── isEmpty.js │ ├── isNAN.js │ ├── isObject.js │ ├── isString.js │ ├── isFunction.js │ ├── isNumber.js │ ├── isNull.js │ ├── isInteger.js │ ├── isUndefined.js │ ├── isPositiveInteger.js │ ├── isPlaceholder.js │ ├── reduceArray.js │ ├── reverse.js │ ├── getObjectClass.js │ ├── reduceObject.js │ ├── findInObject.js │ ├── findInArray.js │ ├── getPath.js │ └── isEquivalent.js ├── findKey.js ├── findLastKey.js ├── add.js ├── max.js ├── min.js ├── divide.js ├── identity.js ├── multiply.js ├── subtract.js ├── notBy.js ├── append.js ├── prepend.js ├── apply.js ├── equalsBy.js ├── typeOf.js ├── equals.js ├── helpers │ ├── _methods.js │ └── _typeCheckValues.js ├── sort.js ├── always.js ├── sortBy.js ├── lt.js ├── gt.js ├── allTestsExist.js ├── lte.js ├── gte.js ├── unique.js ├── partial.js ├── not.js ├── instanceOf.js ├── index.js ├── pipe.js ├── remainder.js ├── compose.js ├── partialRight.js ├── sortWith.js ├── uniqueBy.js ├── bind.js ├── includes.js ├── is.js ├── take.js ├── rest.js ├── flatten.js ├── tap.js ├── has.js ├── ascend.js ├── descend.js ├── arity.js ├── every.js ├── map.js ├── uncurry.js ├── forEach.js ├── pick.js ├── tryCatch.js ├── empty.js ├── insert.js ├── some.js ├── endsWith.js ├── merge.js ├── pluck.js ├── get.js ├── startsWith.js ├── filter.js ├── omit.js ├── reject.js ├── modulo.js ├── mergeDeep.js ├── partition.js ├── entries.js ├── find.js ├── findIndex.js ├── reduce.js ├── reduceRight.js ├── set.js ├── findLastIndex.js ├── findLast.js └── curry.js ├── .eslintrc ├── webpack ├── webpack.config.minified.js ├── webpack.config.js └── webpack.config.dev.js ├── LICENSE ├── .babelrc ├── package.json └── CHANGELOG.md /.npmrc: -------------------------------------------------------------------------------- 1 | scripts-prepend-node-path=true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .nyc_output 3 | coverage 4 | lib 5 | dist 6 | es 7 | node_modules 8 | *.log 9 | -------------------------------------------------------------------------------- /src/findKey.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import findKey from './findIndex'; 3 | 4 | export default findKey; 5 | -------------------------------------------------------------------------------- /src/findLastKey.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import findLastKey from './findLastIndex'; 3 | 4 | export default findLastKey; 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git 2 | .idea 3 | .nyc_output 4 | coverage 5 | DEV_ONLY 6 | node_modules 7 | src 8 | test 9 | webpack 10 | .babelrc 11 | .eslintrc 12 | .gitignore 13 | .npmignore 14 | *.log 15 | yarn.lock 16 | -------------------------------------------------------------------------------- /test/_utils/isArray.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import isArray from 'src/_utils/isArray'; 6 | 7 | test('if isArray is equal to the global Array isArray', (t) => { 8 | t.is(isArray, Array.isArray); 9 | }); 10 | -------------------------------------------------------------------------------- /src/_utils/isArray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function isArray 3 | * 4 | * @description 5 | * test if the item an array 6 | * 7 | * @param {*} object the object to test 8 | * @returns {boolean} is the item an array 9 | */ 10 | export default Array.isArray; 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["rapid7/browser"], 3 | "globals": { 4 | "__dirname": true, 5 | "global": true, 6 | "module": true, 7 | "process": true, 8 | "require": true 9 | }, 10 | "parser": "babel-eslint", 11 | "rules": {} 12 | } 13 | -------------------------------------------------------------------------------- /test/findKey.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import findKey from 'src/findKey'; 6 | import findIndex from 'src/findIndex'; 7 | 8 | test('if findKey is the same function as findIndex', (t) => { 9 | t.is(findKey, findIndex); 10 | }); 11 | -------------------------------------------------------------------------------- /src/_utils/isNAN.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function isNAN 3 | * 4 | * @description 5 | * test if the item is NaN 6 | * 7 | * @param {*} object the object to test 8 | * @returns {boolean} is the item is NaN 9 | */ 10 | export default function isNAN(object) { 11 | return object !== object; 12 | } 13 | -------------------------------------------------------------------------------- /src/_utils/isNull.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function isNull 3 | * 4 | * @description 5 | * test if the item is null 6 | * 7 | * @param {*} object the object to test 8 | * @returns {boolean} is the item is null 9 | */ 10 | export default function isNull(object) { 11 | return object === null; 12 | } 13 | -------------------------------------------------------------------------------- /test/findLastKey.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import findLastKey from 'src/findLastKey'; 6 | import findLastIndex from 'src/findLastIndex'; 7 | 8 | test('if findLastKey is the same function as findLastIndex', (t) => { 9 | t.is(findLastKey, findLastIndex); 10 | }); 11 | -------------------------------------------------------------------------------- /test/add.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import add from 'src/add'; 6 | 7 | test('if add will add the two numbers passed', (t) => { 8 | const first = 10; 9 | const second = 4; 10 | 11 | const result = add(first)(second); 12 | 13 | t.is(result, first + second); 14 | }); 15 | -------------------------------------------------------------------------------- /test/max.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import max from 'src/max'; 6 | 7 | test('if max returns the maximum value of the two numbers', (t) => { 8 | const lower = 10; 9 | const higher = 20; 10 | 11 | const result = max(lower)(higher); 12 | 13 | t.is(result, higher); 14 | }); 15 | -------------------------------------------------------------------------------- /test/min.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import min from 'src/min'; 6 | 7 | test('if min returns the minimum value of the two numbers', (t) => { 8 | const lower = 10; 9 | const higher = 20; 10 | 11 | const result = min(lower)(higher); 12 | 13 | t.is(result, lower); 14 | }); 15 | -------------------------------------------------------------------------------- /src/_utils/isString.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function isString 3 | * 4 | * @description 5 | * test if the item a string 6 | * 7 | * @param {*} object the object to test 8 | * @returns {boolean} is the item a string 9 | */ 10 | export default function isString(object) { 11 | return typeof object === 'string'; 12 | } 13 | -------------------------------------------------------------------------------- /src/_utils/isUndefined.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function isUndefined 3 | * 4 | * @description 5 | * test if the item is undefined 6 | * 7 | * @param {*} object the object to test 8 | * @returns {boolean} is the item is undefined 9 | */ 10 | export default function isUndefined(object) { 11 | return object === void 0; 12 | } 13 | -------------------------------------------------------------------------------- /src/_utils/isFunction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function isFunction 3 | * 4 | * @description 5 | * test if the item a function 6 | * 7 | * @param {*} object the object to test 8 | * @returns {boolean} is the item a function 9 | */ 10 | export default function isFunction(object) { 11 | return typeof object === 'function'; 12 | } 13 | -------------------------------------------------------------------------------- /test/divide.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import divide from 'src/divide'; 6 | 7 | test('if divide will divide the two numbers passed', (t) => { 8 | const first = 10; 9 | const second = 4; 10 | 11 | const result = divide(first)(second); 12 | 13 | t.is(result, first / second); 14 | }); 15 | -------------------------------------------------------------------------------- /test/identity.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import identity from 'src/identity'; 6 | 7 | test('if identity returns the first argument passed and ignores the others', (t) => { 8 | const value = {}; 9 | 10 | const result = identity(value, 'foo', 123); 11 | 12 | t.is(result, value); 13 | }); 14 | -------------------------------------------------------------------------------- /src/identity.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function identity 3 | * 4 | * @description 5 | * function that allows straightforward passthrough of the first argument passed 6 | * 7 | * @param {*} value the value passed 8 | * @returns {*} the first argument passed 9 | */ 10 | export default function identity(value) { 11 | return value; 12 | } 13 | -------------------------------------------------------------------------------- /src/not.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function not 3 | * 4 | * @description 5 | * return the coerced boolean opposite of the value passed's truthiness 6 | * 7 | * @param {*} value the value to coerce 8 | * @returns {boolean} the opposite of the value's truthiness 9 | */ 10 | export default function not(value) { 11 | return !value; 12 | } 13 | -------------------------------------------------------------------------------- /test/multiply.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import multiply from 'src/multiply'; 6 | 7 | test('if multiply will multiply the two numbers passed', (t) => { 8 | const first = 10; 9 | const second = 4; 10 | 11 | const result = multiply(first)(second); 12 | 13 | t.is(result, first * second); 14 | }); 15 | -------------------------------------------------------------------------------- /test/subtract.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import subtract from 'src/subtract'; 6 | 7 | test('if subtract will subtract the two numbers passed', (t) => { 8 | const first = 10; 9 | const second = 4; 10 | 11 | const result = subtract(first)(second); 12 | 13 | t.is(result, first - second); 14 | }); 15 | -------------------------------------------------------------------------------- /src/__.js: -------------------------------------------------------------------------------- 1 | // utils 2 | import isFunction from './_utils/isFunction'; 3 | 4 | const PLACEHOLDER = '__KARI_PARAMETER_PLACEHOLDER__'; 5 | 6 | /** 7 | * @constant {string|Symbol} __ placeholder to use if you want to pass arguments out of order 8 | * @default 9 | */ 10 | export default (isFunction(Symbol) ? Symbol(PLACEHOLDER) : PLACEHOLDER); 11 | -------------------------------------------------------------------------------- /test/notBy.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import notBy from 'src/notBy'; 6 | 7 | test('if notBy returns the falsiness returned by the call to fn with value', (t) => { 8 | const fn = (object) => { 9 | return object === 'foo'; 10 | }; 11 | 12 | t.false(notBy(fn, 'foo')); 13 | t.true(notBy(fn, 'bar')); 14 | }); 15 | -------------------------------------------------------------------------------- /src/_utils/isObject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function isObject 3 | * 4 | * @description 5 | * test if the item a object 6 | * 7 | * @param {*} object the object to test 8 | * @returns {boolean} is the item a object 9 | */ 10 | export default function isObject(object) { 11 | return typeof object === 'object' && !!object && object.constructor === Object; 12 | } 13 | -------------------------------------------------------------------------------- /src/_utils/getKey.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function getKey 3 | * 4 | * @description 5 | * get the key as a number if parseable 6 | * 7 | * @param {string} key the key to try to parse 8 | * @returns {number|string} the parsed key 9 | */ 10 | export default function getKey(key) { 11 | const intKey = +key; 12 | 13 | return `${intKey}` === key ? intKey : key; 14 | } 15 | -------------------------------------------------------------------------------- /test/append.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import append from 'src/append'; 6 | 7 | test('if append will add an item to the end of the array', (t) => { 8 | const array = [1, 2, 3]; 9 | const item = 'x'; 10 | 11 | const result = append(item)(array); 12 | 13 | t.not(result, array); 14 | t.deepEqual(result, [...array, item]); 15 | }); 16 | -------------------------------------------------------------------------------- /src/_utils/isNumber.js: -------------------------------------------------------------------------------- 1 | // utils 2 | import isNAN from './isNAN'; 3 | 4 | /** 5 | * @function isNumber 6 | * 7 | * @description 8 | * test if the item a number 9 | * 10 | * @param {*} object the object to test 11 | * @returns {boolean} is the item a number 12 | */ 13 | export default function isNumber(object) { 14 | return typeof object === 'number' && !isNAN(object); 15 | } 16 | -------------------------------------------------------------------------------- /test/prepend.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import prepend from 'src/prepend'; 6 | 7 | test('if prepend will add an item to the beginning of the array', (t) => { 8 | const array = [1, 2, 3]; 9 | const item = 'x'; 10 | 11 | const result = prepend(item)(array); 12 | 13 | t.not(result, array); 14 | t.deepEqual(result, [item, ...array]); 15 | }); 16 | -------------------------------------------------------------------------------- /test/apply.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import apply from 'src/apply'; 6 | 7 | test('if apply will create a method that will apply the arguments passed to fn', (t) => { 8 | const fn = (a, b, c) => { 9 | return [a, b, c]; 10 | }; 11 | const args = [1, 2, 3]; 12 | 13 | const result = apply(fn)(args); 14 | 15 | t.deepEqual(result, args); 16 | }); 17 | -------------------------------------------------------------------------------- /test/equalsBy.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import equalsBy from 'src/equalsBy'; 6 | 7 | test('if equalsBy determines if the items are equal based on fn being called with each value', (t) => { 8 | const fn = (item) => { 9 | return item === 'foo'; 10 | }; 11 | 12 | t.true(equalsBy(fn, 'foo', 'foo')); 13 | t.false(equalsBy(fn, 'foo', 'bar')); 14 | }); 15 | -------------------------------------------------------------------------------- /test/typeOf.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import typeOf from 'src/typeOf'; 6 | 7 | test('if typeOf will create a method that checks if the value is the type of type', (t) => { 8 | const type = 'string'; 9 | const trueValue = 'foo'; 10 | const falseValue = 123; 11 | 12 | t.true(typeOf(type)(trueValue)); 13 | t.false(typeOf(type)(falseValue)); 14 | }); 15 | -------------------------------------------------------------------------------- /src/_utils/isInteger.js: -------------------------------------------------------------------------------- 1 | // utils 2 | import isNumber from './isNumber'; 3 | 4 | /** 5 | * @function isInteger 6 | * 7 | * @description 8 | * test if the item an integer 9 | * 10 | * @param {*} object the object to test 11 | * @returns {boolean} is the item an integer 12 | */ 13 | export default function isInteger(object) { 14 | return isNumber(object) && ~~object === object; 15 | } 16 | -------------------------------------------------------------------------------- /src/compose.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function compose 3 | * 4 | * @param {...Array} fns the array of functions to compose 5 | * @returns {function(...Array<*>): *} the composed methods as a single method 6 | */ 7 | export default function compose(...fns) { 8 | return fns.reduce( 9 | (f, g) => 10 | function(...args) { 11 | return f(g(...args)); 12 | } 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/typeOf.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function typeOf 6 | * 7 | * @description 8 | * create a function checks if the value is typeof type 9 | * 10 | * @param {*} value the value to test 11 | * @returns {boolean} is the value typeof type 12 | */ 13 | export default curry(function typeOf(type, value) { 14 | return typeof value === type; 15 | }); 16 | -------------------------------------------------------------------------------- /src/_utils/isPlaceholder.js: -------------------------------------------------------------------------------- 1 | // placeholder 2 | import __ from '../__'; 3 | 4 | /** 5 | * @function isPlaceholder 6 | * 7 | * @description 8 | * determine if the object passed is the placeholder 9 | * 10 | * @param {*} object the object to test 11 | * @returns {boolean} is the object the placeholder 12 | */ 13 | export default function isPlaceholder(object) { 14 | return object === __; 15 | } 16 | -------------------------------------------------------------------------------- /src/pipe.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import compose from './compose'; 3 | 4 | // utils 5 | import reverse from './_utils/reverse'; 6 | 7 | /** 8 | * @function pipe 9 | * 10 | * @param {...Array} fns the array of functions to pipe 11 | * @returns {function(...Array<*>): *} the piped methods as a single method 12 | */ 13 | export default function pipe(...fns) { 14 | return compose(...reverse(fns)); 15 | } 16 | -------------------------------------------------------------------------------- /src/add.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function add 6 | * 7 | * @description 8 | * add two values together 9 | * 10 | * @param {number} first the first number to add 11 | * @param {number} second the second number to add 12 | * @returns {number} the sum of first and second 13 | */ 14 | export default curry(function add(first, second) { 15 | return first + second; 16 | }); 17 | -------------------------------------------------------------------------------- /src/divide.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function divide 6 | * 7 | * @description 8 | * divide two values 9 | * 10 | * @param {number} first the number to divide 11 | * @param {number} second the number to divide from the first 12 | * @returns {number} the division of second from first 13 | */ 14 | export default curry(function divide(first, second) { 15 | return first / second; 16 | }); 17 | -------------------------------------------------------------------------------- /src/always.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function always 6 | * 7 | * @description 8 | * create a function that always returns value passed 9 | * 10 | * @param {*} value the value to always return 11 | * @returns {function(): *} the method that will always return value 12 | */ 13 | export default curry(function always(value) { 14 | return function() { 15 | return value; 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /src/_utils/coalesceToArray.js: -------------------------------------------------------------------------------- 1 | // utils 2 | import isArray from './isArray'; 3 | 4 | /** 5 | * @function coalesceToArray 6 | * 7 | * @description 8 | * coalesce the value passed to an array if it is not already one 9 | * 10 | * @param {*} value the value to possibly coalesce 11 | * @returns {Array<*>} the coalesced array 12 | */ 13 | export default function coalesceToArray(value) { 14 | return isArray(value) ? value : [value]; 15 | } 16 | -------------------------------------------------------------------------------- /src/_utils/isPositiveInteger.js: -------------------------------------------------------------------------------- 1 | // utils 2 | import isInteger from './isInteger'; 3 | 4 | /** 5 | * @function isPositiveInteger 6 | * 7 | * @description 8 | * is the object passed a positive integer 9 | * 10 | * @param {number} object the object to test 11 | * @returns {boolean} is the object passed a positive integer 12 | */ 13 | export default function isPositiveInteger(object) { 14 | return isInteger(object) && object > 0; 15 | } 16 | -------------------------------------------------------------------------------- /src/sort.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import coalesceToArray from './_utils/coalesceToArray'; 6 | 7 | /** 8 | * @function sort 9 | * 10 | * @param {function} fn the comparator function 11 | * @param {Array<*>} array the array to sort 12 | * @returns {Array<*>} the sorted array 13 | */ 14 | export default curry(function sort(fn, array) { 15 | return [...coalesceToArray(array)].sort(fn); 16 | }); 17 | -------------------------------------------------------------------------------- /src/subtract.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function subtract 6 | * 7 | * @description 8 | * subtract two values 9 | * 10 | * @param {number} first the number to subtract 11 | * @param {number} second the number to subtract from first 12 | * @returns {number} the division of second from first 13 | */ 14 | export default curry(function subtract(first, second) { 15 | return first - second; 16 | }); 17 | -------------------------------------------------------------------------------- /test/equals.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import sinon from 'sinon'; 4 | 5 | // src 6 | import equals from 'src/equals'; 7 | import * as utils from 'src/_utils/isEquivalent'; 8 | 9 | test('if equals calls isEquivalent', (t) => { 10 | const spy = sinon.spy(utils, 'default'); 11 | 12 | const result = equals(new Map(), new Map()); 13 | 14 | t.true(spy.calledOnce); 15 | t.true(result); 16 | 17 | spy.restore(); 18 | }); 19 | -------------------------------------------------------------------------------- /src/tap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function tap 3 | * 4 | * @description 5 | * execute fn on value before returning value as an identity method would 6 | * 7 | * @param {function} fn the function to call value on 8 | * @returns {function(...Array<*>): *} the method that will handle the tap call with args 9 | */ 10 | export default function tap(fn) { 11 | return function(...args) { 12 | fn(...args); 13 | 14 | return args[0]; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /test/helpers/_methods.js: -------------------------------------------------------------------------------- 1 | // external dependencies 2 | import fs from 'fs'; 3 | 4 | const FILES_OR_DIRECTORIES_TO_SKIP = [ 5 | // files 6 | 'allTestsExist.js', 7 | 'index.js', 8 | '__.js', 9 | 10 | // directories 11 | 'helpers', 12 | '_utils' 13 | ]; 14 | 15 | export const getFiles = (directory) => { 16 | return fs.readdirSync(directory).filter((file) => { 17 | return !~FILES_OR_DIRECTORIES_TO_SKIP.indexOf(file); 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /src/multiply.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function multiply 6 | * 7 | * @description 8 | * multiply two values together 9 | * 10 | * @param {number} first the first number to multiply 11 | * @param {number} second the second number to multiply 12 | * @returns {number} the multiplication of first and second 13 | */ 14 | export default curry(function multiply(first, second) { 15 | return first * second; 16 | }); 17 | -------------------------------------------------------------------------------- /src/partial.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function partial 3 | * 4 | * @param {function} fn the function to make into a partial function 5 | * @param {...Array<*>} outerArgs arguments to partially assign before fn is executed 6 | * @returns {function(...Array<*>): *} the partial function that will apply fn 7 | */ 8 | export default function partial(fn, ...outerArgs) { 9 | return function(...innerArgs) { 10 | return fn(...outerArgs, ...innerArgs); 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/unique.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import identity from './identity'; 3 | import uniqueBy from './uniqueBy'; 4 | 5 | /** 6 | * @function unique 7 | * 8 | * @description 9 | * filter down the collection to the unique values 10 | * 11 | * @param {Array<*>|Object} collection the collection to filter 12 | * @returns {Array<*>|Object} the filtered collection 13 | */ 14 | export default function unique(collection) { 15 | return uniqueBy(identity, collection); 16 | } 17 | -------------------------------------------------------------------------------- /src/partialRight.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function partialRight 3 | * 4 | * @param {function} fn the function to make into a partial function 5 | * @param {...Array<*>} outerArgs arguments to partially assign before fn is executed 6 | * @returns {function(...Array<*>): *} the partial function that will apply fn 7 | */ 8 | export default function partialRight(fn, ...outerArgs) { 9 | return function(...innerArgs) { 10 | return fn(...innerArgs, ...outerArgs); 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/max.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function max 6 | * 7 | * @description 8 | * get the higher of the two numbers passed 9 | * 10 | * @param {number} firstNumber the first number passed 11 | * @param {number} secondNumber the second number passed 12 | * @param {number} the higher of the two numbers 13 | */ 14 | export default curry(function max(firstNumber, secondNumber) { 15 | return Math.max(firstNumber, secondNumber); 16 | }); 17 | -------------------------------------------------------------------------------- /src/min.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function min 6 | * 7 | * @description 8 | * get the lower of the two numbers passed 9 | * 10 | * @param {number} firstNumber the first number passed 11 | * @param {number} secondNumber the second number passed 12 | * @param {number} the lower of the two numbers 13 | */ 14 | export default curry(function min(firstNumber, secondNumber) { 15 | return Math.min(firstNumber, secondNumber); 16 | }); 17 | -------------------------------------------------------------------------------- /test/sort.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import sort from 'src/sort'; 6 | 7 | test('if sort will return a new array sorted based on the comparator function', (t) => { 8 | const comparator = (a, b) => { 9 | return a > b; 10 | }; 11 | const items = [4, 1, 2, 8, 4]; 12 | 13 | const result = sort(comparator)(items); 14 | 15 | t.not(result, items); 16 | 17 | const sortedClone = [...items].sort(); 18 | 19 | t.deepEqual(result, sortedClone); 20 | }); 21 | -------------------------------------------------------------------------------- /src/lt.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function lt 6 | * 7 | * @description 8 | * determine if the first value is less than the second 9 | * 10 | * @param {number|string} firstValue the first value to compare 11 | * @param {number|string} secondValue the second value to compare 12 | * @param {boolean} is the firstValue less than the secondValue 13 | */ 14 | export default curry(function lt(firstValue, secondValue) { 15 | return firstValue < secondValue; 16 | }); 17 | -------------------------------------------------------------------------------- /test/helpers/_typeCheckValues.js: -------------------------------------------------------------------------------- 1 | export const ARRAY = []; 2 | export const BOOLEAN = false; 3 | export const DATE = new Date(); 4 | export const DECIMAL = 123.456; 5 | export const EMPTY_STRING = ''; 6 | export const FUNCTION = () => {}; 7 | export const NAN = NaN; 8 | export const NEGATIVE_INTEGER = -123; 9 | export const NULL = null; 10 | export const OBJECT = {}; 11 | export const POSITIVE_INTEGER = 123; 12 | export const STRING = 'foo'; 13 | export const UNDEFINED = undefined; 14 | export const ZERO = 0; 15 | -------------------------------------------------------------------------------- /src/gt.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function gt 6 | * 7 | * @description 8 | * determine if the first value is greater than the second 9 | * 10 | * @param {number|string} firstValue the first value to compare 11 | * @param {number|string} secondValue the second value to compare 12 | * @param {boolean} is the firstValue greater than the secondValue 13 | */ 14 | export default curry(function gt(firstValue, secondValue) { 15 | return firstValue > secondValue; 16 | }); 17 | -------------------------------------------------------------------------------- /test/always.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import _ from 'lodash'; 4 | import * as types from 'test/helpers/_typeCheckValues'; 5 | 6 | // src 7 | import always from 'src/always'; 8 | 9 | test('if always creates a method that always returns the value it was created with', (t) => { 10 | const value = {}; 11 | 12 | const result = always(value); 13 | 14 | t.true(_.isFunction(result)); 15 | 16 | Object.keys(types).forEach((key) => { 17 | t.is(result(types[key]), value); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/append.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | import insert from './insert'; 4 | 5 | /** 6 | * @function append 7 | * 8 | * @description 9 | * append a new item to the end of the items passed 10 | * 11 | * @param {*} newItem the item to add to items 12 | * @param {Array<*>} array the array of items to append to 13 | * @returns {Array<*>} the updated array of items 14 | */ 15 | export default curry(function append(newItem, array) { 16 | return insert(array.length, newItem, array); 17 | }); 18 | -------------------------------------------------------------------------------- /test/sortBy.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import get from 'src/get'; 6 | import sortBy from 'src/sortBy'; 7 | 8 | test('if sortBy will return a new array sorted based on the values returned from the function passed', (t) => { 9 | const items = [{foo: 'foo'}, {foo: 'bar'}, {foo: 'foo'}, {foo: 'baz'}]; 10 | 11 | const result = sortBy(get('foo'))(items); 12 | 13 | t.not(result, items); 14 | 15 | t.deepEqual(result, [{foo: 'bar'}, {foo: 'baz'}, {foo: 'foo'}, {foo: 'foo'}]); 16 | }); 17 | -------------------------------------------------------------------------------- /src/startsWith.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function startsWith 6 | * 7 | * @param {*} startingValue the value that the collection should start with 8 | * @param {Array<*>|string} valueToTest the value to test for starting with startingValue 9 | * @returns {boolean} does valueToTest starts with startingValue 10 | */ 11 | export default curry(function startsWith(startingValue, valueToTest) { 12 | return !!valueToTest.length && valueToTest.indexOf(startingValue) === 0; 13 | }); 14 | -------------------------------------------------------------------------------- /test/_utils/getKey.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import getKey from 'src/_utils/getKey'; 6 | 7 | test('if getKey will return the key as-is when it is not a number', (t) => { 8 | const key = 'foo'; 9 | 10 | const result = getKey(key); 11 | 12 | t.is(result, key); 13 | }); 14 | 15 | test('if getKey will return the key as a number when it is a number string', (t) => { 16 | const key = '1'; 17 | 18 | const result = getKey(key); 19 | 20 | t.is(result, parseInt(key, 10)); 21 | }); 22 | -------------------------------------------------------------------------------- /test/lt.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import lt from 'src/lt'; 6 | 7 | test('if lt returns true when first numerical value is less than the second, false otherwise', (t) => { 8 | t.true(lt(5)(10)); 9 | 10 | t.false(lt(12)(10)); 11 | t.false(lt(10)(10)); 12 | }); 13 | 14 | test('if lt returns true when first string value is less than the second, false otherwise', (t) => { 15 | t.true(lt('bar')('foo')); 16 | 17 | t.false(lt('baz')('bar')); 18 | t.false(lt('foo')('foo')); 19 | }); 20 | -------------------------------------------------------------------------------- /src/instanceOf.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function instanceOf 6 | * 7 | * @description 8 | * check if the object is an instance of the Constructor 9 | * 10 | * @param {function} Constructor the constructor function to test against 11 | * @param {*} object the object to test 12 | * @param {boolean} is the object an instance of Constructor 13 | */ 14 | export default curry(function instanceOf(Constructor, object) { 15 | return !!object && object instanceof Constructor; 16 | }); 17 | -------------------------------------------------------------------------------- /test/gt.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import gt from 'src/gt'; 6 | 7 | test('if gt returns true when first numerical value is greater than the second, false otherwise', (t) => { 8 | t.true(gt(10)(5)); 9 | 10 | t.false(gt(10)(12)); 11 | t.false(gt(10)(10)); 12 | }); 13 | 14 | test('if gt returns true when first string value is greater than the second, false otherwise', (t) => { 15 | t.true(gt('foo')('bar')); 16 | 17 | t.false(gt('bar')('baz')); 18 | t.false(gt('foo')('foo')); 19 | }); 20 | -------------------------------------------------------------------------------- /src/lte.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function lte 6 | * 7 | * @description 8 | * determine if the first value is less than or equal to the second 9 | * 10 | * @param {number|string} firstValue the first value to compare 11 | * @param {number|string} secondValue the second value to compare 12 | * @param {boolean} is the firstValue less than or equal to the secondValue 13 | */ 14 | export default curry(function lte(firstValue, secondValue) { 15 | return firstValue <= secondValue; 16 | }); 17 | -------------------------------------------------------------------------------- /src/apply.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function apply 6 | * 7 | * @description 8 | * apply the argument passed as second parameter to the function passed as the first, 9 | * helpful in currying variadic functions 10 | * 11 | * @param {function} fn the function to apply the args to 12 | * @param {Array<*>} args the arguments to pass to fn 13 | * @returns {*} the result of fn called with args 14 | */ 15 | export default curry(function apply(fn, args) { 16 | return fn.apply(this, args); 17 | }); 18 | -------------------------------------------------------------------------------- /src/gte.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function gte 6 | * 7 | * @description 8 | * determine if the first value is greater than or equal to the second 9 | * 10 | * @param {number|string} firstValue the first value to compare 11 | * @param {number|string} secondValue the second value to compare 12 | * @param {boolean} is the firstValue greater than or equal to the secondValue 13 | */ 14 | export default curry(function gte(firstValue, secondValue) { 15 | return firstValue >= secondValue; 16 | }); 17 | -------------------------------------------------------------------------------- /src/prepend.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | import insert from './insert'; 4 | 5 | /** 6 | * @function prepend 7 | * 8 | * @description 9 | * prepend a new item to the front of the collection passed 10 | * 11 | * @param {*} newItem the item to add to collection 12 | * @param {Array<*>} collection the collection of items to prepend to 13 | * @returns {Array<*>} the updated collection 14 | */ 15 | export default curry(function prepend(newItem, collection) { 16 | return insert(0, newItem, collection, true); 17 | }); 18 | -------------------------------------------------------------------------------- /src/notBy.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | import not from './not'; 4 | 5 | /** 6 | * @function notBy 7 | * 8 | * @description 9 | * based on calling fn with value, returns the opposite of the return values truthiness 10 | * 11 | * @param {function} fn the function to transform value with 12 | * @param {*} value the value to call fn with 13 | * @returns {boolean} the opposite of the truthiness of the return from fn(value) 14 | */ 15 | export default curry(function notBy(fn, value) { 16 | return not(fn(value)); 17 | }); 18 | -------------------------------------------------------------------------------- /test/allTestsExist.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import {getFiles} from 'test/helpers/_methods'; 4 | 5 | test('if all the files in src have been tested', (t) => { 6 | const srcFiles = getFiles('./src'); 7 | const testFiles = getFiles('./test'); 8 | 9 | t.deepEqual(srcFiles, testFiles); 10 | }); 11 | 12 | test('if all the files in src/_utils have been tested', (t) => { 13 | const srcFiles = getFiles('./src/_utils'); 14 | const testFiles = getFiles('./test/_utils'); 15 | 16 | t.deepEqual(srcFiles, testFiles); 17 | }); 18 | -------------------------------------------------------------------------------- /test/lte.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import lte from 'src/lte'; 6 | 7 | test('if lte returns true when first numerical value is less than or equal to the second, false otherwise', (t) => { 8 | t.true(lte(5)(10)); 9 | t.true(lte(10)(10)); 10 | 11 | t.false(lte(12)(10)); 12 | }); 13 | 14 | test('if lte returns true when first string value is less than or equal to the second, false otherwise', (t) => { 15 | t.true(lte('bar')('foo')); 16 | t.true(lte('foo')('foo')); 17 | 18 | t.false(lte('baz')('bar')); 19 | }); 20 | -------------------------------------------------------------------------------- /test/gte.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import gte from 'src/gte'; 6 | 7 | test('if greater returns true when first numerical value is greater than or equal to the second, false otherwise', (t) => { 8 | t.true(gte(10)(5)); 9 | t.true(gte(10)(10)); 10 | 11 | t.false(gte(10)(12)); 12 | }); 13 | 14 | test('if gte returns true when first string value is greater than or equal to the second, false otherwise', (t) => { 15 | t.true(gte('foo')('bar')); 16 | t.true(gte('foo')('foo')); 17 | 18 | t.false(gte('bar')('baz')); 19 | }); 20 | -------------------------------------------------------------------------------- /src/remainder.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function remainder 6 | * 7 | * @description 8 | * divides the first value from the second and returns the remainder 9 | * 10 | * @param {number} numerator the number to divide 11 | * @param {number} denominator the number to divide from the first 12 | * @returns {number} the remainder of the division 13 | */ 14 | export default curry(function remainder(numerator, denominator) { 15 | const mod = numerator % denominator; 16 | 17 | return numerator > 0 ? ~~mod : Math.ceil(mod); 18 | }); 19 | -------------------------------------------------------------------------------- /src/bind.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function bind 6 | * 7 | * @description 8 | * bind the object passed to the fn as the "this" object, with optional binding of args 9 | * 10 | * @param {function} fn the function to bind 11 | * @param {*} object the object to bind to fn as the "this" 12 | * @param {Array<*>} [args=[]] arguments to partially bind to fn as parameters 13 | * @returns {function} the bound function 14 | */ 15 | export default curry(function bind(fn, object, args = []) { 16 | return fn.bind(object, ...args); 17 | }); 18 | -------------------------------------------------------------------------------- /src/equals.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import isEquivalent from './_utils/isEquivalent'; 6 | 7 | /** 8 | * @function equals 9 | * 10 | * @description 11 | * does the first value equal the second value in value (not strict equality) 12 | * 13 | * @param {*} firstValue the first value to compare 14 | * @param {*} secondValue the second value to compare 15 | * @returns {boolean} are the first and second value equivalent 16 | */ 17 | export default curry(function equals(firstValue, secondValue) { 18 | return isEquivalent(firstValue, secondValue); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unique.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import unique from 'src/unique'; 6 | 7 | test('if unique will reduce the array based on value', (t) => { 8 | const array = ['one', 'two', 'one', 'three', 'two']; 9 | 10 | const result = unique(array); 11 | 12 | t.deepEqual(result, ['one', 'two', 'three']); 13 | }); 14 | 15 | test('if unique will reduce the object based on value', (t) => { 16 | const object = {one: 1, two: 2, alsoOne: 1, three: 3, alsoTwo: 2}; 17 | 18 | const result = unique(object); 19 | 20 | t.deepEqual(result, {one: 1, two: 2, three: 3}); 21 | }); 22 | -------------------------------------------------------------------------------- /src/_utils/getObjectClass.js: -------------------------------------------------------------------------------- 1 | // utils 2 | import isNull from './isNull'; 3 | import isUndefined from './isUndefined'; 4 | 5 | /** 6 | * @function getObjectClass 7 | * 8 | * @description 9 | * get the object's class type 10 | * 11 | * @param {*} object the object to get the class of 12 | * @returns {string} the object's class 13 | */ 14 | export default function getObjectClass(object) { 15 | if (isNull(object)) { 16 | return 'Null'; 17 | } 18 | 19 | if (isUndefined(object)) { 20 | return 'Undefined'; 21 | } 22 | 23 | return Object.prototype.toString.call(object).slice(8, -1); 24 | } 25 | -------------------------------------------------------------------------------- /test/_utils/coalesceToArray.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import coalesceToArray from 'src/_utils/coalesceToArray'; 6 | 7 | test('if coalesceToArray will keep the value as-is if an array', (t) => { 8 | const value = ['foo']; 9 | 10 | const result = coalesceToArray(value); 11 | 12 | t.is(result, value); 13 | }); 14 | 15 | test('if coalesceToArray will make the value an array of that value when not an array', (t) => { 16 | const value = 'foo'; 17 | 18 | const result = coalesceToArray(value); 19 | 20 | t.not(result, value); 21 | t.deepEqual(result, [value]); 22 | }); 23 | -------------------------------------------------------------------------------- /src/findLast.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | import find from './find'; 4 | 5 | // utils 6 | import reverse from './_utils/reverse'; 7 | 8 | /** 9 | * @function findLast 10 | * 11 | * @description 12 | * find the last item that returns truthy based on the call to fn 13 | * 14 | * @param {function(*, number, Array<*>): *} fn the method to test with 15 | * @param {Array<*>} collection the collection of items to test 16 | * @returns {*} the item that matches, or undefined 17 | */ 18 | export default curry(function findLast(fn, collection) { 19 | return find(fn, reverse(collection)); 20 | }); 21 | -------------------------------------------------------------------------------- /test/partial.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import _ from 'lodash'; 4 | 5 | // src 6 | import partial from 'src/partial'; 7 | 8 | test('if partial will create a partial-application method', (t) => { 9 | const initialArgs = [1, 2, 3]; 10 | const additionalArgs = [4, 5]; 11 | 12 | const method = (a, b, c, d, e) => { 13 | return [a, b, c, d, e]; 14 | }; 15 | 16 | const partialed = partial(method, ...initialArgs); 17 | 18 | t.true(_.isFunction(partialed)); 19 | 20 | const result = partialed(...additionalArgs); 21 | 22 | t.deepEqual(result, [...initialArgs, ...additionalArgs]); 23 | }); 24 | -------------------------------------------------------------------------------- /test/not.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import not from 'src/not'; 6 | 7 | test('if not returns the falsiness of the truthy values passed', (t) => { 8 | t.false(not(123)); 9 | t.false(not(true)); 10 | t.false(not('foo')); 11 | t.false(not(/foo/)); 12 | t.false(not({})); 13 | t.false(not([])); 14 | t.false(not(new Map())); 15 | t.false(not(new Set())); 16 | }); 17 | 18 | test('if not returns the falsiness of the falsy values passed', (t) => { 19 | t.true(not(0)); 20 | t.true(not(false)); 21 | t.true(not('')); 22 | t.true(not(null)); 23 | t.true(not(undefined)); 24 | }); 25 | -------------------------------------------------------------------------------- /test/instanceOf.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import instanceOf from 'src/instanceOf'; 6 | 7 | test('if instanceOf returns false if object is falsy', (t) => { 8 | const object = null; 9 | 10 | t.false(instanceOf(Object)(object)); 11 | }); 12 | 13 | test('if instanceOf tests for direct constructor', (t) => { 14 | class Foo {} 15 | 16 | const object = new Foo(); 17 | 18 | t.true(instanceOf(Foo)(object)); 19 | }); 20 | 21 | test('if instanceOf tests up the ancestry tree', (t) => { 22 | class Foo {} 23 | 24 | const object = new Foo(); 25 | 26 | t.true(instanceOf(Object)(object)); 27 | }); 28 | -------------------------------------------------------------------------------- /src/empty.js: -------------------------------------------------------------------------------- 1 | // utils 2 | import is from './is'; 3 | 4 | /** 5 | * @function empty 6 | * 7 | * @description 8 | * return the empty version of the value passed 9 | * 10 | * @param {*} value the value to return an empty version of 11 | * @returns {*} the empty version of value 12 | */ 13 | export default function empty(value) { 14 | if (is(String, value)) { 15 | return ''; 16 | } 17 | 18 | if ( 19 | is(Array, value) 20 | || is(Object, value) 21 | || (typeof Map === 'function' && is(Map, value)) 22 | || (typeof Set === 'function' && is(Set, value)) 23 | ) { 24 | return new value.constructor(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/reject.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import compose from './compose'; 3 | import curry from './curry'; 4 | import filter from './filter'; 5 | import not from './not'; 6 | 7 | /** 8 | * @function reject 9 | * 10 | * @description 11 | * filter the collection based on items that when called with fn return false (opposite of filter) 12 | * 13 | * @param {function} fn the function to filter the collection by 14 | * @param {Array<*>|Object} collection the collection to filter 15 | * @returns {Array<*>|Object} the filtered collection 16 | */ 17 | export default curry(function reject(fn, collection) { 18 | return filter(compose(not, fn), collection); 19 | }); 20 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import {getFiles} from 'test/helpers/_methods'; 4 | 5 | // src 6 | import * as index from 'src/index'; 7 | 8 | test('if all methods are provided as named exports', (t) => { 9 | getFiles('./src').forEach((file) => { 10 | const key = file.replace('.js', ''); 11 | 12 | t.true(index.hasOwnProperty(key)); 13 | }); 14 | }); 15 | 16 | test('if default export contains all the individual files', (t) => { 17 | const kari = index.default; 18 | 19 | getFiles('./src').forEach((file) => { 20 | const key = file.replace('.js', ''); 21 | 22 | t.true(kari.hasOwnProperty(key)); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/findLastIndex.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | import findIndex from './findIndex'; 4 | 5 | // utils 6 | import reverse from './_utils/reverse'; 7 | 8 | /** 9 | * @function findLastIndex 10 | * 11 | * @description 12 | * find the last index of the first item that returns truthy based on the call to fn 13 | * 14 | * @param {function(*, number, Array<*>): *} fn the method to test with 15 | * @param {Array<*>} collection the collection of items to test 16 | * @returns {number} the last index of the item that matches, or -1 17 | */ 18 | export default curry(function findLastIndex(fn, collection) { 19 | return findIndex(fn, reverse(collection)); 20 | }); 21 | -------------------------------------------------------------------------------- /test/pipe.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import _ from 'lodash'; 4 | 5 | // src 6 | import pipe from 'src/pipe'; 7 | 8 | test('if pipe combines the function results passed from left to right', (t) => { 9 | const add = (a, b) => { 10 | return a + b; 11 | }; 12 | const square = (c) => { 13 | return c ** 2; 14 | }; 15 | const halve = (d) => { 16 | return d / 2; 17 | }; 18 | 19 | const piped = pipe(add, square, halve); 20 | 21 | t.true(_.isFunction(piped)); 22 | 23 | const args = [1, 2]; 24 | 25 | const result = piped(...args); 26 | const standardResult = halve(square(add(...args))); 27 | 28 | t.is(result, standardResult); 29 | }); 30 | -------------------------------------------------------------------------------- /webpack/webpack.config.minified.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const webpack = require('webpack'); 4 | const OptimizeJsPlugin = require('optimize-js-plugin'); 5 | 6 | const defaultConfig = require('./webpack.config'); 7 | 8 | module.exports = Object.assign({}, defaultConfig, { 9 | devtool: undefined, 10 | 11 | mode: 'production', 12 | 13 | output: Object.assign({}, defaultConfig.output, { 14 | filename: 'kari.min.js' 15 | }), 16 | 17 | plugins: defaultConfig.plugins.concat([ 18 | new webpack.LoaderOptionsPlugin({ 19 | debug: false, 20 | minimize: true 21 | }), 22 | new OptimizeJsPlugin({ 23 | sourceMap: false 24 | }) 25 | ]) 26 | }); 27 | -------------------------------------------------------------------------------- /test/remainder.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import remainder from 'src/remainder'; 6 | 7 | test('if remainder will get the remainder of the first number divided by the second number passed', (t) => { 8 | const first = 10; 9 | const second = 4; 10 | 11 | const result = remainder(first)(second); 12 | 13 | t.is(result, first % second); 14 | }); 15 | 16 | test('if remainder will get the remainder of the first number divided by the second number passed when the first number is negative', (t) => { 17 | const first = -10; 18 | const second = 4; 19 | 20 | const result = remainder(first)(second); 21 | 22 | t.is(result, first % second); 23 | }); 24 | -------------------------------------------------------------------------------- /src/equalsBy.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | import equals from './equals'; 4 | 5 | /** 6 | * @function equalsBy 7 | * 8 | * @description 9 | * are the values passed equal based on the same value equality comparison, but after some transformation 10 | * performed by fn 11 | * 12 | * @param {function} fn the transformation function 13 | * @param {*} firstValue the first value to compare 14 | * @param {*} secondValue the second value to compare 15 | * @returns {boolean} are the trnasformed first and second values equal 16 | */ 17 | export default curry(function equalsBy(fn, firstValue, secondValue) { 18 | return equals(fn(firstValue), fn(secondValue)); 19 | }); 20 | -------------------------------------------------------------------------------- /src/tryCatch.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function tryCatch 6 | * 7 | * @description 8 | * create a method that handles both successful and failed attempts on the arguments passed 9 | * 10 | * @param {function} tryFn the method to try to execute 11 | * @param {function} catchFn the method that will execute if tryFn fails 12 | * @returns {function(...Array<*>): *} the method that will handle the try / catch 13 | */ 14 | export default curry(function tryCatch(tryFn, catchFn) { 15 | return function(...args) { 16 | try { 17 | return tryFn(...args); 18 | } catch (error) { 19 | return catchFn(error, ...args); 20 | } 21 | }; 22 | }); 23 | -------------------------------------------------------------------------------- /src/ascend.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function ascend 6 | * 7 | * @description 8 | * create a comparator function that will ascending sort by the values returned from calling fn 9 | * 10 | * @param {function} fn the function to call on the values 11 | * @param {*} first the first value to call 12 | * @param {*} second the second value to call 13 | * @returns {number} the result of comparing the first and second values 14 | */ 15 | export default curry(function ascend(fn, first, second) { 16 | const firstValue = fn(first); 17 | const secondValue = fn(second); 18 | 19 | return firstValue < secondValue ? -1 : firstValue > secondValue ? 1 : 0; 20 | }); 21 | -------------------------------------------------------------------------------- /src/sortBy.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import coalesceToArray from './_utils/coalesceToArray'; 6 | 7 | /** 8 | * @function sortBy 9 | * 10 | * @param {function} fn the function to get the value from the iteration item with 11 | * @param {Array<*>} array the array to sort 12 | * @returns {Array<*>} the sorted array 13 | */ 14 | export default curry(function sortBy(fn, array) { 15 | let firstValue, secondValue; 16 | 17 | return [...coalesceToArray(array)].sort((first, second) => { 18 | firstValue = fn(first); 19 | secondValue = fn(second); 20 | 21 | return firstValue < secondValue ? -1 : firstValue > secondValue ? 1 : 0; 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/compose.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import _ from 'lodash'; 4 | 5 | // src 6 | import compose from 'src/compose'; 7 | 8 | test('if compose combines the function results passed from right to left', (t) => { 9 | const add = (a, b) => { 10 | return a + b; 11 | }; 12 | const square = (c) => { 13 | return c ** 2; 14 | }; 15 | const halve = (d) => { 16 | return d / 2; 17 | }; 18 | 19 | const composed = compose(halve, square, add); 20 | 21 | t.true(_.isFunction(composed)); 22 | 23 | const args = [1, 2]; 24 | 25 | const result = composed(...args); 26 | const standardResult = halve(square(add(...args))); 27 | 28 | t.is(result, standardResult); 29 | }); 30 | -------------------------------------------------------------------------------- /src/_utils/findInArray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function findInArray 3 | * 4 | * @description 5 | * find either the index or the item itself in the array 6 | * 7 | * @param {function} fn the function to determine the match 8 | * @param {Array<*>} array the array to search 9 | * @param {boolean} isIndex should the index be returned instead of the item 10 | * @returns {*} the item or index returned 11 | */ 12 | export default function findInArray(fn, array, isIndex) { 13 | let index = -1; 14 | 15 | while (++index < array.length) { 16 | if (fn(array[index], index, array)) { 17 | return isIndex ? index : array[index]; 18 | } 19 | } 20 | 21 | if (isIndex) { 22 | return -1; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/_utils/isEmpty.js: -------------------------------------------------------------------------------- 1 | // utils 2 | import isArray from './isArray'; 3 | import isString from './isString'; 4 | 5 | /** 6 | * @function isEmpty 7 | * 8 | * @description 9 | * is the object empty based on: 10 | * - is it truthy 11 | * - if an array, does it have length 12 | * - if an object, does it have keys 13 | * - is it anything else 14 | * 15 | * @param {*} object the object to test 16 | * @returns {boolean} is the item empty 17 | */ 18 | export default function isEmpty(object) { 19 | if (!object) { 20 | return true; 21 | } 22 | 23 | if (isArray(object) && !object.length) { 24 | return true; 25 | } 26 | 27 | return !isString(object) && !Object.keys(object).length; 28 | } 29 | -------------------------------------------------------------------------------- /src/descend.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function descend 6 | * 7 | * @description 8 | * create a comparator function that will ascending sort by the values returned from calling fn 9 | * 10 | * @param {function} fn the function to call on the values 11 | * @param {*} first the first value to call 12 | * @param {*} second the second value to call 13 | * @returns {number} the result of comparing the first and second values 14 | */ 15 | export default curry(function descend(fn, first, second) { 16 | const firstValue = fn(first); 17 | const secondValue = fn(second); 18 | 19 | return firstValue > secondValue ? -1 : firstValue < secondValue ? 1 : 0; 20 | }); 21 | -------------------------------------------------------------------------------- /test/partialRight.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import _ from 'lodash'; 4 | 5 | // src 6 | import partialRight from 'src/partialRight'; 7 | 8 | test('if partialRight will create a partial-application method where the additionalArgs come before the initialArgs when called', (t) => { 9 | const initialArgs = [1, 2, 3]; 10 | const additionalArgs = [4, 5]; 11 | 12 | const method = (a, b, c, d, e) => { 13 | return [a, b, c, d, e]; 14 | }; 15 | 16 | const partialed = partialRight(method, ...initialArgs); 17 | 18 | t.true(_.isFunction(partialed)); 19 | 20 | const result = partialed(...additionalArgs); 21 | 22 | t.deepEqual(result, [...additionalArgs, ...initialArgs]); 23 | }); 24 | -------------------------------------------------------------------------------- /src/sortWith.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import coalesceToArray from './_utils/coalesceToArray'; 6 | 7 | /** 8 | * @function sortWith 9 | * 10 | * @param {Array} fns the function to use as comparators (in order of comparison) 11 | * @param {Array<*>} array the array to sort 12 | * @returns {Array<*>} the sorted array 13 | */ 14 | export default curry(function sortBy(fns, array) { 15 | return [...coalesceToArray(array)].sort((first, second) => { 16 | let result = 0, 17 | index = -1; 18 | 19 | while (result === 0 && ++index < fns.length) { 20 | result = fns[index](first, second); 21 | } 22 | 23 | return result; 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/uncurry.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import isFunction from './_utils/isFunction'; 6 | 7 | /** 8 | * @function subtract 9 | * 10 | * @description 11 | * subtract two values 12 | * 13 | * @param {number} first the number to subtract 14 | * @param {number} second the number to subtract from first 15 | * @returns {number} the division of second from first 16 | */ 17 | export default curry(function uncurry(arity, fn) { 18 | return function(...args) { 19 | let index = 0, 20 | value = fn(args[index]); 21 | 22 | while (isFunction(value) && ++index < arity) { 23 | value = value(args[index]); 24 | } 25 | 26 | return value; 27 | }; 28 | }); 29 | -------------------------------------------------------------------------------- /test/sortWith.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import ascend from 'src/ascend'; 6 | import descend from 'src/descend'; 7 | import get from 'src/get'; 8 | import sortWith from 'src/sortWith'; 9 | 10 | test('if sortWith will return a new array sorted based on the values returned from the comparators passed', (t) => { 11 | const workers = [{name: 'Bill', salary: 40000}, {name: 'Alex', salary: 40000}, {name: 'Suzy', salary: 50000}]; 12 | 13 | const result = sortWith([descend(get('salary')), ascend(get('name'))])(workers); 14 | 15 | t.not(result, workers); 16 | 17 | t.deepEqual(result, [{name: 'Suzy', salary: 50000}, {name: 'Alex', salary: 40000}, {name: 'Bill', salary: 40000}]); 18 | }); 19 | -------------------------------------------------------------------------------- /src/_utils/reduceArray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function reduceArray 3 | * 4 | * @description 5 | * reduce the array to a new value based on the returns of the call with fn 6 | * 7 | * @param {function(*, *, number, Array<*>): *} fn the method to map with 8 | * @param {Array<*>} array the array of items to map 9 | * @param {*} value the initial value of the reduction 10 | * @returns {*} the reduced array 11 | */ 12 | export default function reduceArray(fn, array, value) { 13 | let index = -1; 14 | 15 | if (value === void 0) { 16 | value = array[0]; 17 | array = array.slice(1); 18 | } 19 | 20 | while (++index < array.length) { 21 | value = fn(value, array[index], index, array); 22 | } 23 | 24 | return value; 25 | } 26 | -------------------------------------------------------------------------------- /test/_utils/isEmpty.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import {ARRAY, BOOLEAN, EMPTY_STRING, NULL, OBJECT, UNDEFINED, ZERO} from 'test/helpers/_typeCheckValues'; 4 | 5 | // src 6 | import isEmpty from 'src/_utils/isEmpty'; 7 | 8 | test('if isEmpty returns true if the object is falsy', (t) => { 9 | t.true(isEmpty(NULL)); 10 | t.true(isEmpty(BOOLEAN)); 11 | t.true(isEmpty(ZERO)); 12 | t.true(isEmpty(EMPTY_STRING)); 13 | t.true(isEmpty(UNDEFINED)); 14 | }); 15 | 16 | test('if isEmpty returns true if the object is an array with no length', (t) => { 17 | t.true(isEmpty(ARRAY)); 18 | }); 19 | 20 | test('if isEmpty returns true if the object is an object without any keys', (t) => { 21 | t.true(isEmpty(OBJECT)); 22 | }); 23 | -------------------------------------------------------------------------------- /test/_utils/isNAN.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import { 4 | ARRAY, 5 | BOOLEAN, 6 | DECIMAL, 7 | FUNCTION, 8 | NAN, 9 | NEGATIVE_INTEGER, 10 | OBJECT, 11 | POSITIVE_INTEGER, 12 | STRING, 13 | UNDEFINED 14 | } from 'test/helpers/_typeCheckValues'; 15 | 16 | // src 17 | import isNAN from 'src/_utils/isNAN'; 18 | 19 | test('if isNAN tests if an object is a number', (t) => { 20 | t.false(isNAN(ARRAY)); 21 | t.false(isNAN(BOOLEAN)); 22 | t.false(isNAN(DECIMAL)); 23 | t.false(isNAN(FUNCTION)); 24 | t.false(isNAN(OBJECT)); 25 | t.false(isNAN(STRING)); 26 | t.false(isNAN(NEGATIVE_INTEGER)); 27 | t.false(isNAN(POSITIVE_INTEGER)); 28 | t.false(isNAN(UNDEFINED)); 29 | 30 | t.true(isNAN(NAN)); 31 | }); 32 | -------------------------------------------------------------------------------- /test/_utils/isObject.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import { 4 | ARRAY, 5 | BOOLEAN, 6 | DECIMAL, 7 | FUNCTION, 8 | NEGATIVE_INTEGER, 9 | OBJECT, 10 | POSITIVE_INTEGER, 11 | STRING, 12 | UNDEFINED 13 | } from 'test/helpers/_typeCheckValues'; 14 | 15 | // src 16 | import isObject from 'src/_utils/isObject'; 17 | 18 | test('if isObject tests if an object is a number', (t) => { 19 | t.false(isObject(ARRAY)); 20 | t.false(isObject(BOOLEAN)); 21 | t.false(isObject(DECIMAL)); 22 | t.false(isObject(FUNCTION)); 23 | t.false(isObject(STRING)); 24 | t.false(isObject(NEGATIVE_INTEGER)); 25 | t.false(isObject(POSITIVE_INTEGER)); 26 | t.false(isObject(UNDEFINED)); 27 | 28 | t.true(isObject(OBJECT)); 29 | }); 30 | -------------------------------------------------------------------------------- /test/_utils/isString.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import { 4 | ARRAY, 5 | BOOLEAN, 6 | DECIMAL, 7 | FUNCTION, 8 | NEGATIVE_INTEGER, 9 | OBJECT, 10 | POSITIVE_INTEGER, 11 | STRING, 12 | UNDEFINED 13 | } from 'test/helpers/_typeCheckValues'; 14 | 15 | // src 16 | import isString from 'src/_utils/isString'; 17 | 18 | test('if isString tests if an object is a string', (t) => { 19 | t.false(isString(ARRAY)); 20 | t.false(isString(BOOLEAN)); 21 | t.false(isString(DECIMAL)); 22 | t.false(isString(FUNCTION)); 23 | t.false(isString(NEGATIVE_INTEGER)); 24 | t.false(isString(POSITIVE_INTEGER)); 25 | t.false(isString(OBJECT)); 26 | t.false(isString(UNDEFINED)); 27 | 28 | t.true(isString(STRING)); 29 | }); 30 | -------------------------------------------------------------------------------- /src/_utils/findInObject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function findInObject 3 | * 4 | * @description 5 | * find either the key or the item itself in the array 6 | * 7 | * @param {function} fn the function to determine the match 8 | * @param {Object} object the object to search 9 | * @param {Array} keys the keys to iterate over 10 | * @param {boolean} isKey should the key be returned instead of the item 11 | * @returns {*} the item or key returned 12 | */ 13 | export default function findInObject(fn, object, keys, isKey) { 14 | let index = -1, 15 | key; 16 | 17 | while (++index < keys.length) { 18 | key = keys[index]; 19 | 20 | if (fn(object[key], key, object)) { 21 | return isKey ? key : object[key]; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/_utils/isFunction.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import { 4 | ARRAY, 5 | BOOLEAN, 6 | DECIMAL, 7 | FUNCTION, 8 | NEGATIVE_INTEGER, 9 | OBJECT, 10 | POSITIVE_INTEGER, 11 | STRING, 12 | UNDEFINED 13 | } from 'test/helpers/_typeCheckValues'; 14 | 15 | // src 16 | import isFunction from 'src/_utils/isFunction'; 17 | 18 | test('if isFunction tests if an object is a number', (t) => { 19 | t.false(isFunction(ARRAY)); 20 | t.false(isFunction(BOOLEAN)); 21 | t.false(isFunction(DECIMAL)); 22 | t.false(isFunction(OBJECT)); 23 | t.false(isFunction(STRING)); 24 | t.false(isFunction(NEGATIVE_INTEGER)); 25 | t.false(isFunction(POSITIVE_INTEGER)); 26 | t.false(isFunction(UNDEFINED)); 27 | 28 | t.true(isFunction(FUNCTION)); 29 | }); 30 | -------------------------------------------------------------------------------- /src/includes.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | import some from './some'; 4 | 5 | // utils 6 | import isFunction from './_utils/isFunction'; 7 | 8 | /** 9 | * @function includes 10 | * 11 | * @description 12 | * does the array or string include the value passed based on strict equality 13 | * 14 | * @param {*} value is the value passed contained in the collection 15 | * @param {Array<*>|Object|string} collection the collection to test if it contains the value 16 | * @returns {boolean} does collection contain value 17 | */ 18 | export default curry(function includes(value, collection) { 19 | return isFunction(collection.indexOf) 20 | ? !!~collection.indexOf(value) 21 | : some((valueToCompare) => valueToCompare === value, collection); 22 | }); 23 | -------------------------------------------------------------------------------- /src/uniqueBy.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | import filter from './filter'; 4 | 5 | /** 6 | * @function uniqueBy 7 | * 8 | * @description 9 | * filter down the collection to the unique values based on their return from fn 10 | * 11 | * @param {Array<*>|Object} collection the collection to filter 12 | * @returns {Array<*>|Object} the filtered collection 13 | */ 14 | export default curry(function uniqueBy(fn, collection) { 15 | let values = [], 16 | value; 17 | 18 | return filter((item, key) => { 19 | value = fn(item, key, collection); 20 | 21 | const doesNotExist = !~values.indexOf(value); 22 | 23 | if (doesNotExist) { 24 | values.push(value); 25 | } 26 | 27 | return doesNotExist; 28 | }, collection); 29 | }); 30 | -------------------------------------------------------------------------------- /src/reduceRight.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | import reduce from './reduce'; 4 | 5 | // utils 6 | import reverse from './_utils/reverse'; 7 | 8 | /** 9 | * @function reduceRight 10 | * 11 | * @description 12 | * reduce the collection to a single value based on fn, starting from the end and working backwards 13 | * 14 | * @param {function(*, *, (number|string), (Array<*>|Object)): *} the method to reduce the collection with 15 | * @param {Array<*>|Object} collection the collection to reduce 16 | * @param {*} initialValue the initial value to start the reduction from 17 | * @returns {*} the reduced value 18 | */ 19 | export default curry(function reduceRight(fn, initialValue, collection) { 20 | return reduce(fn, initialValue, reverse(collection)); 21 | }); 22 | -------------------------------------------------------------------------------- /test/_utils/isNumber.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import { 4 | ARRAY, 5 | BOOLEAN, 6 | DECIMAL, 7 | FUNCTION, 8 | NAN, 9 | NEGATIVE_INTEGER, 10 | OBJECT, 11 | POSITIVE_INTEGER, 12 | STRING, 13 | UNDEFINED 14 | } from 'test/helpers/_typeCheckValues'; 15 | 16 | // src 17 | import isNumber from 'src/_utils/isNumber'; 18 | 19 | test('if isNumber tests if an object is a number', (t) => { 20 | t.false(isNumber(ARRAY)); 21 | t.false(isNumber(BOOLEAN)); 22 | t.false(isNumber(FUNCTION)); 23 | t.false(isNumber(NAN)); 24 | t.false(isNumber(STRING)); 25 | t.false(isNumber(OBJECT)); 26 | t.false(isNumber(UNDEFINED)); 27 | 28 | t.true(isNumber(DECIMAL)); 29 | t.true(isNumber(NEGATIVE_INTEGER)); 30 | t.true(isNumber(POSITIVE_INTEGER)); 31 | }); 32 | -------------------------------------------------------------------------------- /test/_utils/isNull.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import { 4 | ARRAY, 5 | BOOLEAN, 6 | DECIMAL, 7 | FUNCTION, 8 | NAN, 9 | NEGATIVE_INTEGER, 10 | NULL, 11 | OBJECT, 12 | POSITIVE_INTEGER, 13 | STRING, 14 | UNDEFINED 15 | } from 'test/helpers/_typeCheckValues'; 16 | 17 | // src 18 | import isNull from 'src/_utils/isNull'; 19 | 20 | test('if isNull tests if an object is null', (t) => { 21 | t.false(isNull(ARRAY)); 22 | t.false(isNull(BOOLEAN)); 23 | t.false(isNull(DECIMAL)); 24 | t.false(isNull(FUNCTION)); 25 | t.false(isNull(OBJECT)); 26 | t.false(isNull(STRING)); 27 | t.false(isNull(NEGATIVE_INTEGER)); 28 | t.false(isNull(POSITIVE_INTEGER)); 29 | t.false(isNull(UNDEFINED)); 30 | t.false(isNull(NAN)); 31 | 32 | t.true(isNull(NULL)); 33 | }); 34 | -------------------------------------------------------------------------------- /src/modulo.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import isInteger from './_utils/isInteger'; 6 | import isPositiveInteger from './_utils/isPositiveInteger'; 7 | 8 | /** 9 | * @function remainder 10 | * 11 | * @description 12 | * get the mathematical modulo of the numerator and modulus 13 | * 14 | * @param {number} numerator the number to divide 15 | * @param {number} modulus the number to divide from the first 16 | * @returns {number} the remainder of the division 17 | */ 18 | export default curry(function modulo(numerator, modulus) { 19 | if (!isInteger(numerator) || !isPositiveInteger(modulus)) { 20 | return NaN; 21 | } 22 | 23 | const remainder = numerator % modulus; 24 | 25 | return ~~(remainder >= 0 ? remainder : remainder + modulus); 26 | }); 27 | -------------------------------------------------------------------------------- /test/_utils/isInteger.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import { 4 | ARRAY, 5 | BOOLEAN, 6 | DECIMAL, 7 | FUNCTION, 8 | NAN, 9 | NEGATIVE_INTEGER, 10 | OBJECT, 11 | POSITIVE_INTEGER, 12 | STRING, 13 | UNDEFINED 14 | } from 'test/helpers/_typeCheckValues'; 15 | 16 | // src 17 | import isInteger from 'src/_utils/isInteger'; 18 | 19 | test('if isInteger tests if an object is an integer', (t) => { 20 | t.false(isInteger(ARRAY)); 21 | t.false(isInteger(BOOLEAN)); 22 | t.false(isInteger(DECIMAL)); 23 | t.false(isInteger(FUNCTION)); 24 | t.false(isInteger(NAN)); 25 | t.false(isInteger(STRING)); 26 | t.false(isInteger(OBJECT)); 27 | t.false(isInteger(UNDEFINED)); 28 | 29 | t.true(isInteger(NEGATIVE_INTEGER)); 30 | t.true(isInteger(POSITIVE_INTEGER)); 31 | }); 32 | -------------------------------------------------------------------------------- /src/get.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import getPath from './_utils/getPath'; 6 | import isEmpty from './_utils/isEmpty'; 7 | 8 | /** 9 | * @function get 10 | * 11 | * @description 12 | * get the value at the path passed in the object 13 | * 14 | * @param {(Array|number|string)} path the path to get the value with 15 | * @param {Array<*>|Object} object the object to get the value from 16 | * @returns {*} the value at the path passed 17 | */ 18 | export default curry(function get(path, object) { 19 | const cleanPath = getPath(path); 20 | 21 | if (!object || isEmpty(cleanPath)) { 22 | return; 23 | } 24 | 25 | const childSource = object[cleanPath[0]]; 26 | 27 | return cleanPath.length === 1 ? childSource : get(cleanPath.slice(1), childSource); 28 | }); 29 | -------------------------------------------------------------------------------- /test/uniqueBy.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import uniqueBy from 'src/uniqueBy'; 6 | 7 | test('if uniqueBy will reduce the array based on unique returns from the function call', (t) => { 8 | const array = ['one', 'two', 'three', 'four', 'five', 'six']; 9 | const fn = (value) => { 10 | return value.length === 3; 11 | }; 12 | 13 | const result = uniqueBy(fn)(array); 14 | 15 | t.deepEqual(result, ['one', 'three']); 16 | }); 17 | 18 | test('if uniqueBy will reduce the object based on unique returns from the function call', (t) => { 19 | const object = {one: 1, two: 2, three: 3, four: 4, five: 5, six: 6}; 20 | const fn = (value, key) => { 21 | return key.length === 3; 22 | }; 23 | 24 | const result = uniqueBy(fn)(object); 25 | 26 | t.deepEqual(result, {one: 1, three: 3}); 27 | }); 28 | -------------------------------------------------------------------------------- /src/_utils/reduceObject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function reduceObject 3 | * 4 | * @description 5 | * reduce the object to a value based on the returns of the call with fn 6 | * 7 | * @param {function(*, string, Object): *} fn the method to map with 8 | * @param {Object} object the object of items to map 9 | * @param {*} value the initial value of the reduction 10 | * @param {Array} keys the keys of the object 11 | * @returns {Object} the reduced object 12 | */ 13 | export default function reduceObject(fn, object, value, keys) { 14 | let index = -1, 15 | key; 16 | 17 | if (value === void 0) { 18 | value = object[keys[0]]; 19 | keys = keys.slice(1); 20 | } 21 | 22 | while (++index < keys.length) { 23 | key = keys[index]; 24 | 25 | value = fn(value, object[key], key, object); 26 | } 27 | 28 | return value; 29 | } 30 | -------------------------------------------------------------------------------- /src/find.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import coalesceToArray from './_utils/coalesceToArray'; 6 | import findInArray from './_utils/findInArray'; 7 | import findInObject from './_utils/findInObject'; 8 | import isObject from './_utils/isObject'; 9 | 10 | /** 11 | * @function find 12 | * 13 | * @description 14 | * find the first item that returns truthy based on the call to fn 15 | * 16 | * @param {function(*, number, Array<*>): *} fn the method to test with 17 | * @param {Array<*>} collection the collection of items to test 18 | * @returns {*} the item that matches, or undefined 19 | */ 20 | export default curry(function find(fn, collection) { 21 | return isObject(collection) 22 | ? findInObject(fn, collection, Object.keys(collection), false) 23 | : findInArray(fn, coalesceToArray(collection), false); 24 | }); 25 | -------------------------------------------------------------------------------- /test/bind.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import _ from 'lodash'; 4 | 5 | // src 6 | import bind from 'src/bind'; 7 | 8 | test('if bind will bind the object to the fn passed', (t) => { 9 | const fn = function() { 10 | return this; 11 | }; 12 | const object = {}; 13 | 14 | const result = bind(fn)(object); 15 | 16 | t.true(_.isFunction(result)); 17 | 18 | t.is(result(), object); 19 | }); 20 | 21 | test('if bind will bind the object to the fn passed with additional arguments', (t) => { 22 | const fn = function(...args) { 23 | return { 24 | args, 25 | object: this 26 | }; 27 | }; 28 | const object = {}; 29 | const args = ['foo', 'bar']; 30 | 31 | const result = bind(fn)(object, args); 32 | 33 | t.true(_.isFunction(result)); 34 | 35 | t.deepEqual(result(), { 36 | args, 37 | object 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/_utils/isUndefined.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import { 4 | ARRAY, 5 | BOOLEAN, 6 | DECIMAL, 7 | FUNCTION, 8 | NAN, 9 | NEGATIVE_INTEGER, 10 | NULL, 11 | OBJECT, 12 | POSITIVE_INTEGER, 13 | STRING, 14 | UNDEFINED 15 | } from 'test/helpers/_typeCheckValues'; 16 | 17 | // src 18 | import isUndefined from 'src/_utils/isUndefined'; 19 | 20 | test('if isUndefined tests if an object is undefined', (t) => { 21 | t.false(isUndefined(ARRAY)); 22 | t.false(isUndefined(BOOLEAN)); 23 | t.false(isUndefined(DECIMAL)); 24 | t.false(isUndefined(FUNCTION)); 25 | t.false(isUndefined(OBJECT)); 26 | t.false(isUndefined(STRING)); 27 | t.false(isUndefined(NEGATIVE_INTEGER)); 28 | t.false(isUndefined(POSITIVE_INTEGER)); 29 | t.false(isUndefined(NAN)); 30 | t.false(isUndefined(NULL)); 31 | 32 | t.true(isUndefined(UNDEFINED)); 33 | }); 34 | -------------------------------------------------------------------------------- /test/includes.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import includes from 'src/includes'; 6 | 7 | test('if includes checks the array to see if the value is contained', (t) => { 8 | const value = {}; 9 | const array = ['foo', value, 123]; 10 | 11 | t.true(includes(value)(array)); 12 | t.false(includes('bar')(array)); 13 | }); 14 | 15 | test('if includes checks the object to see if the value is contained', (t) => { 16 | const value = 'bar'; 17 | const object = { 18 | foo: 'foo', 19 | bar: value, 20 | baz: 'baz' 21 | }; 22 | 23 | t.true(includes(value)(object)); 24 | t.false(includes('blah')(object)); 25 | }); 26 | 27 | test('if includes checks the string to see if the value is contained', (t) => { 28 | const value = 'f'; 29 | const string = 'foo'; 30 | 31 | t.true(includes(value)(string)); 32 | t.false(includes('b')(string)); 33 | }); 34 | -------------------------------------------------------------------------------- /src/findIndex.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import coalesceToArray from './_utils/coalesceToArray'; 6 | import findInArray from './_utils/findInArray'; 7 | import findInObject from './_utils/findInObject'; 8 | import isObject from './_utils/isObject'; 9 | 10 | /** 11 | * @function findIndex 12 | * 13 | * @description 14 | * find the index of the first item that returns truthy based on the call to fn 15 | * 16 | * @param {function(*, number, Array<*>): *} fn the method to test with 17 | * @param {Array<*>} collection the collection of items to test 18 | * @returns {number} the index of the item that matches, or -1 19 | */ 20 | export default curry(function findIndex(fn, collection) { 21 | return isObject(collection) 22 | ? findInObject(fn, collection, Object.keys(collection), true) 23 | : findInArray(fn, coalesceToArray(collection), true); 24 | }); 25 | -------------------------------------------------------------------------------- /test/_utils/isPositiveInteger.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import { 4 | ARRAY, 5 | BOOLEAN, 6 | DECIMAL, 7 | FUNCTION, 8 | NAN, 9 | NEGATIVE_INTEGER, 10 | OBJECT, 11 | POSITIVE_INTEGER, 12 | STRING, 13 | UNDEFINED 14 | } from 'test/helpers/_typeCheckValues'; 15 | 16 | // src 17 | import isPositiveInteger from 'src/_utils/isPositiveInteger'; 18 | 19 | test('if isPositiveInteger tests if an object is an integer', (t) => { 20 | t.false(isPositiveInteger(ARRAY)); 21 | t.false(isPositiveInteger(BOOLEAN)); 22 | t.false(isPositiveInteger(DECIMAL)); 23 | t.false(isPositiveInteger(FUNCTION)); 24 | t.false(isPositiveInteger(NAN)); 25 | t.false(isPositiveInteger(NEGATIVE_INTEGER)); 26 | t.false(isPositiveInteger(STRING)); 27 | t.false(isPositiveInteger(OBJECT)); 28 | t.false(isPositiveInteger(UNDEFINED)); 29 | 30 | t.true(isPositiveInteger(POSITIVE_INTEGER)); 31 | }); 32 | -------------------------------------------------------------------------------- /test/_utils/isPlaceholder.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import { 4 | ARRAY, 5 | BOOLEAN, 6 | DECIMAL, 7 | FUNCTION, 8 | NAN, 9 | NEGATIVE_INTEGER, 10 | OBJECT, 11 | POSITIVE_INTEGER, 12 | STRING, 13 | UNDEFINED 14 | } from 'test/helpers/_typeCheckValues'; 15 | 16 | // src 17 | import isPlaceholder from 'src/_utils/isPlaceholder'; 18 | import __ from 'src/__'; 19 | 20 | test('if isPlaceholder tests if an object is the placeholder value', (t) => { 21 | t.false(isPlaceholder(ARRAY)); 22 | t.false(isPlaceholder(BOOLEAN)); 23 | t.false(isPlaceholder(FUNCTION)); 24 | t.false(isPlaceholder(NAN)); 25 | t.false(isPlaceholder(STRING)); 26 | t.false(isPlaceholder(OBJECT)); 27 | t.false(isPlaceholder(UNDEFINED)); 28 | t.false(isPlaceholder(DECIMAL)); 29 | t.false(isPlaceholder(NEGATIVE_INTEGER)); 30 | t.false(isPlaceholder(POSITIVE_INTEGER)); 31 | 32 | t.true(isPlaceholder(__)); 33 | }); 34 | -------------------------------------------------------------------------------- /src/insert.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import coalesceToArray from './_utils/coalesceToArray'; 6 | import isObject from './_utils/isObject'; 7 | 8 | /** 9 | * @function insert 10 | * 11 | * @description 12 | * insert a new item into the collection of items 13 | * 14 | * @param {number|string} key the index to assign into 15 | * @param {Array<*>|*} newItems the new item(s) to add into the collection at index 16 | * @param {Array<*>} collection the collection to insert into 17 | * @returns {Array<*>} the collection with the new items inserted 18 | */ 19 | export default curry(function insert(key, newItems, collection) { 20 | if (isObject(collection)) { 21 | return { 22 | ...collection, 23 | [key]: newItems, 24 | }; 25 | } 26 | 27 | const array = coalesceToArray(collection); 28 | 29 | return [...array.slice(0, key), ...coalesceToArray(newItems), ...array.slice(key)]; 30 | }); 31 | -------------------------------------------------------------------------------- /test/is.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import is from 'src/is'; 6 | 7 | test('if is checks if a value has the constructor passed when the constructor is a function', (t) => { 8 | t.true(is(String)('foo')); 9 | t.false(is(String)(123)); 10 | 11 | t.true(is(Number)(123)); 12 | t.false(is(Number)('foo')); 13 | 14 | t.true(is(Array)([])); 15 | t.false(is(Array)({})); 16 | 17 | t.true(is(Object)({})); 18 | t.false(is(Object)([])); 19 | 20 | t.true(is(Function)(() => {})); 21 | t.false(is(Function)('foo')); 22 | }); 23 | 24 | test('if is checks for strict equality when the constructor passed is not a function', (t) => { 25 | t.true(is('foo')('foo')); 26 | t.false(is('foo')('bar')); 27 | 28 | t.true(is(123)(123)); 29 | t.false(is(123)(321)); 30 | }); 31 | 32 | test('if is checks if both the constructor and the value are NaN', (t) => { 33 | t.true(is(NaN)(NaN)); 34 | t.false(is(NaN)(123)); 35 | }); 36 | -------------------------------------------------------------------------------- /src/pluck.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | import reduce from './reduce'; 4 | 5 | // utils 6 | import coalesceToArray from './_utils/coalesceToArray'; 7 | import isObject from './_utils/isObject'; 8 | 9 | /** 10 | * @function pluck 11 | * 12 | * @description 13 | * get the values from the collection of objects when the key is present in the object 14 | * 15 | * @param {string} key the key to find in the objects in items 16 | * @param {Array} collection the collection of items to get from 17 | * @returns {Array<*>} the values at key in the collection of objects 18 | */ 19 | export default curry(function pluck(key, collection) { 20 | return reduce( 21 | (pluckedItems, item) => { 22 | if (item.hasOwnProperty(key)) { 23 | pluckedItems.push(item[key]); 24 | } 25 | 26 | return pluckedItems; 27 | }, 28 | [], 29 | isObject(collection) ? collection : coalesceToArray(collection) 30 | ); 31 | }); 32 | -------------------------------------------------------------------------------- /src/is.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import isFunction from './_utils/isFunction'; 6 | import isNAN from './_utils/isNAN'; 7 | 8 | /** 9 | * @function is 10 | * 11 | * @description 12 | * is the value an instance of the constructor passed, or if undefined / null is it equal 13 | * 14 | * @param {function|null|undefined} Constructor constructor function to test value against 15 | * @param {*} value the value to test 16 | * @returns {boolean} is the value an instance of the Constructor 17 | */ 18 | export default curry(function is(Constructor, value) { 19 | if (value === Constructor) { 20 | return true; 21 | } 22 | 23 | if (isFunction(Constructor)) { 24 | /* eslint-disable eqeqeq */ 25 | return ( 26 | value != null 27 | /* eslint-enable */ 28 | && (value === Constructor || value.constructor === Constructor) 29 | ); 30 | } 31 | 32 | return isNAN(Constructor) && isNAN(value); 33 | }); 34 | -------------------------------------------------------------------------------- /src/reduce.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import coalesceToArray from './_utils/coalesceToArray'; 6 | import isObject from './_utils/isObject'; 7 | import reduceArray from './_utils/reduceArray'; 8 | import reduceObject from './_utils/reduceObject'; 9 | 10 | /** 11 | * @function reduce 12 | * 13 | * @description 14 | * reduce the collection to a single value based on fn 15 | * 16 | * @param {function(*, *, (number|string), (Array<*>|Object)): *} the method to reduce the collection with 17 | * @param {Array<*>|Object} collection the collection to reduce 18 | * @param {*} initialValue the initial value to start the reduction from 19 | * @returns {*} the reduced value 20 | */ 21 | export default curry(function reduce(fn, initialValue, collection) { 22 | return isObject(collection) 23 | ? reduceObject(fn, collection, initialValue, Object.keys(collection)) 24 | : reduceArray(fn, coalesceToArray(collection), initialValue); 25 | }); 26 | -------------------------------------------------------------------------------- /test/take.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import take from 'src/take'; 6 | 7 | test('if take will get the first n number of items in the array', (t) => { 8 | const items = [1, 2, 3, 4, 5]; 9 | const n = 2; 10 | 11 | const result = take(n)(items); 12 | 13 | t.is(result.length, n); 14 | t.deepEqual(result, items.slice(0, n)); 15 | }); 16 | 17 | test('if take will return an empty array when the size requested is zero', (t) => { 18 | const items = [1, 2, 3, 4, 5]; 19 | const n = 0; 20 | 21 | const result = take(n)(items); 22 | 23 | t.is(result.length, n); 24 | t.deepEqual(result, []); 25 | }); 26 | 27 | test('if take will get the first n number of items in the object', (t) => { 28 | const items = {one: 1, two: 2, three: 3, four: 4, five: 5}; 29 | const n = 2; 30 | 31 | const result = take(n)(items); 32 | 33 | t.is(Object.keys(result).length, n); 34 | t.deepEqual(result, { 35 | one: 1, 36 | two: 2 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/rest.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import rest from 'src/rest'; 6 | 7 | test('if take will get the last n number of items in the array', (t) => { 8 | const items = [1, 2, 3, 4, 5]; 9 | const n = 2; 10 | 11 | const result = rest(n)(items); 12 | 13 | t.is(result.length, n); 14 | t.deepEqual(result, items.slice(items.length - n)); 15 | }); 16 | 17 | test('if take will return an empty array when the size is zero', (t) => { 18 | const items = [1, 2, 3, 4, 5]; 19 | const n = 0; 20 | 21 | const result = rest(n)(items); 22 | 23 | t.is(result.length, n); 24 | t.deepEqual(result, []); 25 | }); 26 | 27 | test('if take will get the last n number of items in the object', (t) => { 28 | const items = {one: 1, two: 2, three: 3, four: 4, five: 5}; 29 | const n = 2; 30 | 31 | const result = rest(n)(items); 32 | 33 | t.is(Object.keys(result).length, n); 34 | t.deepEqual(result, { 35 | four: 4, 36 | five: 5 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/_utils/reduceArray.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import reduceArray from 'src/_utils/reduceArray'; 6 | 7 | test('if reduceArray will perform a reduction based on the items and initialValue passed', (t) => { 8 | const sum = (total, num) => { 9 | return total + num; 10 | }; 11 | 12 | const items = [1, 2, 3, 4, 5]; 13 | const initialValue = 0; 14 | 15 | const result = reduceArray(sum, items, initialValue); 16 | const expectedResult = items.reduce(sum, initialValue); 17 | 18 | t.is(result, expectedResult); 19 | }); 20 | 21 | test('if reduceArray will perform a reduction based on the items passed and assume initialValue is the first item when not given', (t) => { 22 | const sum = (total, num) => { 23 | return total + num; 24 | }; 25 | 26 | const items = [1, 2, 3, 4, 5]; 27 | 28 | const result = reduceArray(sum, items, undefined); 29 | const expectedResult = items.reduce(sum); 30 | 31 | t.is(result, expectedResult); 32 | }); 33 | -------------------------------------------------------------------------------- /test/flatten.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import flatten from 'src/flatten'; 6 | 7 | test('if flatten will flatten the nested array', (t) => { 8 | const value = [1, [2, 3], [[4, 5], 6]]; 9 | 10 | const result = flatten(value); 11 | 12 | t.deepEqual(result, [1, 2, 3, 4, 5, 6]); 13 | }); 14 | 15 | test('if flatten will flatten the nested object', (t) => { 16 | const value = { 17 | one: 1, 18 | two: { 19 | three: 3, 20 | four: 4 21 | }, 22 | five: { 23 | six: { 24 | seven: 7 25 | }, 26 | eight: 8 27 | } 28 | }; 29 | 30 | const result = flatten(value); 31 | 32 | t.deepEqual(result, { 33 | one: 1, 34 | three: 3, 35 | four: 4, 36 | seven: 7, 37 | eight: 8 38 | }); 39 | }); 40 | 41 | test('if flatten will return an array with the item when the item is not an array or object', (t) => { 42 | const value = 'foo'; 43 | 44 | const result = flatten(value); 45 | 46 | t.deepEqual(result, [value]); 47 | }); 48 | -------------------------------------------------------------------------------- /test/tap.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import sinon from 'sinon'; 4 | 5 | // src 6 | import tap from 'src/tap'; 7 | 8 | test('if tap will call the fn passed with the value before returning the value', (t) => { 9 | const spy = sinon.spy(); 10 | const value = {}; 11 | 12 | const result = tap(spy)(value); 13 | 14 | t.true(spy.calledOnce); 15 | t.true(spy.calledWith(value)); 16 | 17 | t.is(result, value); 18 | }); 19 | 20 | test('if tap will handle being sent multiple args', (t) => { 21 | const spy = sinon.spy(); 22 | const value = {}; 23 | const value2 = {}; 24 | 25 | const result = tap(spy)(value, value2); 26 | 27 | t.true(spy.calledOnce); 28 | t.true(spy.calledWith(value, value2)); 29 | 30 | t.is(result, value); 31 | }); 32 | 33 | test('if tap will handle being sent no args', (t) => { 34 | const spy = sinon.spy(); 35 | 36 | const result = tap(spy)(); 37 | 38 | t.true(spy.calledOnce); 39 | t.is(spy.firstCall.args.length, 0); 40 | 41 | t.is(result, undefined); 42 | }); 43 | -------------------------------------------------------------------------------- /test/_utils/reverse.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import reverse from 'src/_utils/reverse'; 6 | 7 | test('if reverse will result in the same thing as .reverse() for an array', (t) => { 8 | const numbers = [1, 2, 3]; 9 | const strings = ['foo', 'bar', 'baz']; 10 | const objects = [{foo: 'foo'}, {bar: 'bar'}, {baz: 'baz'}]; 11 | 12 | const numberResult = reverse(numbers); 13 | 14 | t.deepEqual(numberResult, numbers.reverse()); 15 | 16 | const stringResult = reverse(strings); 17 | 18 | t.deepEqual(stringResult, strings.reverse()); 19 | 20 | const objectResult = reverse(objects); 21 | 22 | t.deepEqual(objectResult, objects.reverse()); 23 | }); 24 | 25 | test('if reverse will reverse an object based on the keys', (t) => { 26 | const original = { 27 | foo: 'foo', 28 | bar: 'bar', 29 | baz: 'baz' 30 | }; 31 | 32 | t.deepEqual(Object.keys(original), ['foo', 'bar', 'baz']); 33 | 34 | const reversed = reverse(original); 35 | 36 | t.deepEqual(Object.keys(reversed), ['baz', 'bar', 'foo']); 37 | 38 | t.deepEqual(original, reversed); 39 | }); 40 | -------------------------------------------------------------------------------- /test/has.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import has from 'src/has'; 6 | 7 | test('if has correctly determines if an array has an index', (t) => { 8 | const array = ['foo']; 9 | 10 | t.true(has(0)(array)); 11 | t.false(has(1)(array)); 12 | }); 13 | 14 | test('if has correctly determines if an object has a key', (t) => { 15 | const object = { 16 | foo: 'bar' 17 | }; 18 | 19 | t.true(has('foo')(object)); 20 | t.false(has('bar')(object)); 21 | }); 22 | 23 | test('if has returns false if the object is null or undefined', (t) => { 24 | t.false(has('foo')(null)); 25 | t.false(has('bar')(undefined)); 26 | }); 27 | 28 | test('if has correctly determines when the nested path exists in the array', (t) => { 29 | const array = ['foo', {bar: 'baz'}]; 30 | 31 | t.true(has('[1].bar')(array)); 32 | t.false(has('[2].baz')(array)); 33 | }); 34 | 35 | test('if has correctly determines when the nested path exists in the object', (t) => { 36 | const object = {foo: ['bar', {baz: 'baz'}]}; 37 | 38 | t.true(has('foo[1].baz')(object)); 39 | t.false(has('foo[0].baz')(object)); 40 | }); 41 | -------------------------------------------------------------------------------- /test/ascend.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import ascend from 'src/ascend'; 6 | 7 | test('if ascend will return 1 when the value returned from the first call is greater than the second', (t) => { 8 | const getter = (string) => { 9 | return string.length; 10 | }; 11 | const first = 'foo'; 12 | const second = 'ba'; 13 | 14 | const result = ascend(getter)(first)(second); 15 | 16 | t.is(result, 1); 17 | }); 18 | 19 | test('if ascend will return -1 when the value returned from the first call is less than the second', (t) => { 20 | const getter = (string) => { 21 | return string.length; 22 | }; 23 | const first = 'fo'; 24 | const second = 'bar'; 25 | 26 | const result = ascend(getter)(first)(second); 27 | 28 | t.is(result, -1); 29 | }); 30 | 31 | test('if ascend will return 0 when the value returned from the first call is equal to the second', (t) => { 32 | const getter = (string) => { 33 | return string.length; 34 | }; 35 | const first = 'foo'; 36 | const second = 'bar'; 37 | 38 | const result = ascend(getter)(first)(second); 39 | 40 | t.is(result, 0); 41 | }); 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tony Quetano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/descend.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import descend from 'src/descend'; 6 | 7 | test('if descend will return -1 when the value returned from the first call is greater than the second', (t) => { 8 | const getter = (string) => { 9 | return string.length; 10 | }; 11 | const first = 'foo'; 12 | const second = 'ba'; 13 | 14 | const result = descend(getter)(first)(second); 15 | 16 | t.is(result, -1); 17 | }); 18 | 19 | test('if descend will return 1 when the value returned from the first call is less than the second', (t) => { 20 | const getter = (string) => { 21 | return string.length; 22 | }; 23 | const first = 'fo'; 24 | const second = 'bar'; 25 | 26 | const result = descend(getter)(first)(second); 27 | 28 | t.is(result, 1); 29 | }); 30 | 31 | test('if descend will return 0 when the value returned from the first call is equal to the second', (t) => { 32 | const getter = (string) => { 33 | return string.length; 34 | }; 35 | const first = 'foo'; 36 | const second = 'bar'; 37 | 38 | const result = descend(getter)(first)(second); 39 | 40 | t.is(result, 0); 41 | }); 42 | -------------------------------------------------------------------------------- /test/arity.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import arity from 'src/arity'; 6 | 7 | test('if arity will create a method that limits the arity to the value passed', (t) => { 8 | const fn = (a, b, c) => { 9 | return [a, b, c]; 10 | }; 11 | const size = 2; 12 | const args = [1, 2, 3]; 13 | 14 | const result = arity(size)(fn)(...args); 15 | 16 | t.notDeepEqual(result, args); 17 | 18 | const expectedResult = args.map((arg, index) => { 19 | return index < size ? arg : undefined; 20 | }); 21 | 22 | t.deepEqual(result, expectedResult); 23 | }); 24 | 25 | test('if arity will create a method that extends the arity to the value passed (curried)', (t) => { 26 | const fn = (a, b, c, ...otherArgs) => { 27 | return [a, b, c, ...otherArgs]; 28 | }; 29 | const size = 4; 30 | const args = [1, 2, 3]; 31 | 32 | const result = arity(size)(fn)(...args); 33 | 34 | t.notDeepEqual(result, args); 35 | 36 | let expectedResult = [], 37 | index = -1; 38 | 39 | while (++index < size) { 40 | expectedResult[index] = args[index]; 41 | } 42 | 43 | t.deepEqual(result, expectedResult); 44 | }); 45 | -------------------------------------------------------------------------------- /src/arity.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | /** 5 | * @function getCompleteArgs 6 | * 7 | * @description 8 | * get the complete list of arguments based on arity, even if it is larger than the 9 | * original args list 10 | * 11 | * @param {Array<*>} args the args passed 12 | * @param {number} arity the number of arguments to return in the array 13 | * @returns {Array<*>} the complete list of args 14 | */ 15 | export function getCompleteArgs(args, arity) { 16 | let index = -1, 17 | completeArgs = []; 18 | 19 | while (++index < arity) { 20 | completeArgs[index] = args[index]; 21 | } 22 | 23 | return completeArgs; 24 | } 25 | 26 | /** 27 | * @function arity 28 | * 29 | * @description 30 | * create a function that will pass the arity of args requested to fn 31 | * 32 | * @param {number} arity the intended arity of fn 33 | * @param {function} fn the fn to define the arity of 34 | * @returns {function(...Array<*>): *} the function that will apply the arity of args requested to fn 35 | */ 36 | export default curry(function arity(arity, fn) { 37 | return function(...args) { 38 | return fn.apply(this, getCompleteArgs(args, arity)); 39 | }; 40 | }); 41 | -------------------------------------------------------------------------------- /webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const eslintFriendlyFormatter = require('eslint-friendly-formatter'); 4 | const path = require('path'); 5 | const webpack = require('webpack'); 6 | 7 | const ROOT = path.join(__dirname, '..'); 8 | 9 | module.exports = { 10 | devtool: '#source-map', 11 | 12 | entry: [path.join(ROOT, 'src', 'index.js')], 13 | 14 | mode: 'development', 15 | 16 | module: { 17 | rules: [ 18 | { 19 | enforce: 'pre', 20 | include: [path.join(ROOT, 'src')], 21 | loader: 'eslint-loader', 22 | options: { 23 | configFile: '.eslintrc', 24 | failOnError: true, 25 | failOnWarning: false, 26 | formatter: eslintFriendlyFormatter, 27 | }, 28 | test: /\.js$/, 29 | }, 30 | { 31 | include: [path.join(ROOT, 'src')], 32 | loader: 'babel-loader', 33 | test: /\.js$/, 34 | }, 35 | ], 36 | }, 37 | 38 | output: { 39 | filename: 'kari.js', 40 | library: 'kari', 41 | libraryTarget: 'umd', 42 | path: path.join(ROOT, 'dist'), 43 | umdNamedDefine: true, 44 | }, 45 | 46 | plugins: [new webpack.EnvironmentPlugin(['NODE_ENV'])], 47 | }; 48 | -------------------------------------------------------------------------------- /test/every.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import _ from 'lodash'; 4 | 5 | // src 6 | import every from 'src/every'; 7 | 8 | const isEven = (number) => { 9 | return number % 2 === 0; 10 | }; 11 | 12 | test('if every returns true if all items in the array match the method', (t) => { 13 | const items = [1, 2, 3, 4, 5]; 14 | 15 | const result = every(_.isNumber)(items); 16 | 17 | t.true(result); 18 | }); 19 | 20 | test('if every returns true if all items in the array match the method', (t) => { 21 | const items = [1, 2, 3, 4, 5]; 22 | 23 | const result = every(isEven)(items); 24 | 25 | t.false(result); 26 | }); 27 | 28 | test('if every does the same for objects as it does for arrays', (t) => { 29 | const falseItems = { 30 | foo: 0, 31 | bar: 1, 32 | baz: 2 33 | }; 34 | const trueItems = { 35 | foo: 0, 36 | bar: 2, 37 | baz: 4 38 | }; 39 | 40 | t.false(every(isEven, falseItems)); 41 | t.true(every(isEven, trueItems)); 42 | }); 43 | 44 | test('if every returns true when the array or object is empty', (t) => { 45 | const arrayResult = every(isEven, []); 46 | 47 | t.true(arrayResult); 48 | 49 | const objectResult = every(isEven, {}); 50 | 51 | t.true(objectResult); 52 | }); 53 | -------------------------------------------------------------------------------- /src/endsWith.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import isString from './_utils/isString'; 6 | 7 | /** 8 | * @function endsWithString 9 | * 10 | * @param {string} endingValue the value that the collection should end with 11 | * @param {string} valueToTest the value to test for ending with endingValue 12 | * @returns {boolean} does valueToTest end with endingValue 13 | */ 14 | function endsWithString(endingValue, valueToTest) { 15 | const position = valueToTest.length - endingValue.length; 16 | const lastIndexOfValue = valueToTest.lastIndexOf(endingValue); 17 | 18 | return !!~lastIndexOfValue && lastIndexOfValue === position; 19 | } 20 | 21 | /** 22 | * @function endsWith 23 | * 24 | * @param {*} endingValue the value that the collection should end with 25 | * @param {Array<*>|string} valueToTest the value to test for ending with endingValue 26 | * @returns {boolean} does valueToTest end with endingValue 27 | */ 28 | export default curry(function endsWith(endingValue, valueToTest) { 29 | if (!valueToTest.length) { 30 | return false; 31 | } 32 | 33 | return isString(valueToTest) 34 | ? endsWithString(endingValue, valueToTest) 35 | : valueToTest[valueToTest.length - 1] === endingValue; 36 | }); 37 | -------------------------------------------------------------------------------- /src/_utils/reverse.js: -------------------------------------------------------------------------------- 1 | // utils 2 | import coalesceToArray from './coalesceToArray'; 3 | import isObject from './isObject'; 4 | 5 | /** 6 | * @function reverseArray 7 | * 8 | * @description 9 | * immutably reverse the order of an array 10 | * 11 | * @param {Array<*>} array the array to reverse 12 | * @returns {Array<*>} the reversed array 13 | */ 14 | function reverseArray(array) { 15 | let index = array.length, 16 | newArray = []; 17 | 18 | while (index--) { 19 | newArray.push(array[index]); 20 | } 21 | 22 | return newArray; 23 | } 24 | 25 | /** 26 | * @function reverseObject 27 | * 28 | * @description 29 | * immutably reverse the order of an object 30 | * 31 | * @param {Object} object the object to reverse 32 | * @returns {Object} the reversed object 33 | */ 34 | function reverseObject(object) { 35 | const keys = Object.keys(object); 36 | 37 | let index = keys.length, 38 | newObject = {}, 39 | key; 40 | 41 | while (index--) { 42 | key = keys[index]; 43 | 44 | newObject[key] = object[key]; 45 | } 46 | 47 | return newObject; 48 | } 49 | 50 | export default function reverse(collection) { 51 | return isObject(collection) ? reverseObject(collection) : reverseArray(coalesceToArray(collection)); 52 | } 53 | -------------------------------------------------------------------------------- /test/map.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import map from 'src/map'; 6 | 7 | test('if map will map the values resulting from the function to a new array', (t) => { 8 | const items = [1, 2, 3, 4, 5]; 9 | const method = (item, index) => { 10 | return { 11 | [index]: item 12 | }; 13 | }; 14 | 15 | const result = map(method)(items); 16 | const expectedResult = items.map(method); 17 | 18 | t.not(result, items); 19 | t.deepEqual(result, expectedResult); 20 | }); 21 | 22 | test('if map does the same thing for objects as arrays', (t) => { 23 | const items = { 24 | foo: 'bar', 25 | bar: 'baz', 26 | baz: 'foo' 27 | }; 28 | const method = (value, key) => { 29 | return key; 30 | }; 31 | 32 | const result = map(method, items); 33 | 34 | t.not(result, items); 35 | t.deepEqual(result, { 36 | foo: 'foo', 37 | bar: 'bar', 38 | baz: 'baz' 39 | }); 40 | }); 41 | 42 | test('if map returns the items passed mapped as an array if not an object or array', (t) => { 43 | const items = 'foo'; 44 | const method = (item, index) => { 45 | return { 46 | [index]: item 47 | }; 48 | }; 49 | 50 | const result = map(method, items); 51 | 52 | t.deepEqual(result, [{0: items}]); 53 | }); 54 | -------------------------------------------------------------------------------- /test/uncurry.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import _ from 'lodash'; 4 | 5 | // src 6 | import uncurry from 'src/uncurry'; 7 | import curry from 'src/curry'; 8 | 9 | test('if uncurry will return a method that will remove currying on the original curried function and instead accept all normal parameters', (t) => { 10 | const originalMethod = (a, b, c, d) => { 11 | return [a, b, c, d]; 12 | }; 13 | 14 | const curried = curry(originalMethod); 15 | 16 | const first = 1; 17 | const second = 2; 18 | const third = 3; 19 | const fourth = 4; 20 | 21 | const testCurried = curried(first)(second)(third)(fourth); 22 | 23 | t.deepEqual(testCurried, [first, second, third, fourth]); 24 | 25 | const uncurried = uncurry(originalMethod.length)(curried); 26 | 27 | t.true(_.isFunction(uncurried)); 28 | 29 | t.deepEqual(uncurried(first, second, third, fourth), [first, second, third, fourth]); 30 | 31 | t.throws(() => { 32 | uncurried(first)(second)(third)(fourth); 33 | }); 34 | }); 35 | 36 | test('if uncurry will return the value before the arity completes if the value returned from the call is not a function', (t) => { 37 | const arity = 4; 38 | const value = 'foo'; 39 | 40 | const result = uncurry(arity)(_.identity)(value); 41 | 42 | t.is(result, value); 43 | }); 44 | -------------------------------------------------------------------------------- /test/forEach.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import _ from 'lodash'; 4 | import sinon from 'sinon'; 5 | 6 | // src 7 | import forEach from 'src/forEach'; 8 | 9 | test('if forEach will iterate over each item in the array and call the method', (t) => { 10 | const method = sinon.spy(); 11 | 12 | const items = [1, 2, 3, 4, 5]; 13 | 14 | forEach(method)(items); 15 | 16 | t.is(method.callCount, items.length); 17 | }); 18 | 19 | test('if forEach will return the items after iterations for chainability', (t) => { 20 | const items = [1, 2, 3, 4, 5]; 21 | 22 | const result = forEach(_.noop, items); 23 | 24 | t.is(result, items); 25 | }); 26 | 27 | test('if forEach will do the same for objects as arrays', (t) => { 28 | const items = { 29 | foo: 'bar', 30 | bar: 'baz', 31 | baz: 'foo' 32 | }; 33 | 34 | const method = sinon.spy(); 35 | 36 | const result = forEach(method, items); 37 | 38 | t.is(method.callCount, Object.keys(items).length); 39 | t.is(result, items); 40 | }); 41 | 42 | test('if forEach will return the original items as an array if not an object or array', (t) => { 43 | const items = 'foo'; 44 | 45 | const method = sinon.spy(); 46 | 47 | const result = forEach(method, items); 48 | 49 | t.is(method.callCount, 1); 50 | t.deepEqual(result, [items]); 51 | }); 52 | -------------------------------------------------------------------------------- /test/pick.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import pick from 'src/pick'; 6 | 7 | test('if pick will pick the indices out of the array', (t) => { 8 | const items = [1, 2, 3, 4, 5, 6]; 9 | const keys = [1, 4]; 10 | 11 | const result = pick(keys)(items); 12 | 13 | t.deepEqual( 14 | result, 15 | keys.map((index) => { 16 | return items[index]; 17 | }) 18 | ); 19 | }); 20 | 21 | test('if pick will pick the keys out of the object', (t) => { 22 | const items = { 23 | foo: 'bar', 24 | bar: 'baz', 25 | baz: 'foo' 26 | }; 27 | const keys = ['foo', 'baz']; 28 | 29 | const result = pick(keys)(items); 30 | 31 | t.deepEqual(result, { 32 | foo: items.foo, 33 | baz: items.baz 34 | }); 35 | }); 36 | 37 | test('if pick only applies the keys that exist in the original object', (t) => { 38 | const items = { 39 | foo: 'bar', 40 | bar: 'baz' 41 | }; 42 | const keys = ['foo', 'baz']; 43 | 44 | const result = pick(keys, items); 45 | 46 | t.deepEqual(result, { 47 | foo: 'bar' 48 | }); 49 | }); 50 | 51 | test('if pick returns an empty object if it is not an object or array', (t) => { 52 | const items = 'foo'; 53 | const keys = ['foo', 'baz']; 54 | 55 | const result = pick(keys, items); 56 | 57 | t.deepEqual(result, {}); 58 | }); 59 | -------------------------------------------------------------------------------- /src/entries.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import reduce from './reduce'; 3 | 4 | // utils 5 | import isFunction from './_utils/isFunction'; 6 | 7 | /** 8 | * @function getEntriesFromIterable 9 | * 10 | * @description 11 | * get the pairs of [key, value] for the items in the iterable collection 12 | * 13 | * @param {Map|Set} collection the collection to get the entries of 14 | * @returns {Array>} the entries of the collection 15 | */ 16 | function getEntriesFromIterable(collection) { 17 | if (isFunction(Array.from)) { 18 | return Array.from(collection.entries()); 19 | } 20 | 21 | let entries = []; 22 | 23 | collection.forEach((value, key) => { 24 | entries.push([key, value]); 25 | }); 26 | 27 | return entries; 28 | } 29 | 30 | /** 31 | * @function entries 32 | * 33 | * @description 34 | * get the pairs of [key, value] for the items in the collection 35 | * 36 | * @param {Array<*>|Map|Object|Set} collection the collection to get the entries of 37 | * @returns {Array>} the entries of the collection 38 | */ 39 | export default function entries(collection) { 40 | if (!collection) { 41 | return []; 42 | } 43 | 44 | return isFunction(collection.entries) 45 | ? getEntriesFromIterable(collection) 46 | : reduce((entries, value, key) => [...entries, [key, value]], [], collection); 47 | } 48 | -------------------------------------------------------------------------------- /test/tryCatch.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import _ from 'lodash'; 4 | import sinon from 'sinon'; 5 | 6 | // src 7 | import tryCatch from 'src/tryCatch'; 8 | 9 | test('if tryCatch will fire only tryFn if there are no errors', (t) => { 10 | const tryFn = sinon.spy(JSON, 'parse'); 11 | const catchFn = sinon.spy(_, 'identity'); 12 | 13 | const value = '{"foo": "bar"}'; 14 | 15 | const result = tryCatch(tryFn, catchFn)(value); 16 | 17 | t.true(tryFn.calledOnce); 18 | t.true(tryFn.calledWith(value)); 19 | 20 | t.true(catchFn.notCalled); 21 | 22 | t.deepEqual(result, { 23 | foo: 'bar' 24 | }); 25 | 26 | tryFn.restore(); 27 | catchFn.restore(); 28 | }); 29 | 30 | test('if tryCatch will fire tryFn and then catchFn if there is an error', (t) => { 31 | const tryFn = sinon.spy(JSON, 'parse'); 32 | const catchFn = sinon.spy(_, 'identity'); 33 | 34 | const value = {foo: 'bar'}; 35 | 36 | const result = tryCatch(tryFn, catchFn)(value); 37 | 38 | t.true(tryFn.calledOnce); 39 | t.true(tryFn.calledWith(value)); 40 | 41 | t.true(catchFn.calledOnce); 42 | 43 | const args = catchFn.firstCall.args; 44 | 45 | t.is(args.length, 2); 46 | 47 | t.true(_.isError(args[0])); 48 | t.is(args[1], value); 49 | 50 | t.is(result, args[0]); 51 | 52 | tryFn.restore(); 53 | catchFn.restore(); 54 | }); 55 | -------------------------------------------------------------------------------- /test/_utils/getObjectClass.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import { 4 | ARRAY, 5 | BOOLEAN, 6 | DECIMAL, 7 | FUNCTION, 8 | NAN, 9 | NULL, 10 | OBJECT, 11 | POSITIVE_INTEGER, 12 | STRING, 13 | UNDEFINED 14 | } from 'test/helpers/_typeCheckValues'; 15 | 16 | // src 17 | import getObjectClass from 'src/_utils/getObjectClass'; 18 | 19 | test('if getObjectClass returns the correct string if null', (t) => { 20 | const result = getObjectClass(NULL); 21 | 22 | t.is(result, 'Null'); 23 | }); 24 | 25 | test('if getObjectClass returns the correct string if undefined', (t) => { 26 | const result = getObjectClass(UNDEFINED); 27 | 28 | t.is(result, 'Undefined'); 29 | }); 30 | 31 | test('if getObjectClass returns the correct string if a specific object class', (t) => { 32 | const array = getObjectClass(ARRAY); 33 | const boolean = getObjectClass(BOOLEAN); 34 | const decimal = getObjectClass(DECIMAL); 35 | const fn = getObjectClass(FUNCTION); 36 | const nan = getObjectClass(NAN); 37 | const object = getObjectClass(OBJECT); 38 | const integer = getObjectClass(POSITIVE_INTEGER); 39 | const string = getObjectClass(STRING); 40 | 41 | t.is(array, 'Array'); 42 | t.is(boolean, 'Boolean'); 43 | t.is(decimal, 'Number'); 44 | t.is(fn, 'Function'); 45 | t.is(nan, 'Number'); 46 | t.is(object, 'Object'); 47 | t.is(integer, 'Number'); 48 | t.is(string, 'String'); 49 | }); 50 | -------------------------------------------------------------------------------- /webpack/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | 6 | const defaultConfig = require('./webpack.config'); 7 | 8 | const PORT = 3000; 9 | const ROOT = path.join(__dirname, '..'); 10 | 11 | module.exports = Object.assign({}, defaultConfig, { 12 | devServer: { 13 | contentBase: './dist', 14 | host: 'localhost', 15 | inline: true, 16 | lazy: false, 17 | noInfo: false, 18 | port: PORT, 19 | quiet: false, 20 | stats: { 21 | colors: true, 22 | progress: true, 23 | }, 24 | watchOptions: { 25 | ignored: /node_modules/, 26 | }, 27 | }, 28 | 29 | entry: [path.join(ROOT, 'DEV_ONLY', 'index.js')], 30 | 31 | module: Object.assign({}, defaultConfig.module, { 32 | rules: defaultConfig.module.rules.map((rule) => { 33 | if (rule.loader !== 'babel-loader') { 34 | return rule; 35 | } 36 | 37 | return Object.assign({}, rule, { 38 | include: rule.include.concat([path.join(ROOT, 'DEV_ONLY')]), 39 | options: Object.assign({}, rule.options, { 40 | presets: ['react'], 41 | }), 42 | }); 43 | }), 44 | }), 45 | 46 | output: Object.assign({}, defaultConfig.output, { 47 | publicPath: `http://localhost:${PORT}/`, 48 | }), 49 | 50 | plugins: defaultConfig.plugins.concat([new HtmlWebpackPlugin()]), 51 | }); 52 | -------------------------------------------------------------------------------- /test/_utils/reduceObject.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import reduceObject from 'src/_utils/reduceObject'; 6 | 7 | test('if reduceObject will perform a reduction based on the items and initialValue passed', (t) => { 8 | const sum = (total, num) => { 9 | return total + num; 10 | }; 11 | 12 | const items = { 13 | one: 1, 14 | two: 2, 15 | three: 3, 16 | four: 4, 17 | five: 5 18 | }; 19 | const initialValue = 0; 20 | const keys = Object.keys(items); 21 | 22 | const result = reduceObject(sum, items, initialValue, keys); 23 | 24 | let index = -1, 25 | expectedResult = 0; 26 | 27 | while (++index < keys.length) { 28 | expectedResult += items[keys[index]]; 29 | } 30 | 31 | t.is(result, expectedResult); 32 | }); 33 | 34 | test('if reduceObject will perform a reduction based on the items passed and assume initialValue is the first item when not given', (t) => { 35 | const sum = (total, num) => { 36 | return total + num; 37 | }; 38 | 39 | const items = { 40 | one: 1, 41 | two: 2, 42 | three: 3, 43 | four: 4, 44 | five: 5 45 | }; 46 | const keys = Object.keys(items); 47 | 48 | const result = reduceObject(sum, items, undefined, keys); 49 | 50 | let index = 0, 51 | expectedResult = items[keys[0]]; 52 | 53 | while (++index < keys.length) { 54 | expectedResult += items[keys[index]]; 55 | } 56 | 57 | t.is(result, expectedResult); 58 | }); 59 | -------------------------------------------------------------------------------- /test/empty.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import empty from 'src/empty'; 6 | 7 | test('if empty returns an empty array if the value passed is an array', (t) => { 8 | const value = ['foo', 'bar', 'baz']; 9 | 10 | const result = empty(value); 11 | 12 | t.deepEqual(result, []); 13 | }); 14 | 15 | test('if empty returns an empty object if the value passed is an object', (t) => { 16 | const value = { 17 | foo: 'bar', 18 | bar: 'baz' 19 | }; 20 | 21 | const result = empty(value); 22 | 23 | t.deepEqual(result, {}); 24 | }); 25 | 26 | test('if empty returns an empty string if the value passed is a string', (t) => { 27 | const value = 'foo'; 28 | 29 | const result = empty(value); 30 | 31 | t.deepEqual(result, ''); 32 | }); 33 | 34 | test('if empty returns an empty map if the value passed is a map', (t) => { 35 | const value = new Map().set('foo', 'bar'); 36 | 37 | const result = empty(value); 38 | 39 | t.deepEqual(result, new Map()); 40 | }); 41 | 42 | test('if empty returns an set map if the value passed is a set', (t) => { 43 | const value = new Set().add('foo'); 44 | 45 | const result = empty(value); 46 | 47 | t.deepEqual(result, new Set()); 48 | }); 49 | 50 | test('if empty returns undefined if the value passed is anything else', (t) => { 51 | const number = 123; 52 | const regexp = /foo/; 53 | 54 | t.deepEqual(empty(number), undefined); 55 | t.deepEqual(empty(regexp), undefined); 56 | }); 57 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "development": { 4 | "presets": [ 5 | [ 6 | "env", 7 | { 8 | "exclude": ["transform-es2015-typeof-symbol"], 9 | "loose": true, 10 | "modules": false 11 | } 12 | ], 13 | "stage-2" 14 | ] 15 | }, 16 | "es": { 17 | "presets": [ 18 | [ 19 | "env", 20 | { 21 | "exclude": ["transform-es2015-typeof-symbol"], 22 | "loose": true, 23 | "modules": false 24 | } 25 | ], 26 | "stage-2" 27 | ] 28 | }, 29 | "lib": { 30 | "presets": [ 31 | [ 32 | "env", 33 | { 34 | "exclude": ["transform-es2015-typeof-symbol"], 35 | "loose": true 36 | } 37 | ], 38 | "stage-2" 39 | ] 40 | }, 41 | "production": { 42 | "presets": [ 43 | [ 44 | "env", 45 | { 46 | "exclude": ["transform-es2015-typeof-symbol"], 47 | "loose": true, 48 | "modules": false 49 | } 50 | ], 51 | "stage-2" 52 | ] 53 | }, 54 | "test": { 55 | "presets": [ 56 | [ 57 | "env", 58 | { 59 | "exclude": ["transform-es2015-typeof-symbol"], 60 | "loose": true 61 | } 62 | ], 63 | "stage-2" 64 | ] 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/set.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import getPath from './_utils/getPath'; 6 | import isArray from './_utils/isArray'; 7 | import isEmpty from './_utils/isEmpty'; 8 | import isNumber from './_utils/isNumber'; 9 | import isObject from './_utils/isObject'; 10 | 11 | /** 12 | * @function set 13 | * 14 | * @description 15 | * create a new collection based on the original, deeply setting the value 16 | * 17 | * @param {Array|number|string} path the path to deeply assign to 18 | * @param {*} value the value to assign 19 | * @param {Array<*>|Object} collection the collection to deeply assign to 20 | * @returns {Array<*>|Object} the collection clone, with the value deeply assigned 21 | */ 22 | function set(path, value, collection) { 23 | const cleanPath = getPath(path); 24 | 25 | if (isEmpty(cleanPath)) { 26 | return collection; 27 | } 28 | 29 | let destination = isArray(collection) ? [...collection] : {...collection}; 30 | 31 | if (cleanPath.length === 1) { 32 | destination[cleanPath[0]] = value; 33 | 34 | return destination; 35 | } 36 | 37 | const childPath = cleanPath[0]; 38 | const childSource = collection[childPath]; 39 | const descentantSource = 40 | isArray(childSource) || isObject(childSource) ? childSource : isNumber(cleanPath[1]) ? [] : {}; 41 | 42 | destination[childPath] = set(cleanPath.slice(1), value, descentantSource); 43 | 44 | return destination; 45 | } 46 | 47 | export default curry(set); 48 | -------------------------------------------------------------------------------- /test/insert.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import insert from 'src/insert'; 6 | 7 | test('if insert will add an item to the middle of the array at the index specified', (t) => { 8 | const array = [1, 2, 3]; 9 | const item = 'x'; 10 | const index = 2; 11 | 12 | const result = insert(index)(item)(array); 13 | 14 | t.not(result, array); 15 | t.deepEqual(result, [...array.slice(0, index), item, ...array.slice(index)]); 16 | }); 17 | 18 | test('if insert will add multiple items if the item is an array', (t) => { 19 | const array = [1, 2, 3]; 20 | const items = ['x', 'y']; 21 | const index = 2; 22 | 23 | const result = insert(index)(items)(array); 24 | 25 | t.not(result, array); 26 | t.deepEqual(result, [...array.slice(0, index), ...items, ...array.slice(index)]); 27 | }); 28 | 29 | test('if insert will add the item to the object', (t) => { 30 | const object = { 31 | foo: 'bar' 32 | }; 33 | const item = 'baz'; 34 | const key = 'bar'; 35 | 36 | const result = insert(key)(item)(object); 37 | 38 | t.not(result, object); 39 | t.deepEqual(result, { 40 | ...object, 41 | [key]: item 42 | }); 43 | }); 44 | 45 | test('if insert will convert items to an array and insert the new value if it is not already an array', (t) => { 46 | const value = 'foo'; 47 | const item = 'bar'; 48 | const index = 0; 49 | 50 | const result = insert(index)(item)(value); 51 | 52 | t.not(result, value); 53 | t.deepEqual(result, [item, value]); 54 | }); 55 | -------------------------------------------------------------------------------- /test/some.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import _ from 'lodash'; 4 | 5 | // src 6 | import some from 'src/some'; 7 | 8 | const isEven = (number) => { 9 | return number % 2 === 0; 10 | }; 11 | 12 | const isZero = (number) => { 13 | return number === 0; 14 | }; 15 | 16 | test('if some returns true if all items in the array match the method', (t) => { 17 | const items = [1, 2, 3, 4, 5]; 18 | 19 | const result = some(_.isNumber)(items); 20 | 21 | t.true(result); 22 | }); 23 | 24 | test('if some returns true if some items in the array match the method', (t) => { 25 | const items = [1, 2, 3, 4, 5]; 26 | 27 | const result = some(isEven)(items); 28 | 29 | t.true(result); 30 | }); 31 | 32 | test('if some returns false if no items in the array match the method', (t) => { 33 | const items = [1, 2, 3, 4, 5]; 34 | 35 | const result = some(isZero)(items); 36 | 37 | t.false(result); 38 | }); 39 | 40 | test('if some does the same for objects as it does arrays', (t) => { 41 | const truthyItems = { 42 | foo: 1, 43 | bar: 2, 44 | baz: 3 45 | }; 46 | const falsyItems = { 47 | foo: 1, 48 | bar: 2, 49 | baz: 3 50 | }; 51 | 52 | const truthyResult = some(isEven, truthyItems); 53 | 54 | t.true(truthyResult); 55 | 56 | const falsyResult = some(isZero, falsyItems); 57 | 58 | t.false(falsyResult); 59 | }); 60 | 61 | test('if some returns true when the array or object is empty', (t) => { 62 | t.true(some(isEven, [])); 63 | t.true(some(isEven, {})); 64 | }); 65 | -------------------------------------------------------------------------------- /test/endsWith.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import endsWith from 'src/endsWith'; 6 | 7 | test('if endsWith will return true if the last item in the array matches the value', (t) => { 8 | const value = {}; 9 | const array = [{}, {}, value]; 10 | 11 | const result = endsWith(value)(array); 12 | 13 | t.true(result); 14 | }); 15 | 16 | test('if endsWith will return false if the last item in the array does not match the value', (t) => { 17 | const value = {}; 18 | const array = [{}, value, {}]; 19 | 20 | const result = endsWith(value)(array); 21 | 22 | t.false(result); 23 | }); 24 | 25 | test('if endsWith returns false when the array is empty', (t) => { 26 | const value = {}; 27 | const array = []; 28 | 29 | const result = endsWith(value)(array); 30 | 31 | t.false(result); 32 | }); 33 | 34 | test('if endsWith will return true if the last letter in the word matches the value', (t) => { 35 | const value = 'f'; 36 | const string = `oo${value}`; 37 | 38 | const result = endsWith(value)(string); 39 | 40 | t.true(result); 41 | }); 42 | 43 | test('if endsWith will return false if the last letter in the word does not match the value', (t) => { 44 | const value = 'f'; 45 | const string = `o${value}o`; 46 | 47 | const result = endsWith(value)(string); 48 | 49 | t.false(result); 50 | }); 51 | 52 | test('if endsWith returns false when the string is empty', (t) => { 53 | const value = {}; 54 | const string = ''; 55 | 56 | const result = endsWith(value)(string); 57 | 58 | t.false(result); 59 | }); 60 | -------------------------------------------------------------------------------- /test/merge.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import merge from 'src/merge'; 6 | 7 | test('if merge will merge two arrays of equal length', (t) => { 8 | const first = [1, 2, 3]; 9 | const second = ['foo', 'bar', 'baz']; 10 | 11 | const result = merge(first, second); 12 | 13 | t.deepEqual(result, second); 14 | }); 15 | 16 | test('if merge will merge two arrays where the first is longer than the second', (t) => { 17 | const first = [1, 2, 3, 4, 5]; 18 | const second = ['foo', 'bar', 'baz']; 19 | 20 | const result = merge(first, second); 21 | 22 | t.deepEqual(result, [...second, ...first.slice(3)]); 23 | }); 24 | 25 | test('if merge will merge two arrays where the first is shorter than the second', (t) => { 26 | const first = [1, 2, 3]; 27 | const second = ['foo', 'bar', 'baz', 'blah']; 28 | 29 | const result = merge(first, second); 30 | 31 | t.deepEqual(result, second); 32 | }); 33 | 34 | test('if merge will merge two objects with alike keys', (t) => { 35 | const first = { 36 | foo: 'foo', 37 | bar: 'bar' 38 | }; 39 | const second = { 40 | foo: 'bar', 41 | bar: 'baz' 42 | }; 43 | 44 | const result = merge(first, second); 45 | 46 | t.deepEqual(result, second); 47 | }); 48 | 49 | test('if merge will merge two objects with different keys', (t) => { 50 | const first = { 51 | foo: 'foo', 52 | bar: 'bar' 53 | }; 54 | const second = { 55 | baz: 'baz' 56 | }; 57 | 58 | const result = merge(first, second); 59 | 60 | t.deepEqual(result, { 61 | ...first, 62 | ...second 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/pluck.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import pluck from 'src/pluck'; 6 | 7 | test('if pluck will pluck the value at the given key from the objects in the array', (t) => { 8 | const key = 'foo'; 9 | 10 | const collection = new Array(10).fill(key).map((keyToAssign, index) => { 11 | return { 12 | [keyToAssign]: index 13 | }; 14 | }); 15 | 16 | const result = pluck(key)(collection); 17 | const expectedResult = new Array(10).fill(key).map((ignored, index) => { 18 | return index; 19 | }); 20 | 21 | t.deepEqual(result, expectedResult); 22 | }); 23 | 24 | test('if pluck will not push the value if the key does not exist', (t) => { 25 | const key = 'foo'; 26 | 27 | const collection = new Array(10).fill(key).map((keyToAssign, index) => { 28 | return index === 5 29 | ? {} 30 | : { 31 | [keyToAssign]: index 32 | }; 33 | }); 34 | 35 | const result = pluck(key, collection); 36 | const expectedResult = new Array(10) 37 | .fill(key) 38 | .map((ignored, index) => { 39 | return index; 40 | }) 41 | .filter((value) => { 42 | return value !== 5; 43 | }); 44 | 45 | t.deepEqual(result, expectedResult); 46 | }); 47 | 48 | test('if pluck will pluck the value at the given key from the object passed', (t) => { 49 | const key = 'bar'; 50 | 51 | const collection = { 52 | foo: { 53 | bar: 'baz' 54 | }, 55 | bar: { 56 | baz: 'baz' 57 | } 58 | }; 59 | 60 | const result = pluck(key, collection); 61 | 62 | t.deepEqual(result, ['baz']); 63 | }); 64 | -------------------------------------------------------------------------------- /test/get.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import get from 'src/get'; 6 | 7 | test('if get will get the shallow key value', (t) => { 8 | const value = 'foo'; 9 | 10 | const key = 'shallow'; 11 | const object = { 12 | shallow: value, 13 | deeply: [ 14 | { 15 | nested: [ 16 | { 17 | value 18 | } 19 | ] 20 | } 21 | ] 22 | }; 23 | 24 | const result = get(key)(object); 25 | 26 | t.is(result, value); 27 | }); 28 | 29 | test('if get will get the deep key value', (t) => { 30 | const value = 'foo'; 31 | 32 | const key = 'deeply[0].nested[0].value'; 33 | const object = { 34 | shallow: value, 35 | deeply: [ 36 | { 37 | nested: [ 38 | { 39 | value 40 | } 41 | ] 42 | } 43 | ] 44 | }; 45 | 46 | const result = get(key)(object); 47 | 48 | t.is(result, value); 49 | }); 50 | 51 | test('if get will return undefined when the path is empty', (t) => { 52 | const value = 'foo'; 53 | 54 | const key = []; 55 | const object = { 56 | shallow: value, 57 | deeply: [ 58 | { 59 | nested: [ 60 | { 61 | value 62 | } 63 | ] 64 | } 65 | ] 66 | }; 67 | 68 | const result = get(key, object); 69 | 70 | t.is(result, undefined); 71 | }); 72 | 73 | test('if get will return undefined when the object is falsy', (t) => { 74 | const key = 'bar'; 75 | const object = null; 76 | 77 | const result = get(key, object); 78 | 79 | t.is(result, undefined); 80 | }); 81 | -------------------------------------------------------------------------------- /test/startsWith.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import startsWith from 'src/startsWith'; 6 | 7 | test('if startsWith will return true if the first item in the array matches the value', (t) => { 8 | const value = {}; 9 | const array = [value, {}, {}]; 10 | 11 | const result = startsWith(value)(array); 12 | 13 | t.true(result); 14 | }); 15 | 16 | test('if startsWith will return false if the first item in the array does not match the value', (t) => { 17 | const value = {}; 18 | const array = [{}, value, {}]; 19 | 20 | const result = startsWith(value)(array); 21 | 22 | t.false(result); 23 | }); 24 | 25 | test('if startsWith returns false when the array is empty', (t) => { 26 | const value = {}; 27 | const array = []; 28 | 29 | const result = startsWith(value)(array); 30 | 31 | t.false(result); 32 | }); 33 | 34 | test('if startsWith will return true if the first letter in the word matches the value', (t) => { 35 | const value = 'f'; 36 | const string = `${value}oo`; 37 | 38 | const result = startsWith(value)(string); 39 | 40 | t.true(result); 41 | }); 42 | 43 | test('if startsWith will return false if the first letter in the word does not match the value', (t) => { 44 | const value = 'f'; 45 | const string = `o${value}o`; 46 | 47 | const result = startsWith(value)(string); 48 | 49 | t.false(result); 50 | }); 51 | 52 | test('if startsWith returns false when the string is empty', (t) => { 53 | const value = {}; 54 | const string = ''; 55 | 56 | const result = startsWith(value)(string); 57 | 58 | t.false(result); 59 | }); 60 | -------------------------------------------------------------------------------- /test/filter.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import sinon from 'sinon'; 4 | 5 | // src 6 | import filter from 'src/filter'; 7 | 8 | test('if filter will filter the values resulting from the function to a new array', (t) => { 9 | const items = [1, 2, 3, 4, 5]; 10 | const method = (item) => { 11 | return item % 2 === 0; 12 | }; 13 | 14 | const result = filter(method)(items); 15 | const expectedResult = items.filter(method); 16 | 17 | t.not(result, items); 18 | t.deepEqual(result, expectedResult); 19 | }); 20 | 21 | test('if filter does the same thing for objects as arrays', (t) => { 22 | const items = { 23 | foo: 'bar', 24 | bar: 'baz', 25 | baz: 'foo' 26 | }; 27 | const method = (value, key) => { 28 | return value === 'foo' || key === 'foo'; 29 | }; 30 | 31 | const result = filter(method, items); 32 | 33 | t.not(result, items); 34 | t.deepEqual(result, { 35 | foo: 'bar', 36 | baz: 'foo' 37 | }); 38 | }); 39 | 40 | test('if filter returns the items passed as an array if not an object or array and returns truthy', (t) => { 41 | const items = 'foo'; 42 | const method = sinon.stub().returns(true); 43 | 44 | const result = filter(method, items); 45 | 46 | t.true(method.calledOnce); 47 | t.deepEqual(result, [items]); 48 | }); 49 | 50 | test('if filter returns an empty array if not an object or array and returns falsy', (t) => { 51 | const items = 'foo'; 52 | const method = sinon.stub().returns(false); 53 | 54 | const result = filter(method, items); 55 | 56 | t.true(method.calledOnce); 57 | t.deepEqual(result, []); 58 | }); 59 | -------------------------------------------------------------------------------- /test/omit.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import omit from 'src/omit'; 6 | 7 | test('if omit will omit the indices out of the array', (t) => { 8 | const items = [1, 2, 3, 4, 5, 6]; 9 | const keys = [1, 4]; 10 | 11 | const result = omit(keys)(items); 12 | 13 | t.deepEqual( 14 | result, 15 | items.filter((ignored, index) => { 16 | return !~keys.indexOf(index); 17 | }) 18 | ); 19 | }); 20 | 21 | test('if omit will omit the keys out of the object', (t) => { 22 | const items = { 23 | foo: 'bar', 24 | bar: 'baz', 25 | baz: 'foo' 26 | }; 27 | const keys = ['foo', 'baz']; 28 | 29 | const result = omit(keys)(items); 30 | 31 | t.deepEqual(result, { 32 | bar: 'baz' 33 | }); 34 | }); 35 | 36 | test('if omit will return the original object as an array if not an array or object', (t) => { 37 | const items = 'foo'; 38 | const keys = ['toString']; 39 | 40 | const result = omit(keys, items); 41 | 42 | t.deepEqual(result, [items]); 43 | }); 44 | 45 | test('if omit will remove the nested item from the array', (t) => { 46 | const items = [{foo: ['bar', 'baz']}]; 47 | const keys = ['[0]foo[0]', '[0]foo[1]']; 48 | 49 | const result = omit(keys, items); 50 | 51 | t.deepEqual(result, [ 52 | { 53 | foo: [] 54 | } 55 | ]); 56 | }); 57 | 58 | test('if omit will remove the nested keys from the object', (t) => { 59 | const items = {foo: ['bar', {baz: 'baz'}]}; 60 | const keys = ['foo[1].baz']; 61 | 62 | const result = omit(keys, items); 63 | 64 | t.deepEqual(result, { 65 | foo: ['bar', {}] 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/reject.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import sinon from 'sinon'; 4 | 5 | // src 6 | import reject from 'src/reject'; 7 | 8 | test('if reject will filter the values returning truthy from the function to a new array', (t) => { 9 | const items = [1, 2, 3, 4, 5]; 10 | const method = (item) => { 11 | return item % 2 === 0; 12 | }; 13 | 14 | const result = reject(method)(items); 15 | const expectedResult = items.filter((item) => { 16 | return item % 2 !== 0; 17 | }); 18 | 19 | t.not(result, items); 20 | t.deepEqual(result, expectedResult); 21 | }); 22 | 23 | test('if reject does the same thing for objects as arrays', (t) => { 24 | const items = { 25 | foo: 'bar', 26 | bar: 'baz', 27 | baz: 'foo' 28 | }; 29 | const method = (value, key) => { 30 | return value === 'foo' || key === 'foo'; 31 | }; 32 | 33 | const result = reject(method)(items); 34 | 35 | t.not(result, items); 36 | t.deepEqual(result, { 37 | bar: 'baz' 38 | }); 39 | }); 40 | 41 | test('if filter returns the items passed as an array if not an object or array and returns falsy', (t) => { 42 | const items = 'foo'; 43 | const method = sinon.stub().returns(false); 44 | 45 | const result = reject(method)(items); 46 | 47 | t.true(method.calledOnce); 48 | t.deepEqual(result, [items]); 49 | }); 50 | 51 | test('if filter returns an empty array if not an object or array and returns truthy', (t) => { 52 | const items = 'foo'; 53 | const method = sinon.stub().returns(true); 54 | 55 | const result = reject(method)(items); 56 | 57 | t.true(method.calledOnce); 58 | t.deepEqual(result, []); 59 | }); 60 | -------------------------------------------------------------------------------- /src/flatten.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import reduce from './reduce'; 3 | 4 | // utils 5 | import coalesceToArray from './_utils/coalesceToArray'; 6 | import isArray from './_utils/isArray'; 7 | import isObject from './_utils/isObject'; 8 | 9 | /** 10 | * @function flattenArray 11 | * 12 | * @description 13 | * recursively flatten the array values into a single flat array 14 | * 15 | * @param {Array<*>} newArray the array to flatten into 16 | * @param {Array<*>} array the array to flatten 17 | * @returns {Array<*>} the flattened array 18 | */ 19 | const flattenArray = reduce((array, value) => 20 | !isArray(value) ? [...array, value] : [...array, ...flattenArray([], value)] 21 | ); 22 | 23 | /** 24 | * @function flattenObject 25 | * 26 | * @description 27 | * recursively flatten the object values into a single flat object 28 | * 29 | * @param {Object} newObject the object to flatten into 30 | * @param {Object} object the object to flatten 31 | * @returns {Object} the flattened object 32 | */ 33 | const flattenObject = reduce((object, value, key) => 34 | !isObject(value) 35 | ? { 36 | ...object, 37 | [key]: value, 38 | } 39 | : { 40 | ...object, 41 | ...flattenObject({}, value), 42 | } 43 | ); 44 | 45 | /** 46 | * @function flatten 47 | * 48 | * @description 49 | * recursively flatten the collection into a single alike collection 50 | * 51 | * @param {Array<*>|Object} collection the collection to flatten 52 | * @returns {Array<*>|Object} the flattened collection 53 | */ 54 | export default function flatten(collection) { 55 | return isObject(collection) ? flattenObject({}, collection) : flattenArray([], coalesceToArray(collection)); 56 | } 57 | -------------------------------------------------------------------------------- /src/has.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import getPath from './_utils/getPath'; 6 | 7 | /** 8 | * @function hasProperty 9 | * 10 | * @description 11 | * determine if the object has the property 12 | * 13 | * @param {string} property the property to find in object 14 | * @param {*} object the object to check if property exists in 15 | * @returns {boolean} does the object have the property 16 | */ 17 | function hasProperty(property, object) { 18 | return object != null && object.hasOwnProperty(property); // eslint-disable-line eqeqeq 19 | } 20 | 21 | /** 22 | * @function hasNestedProperty 23 | * 24 | * @description 25 | * does the value have the nested key structure denoted by path 26 | * 27 | * @param {Array} path the path to test 28 | * @param {*} value the value to check for the nested path existence 29 | * @returns {boolean} does the nested key structure exist in value 30 | */ 31 | function hasNestedProperty(path, value) { 32 | const key = path.shift(); 33 | const valueHasProperty = hasProperty(key, value); 34 | 35 | if (!path.length) { 36 | return valueHasProperty; 37 | } 38 | 39 | return valueHasProperty ? hasNestedProperty(path, value[key]) : false; 40 | } 41 | 42 | /** 43 | * @function has 44 | * 45 | * @description 46 | * does the value contain the keyOrIndex passed 47 | * 48 | * @param {number|string} keyOrIndex the key or index that the value may contain 49 | * @param {*} value the value to test if key is present in 50 | * @returns {boolean} does value have keyOrIndex 51 | */ 52 | export default curry(function has(keyOrIndex, value) { 53 | return hasNestedProperty(getPath(keyOrIndex), value); 54 | }); 55 | -------------------------------------------------------------------------------- /src/merge.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import coalesceToArray from './_utils/coalesceToArray'; 6 | import isObject from './_utils/isObject'; 7 | 8 | /** 9 | * @function mergeArrays 10 | * 11 | * @description 12 | * shallowly merge two arrays into a new object modeled after the first 13 | * 14 | * @param {Array<*>} array1 the first array to merge 15 | * @param {Array<*>} array2 the second array to merge 16 | * @returns {Array<*>} the merged arrays 17 | */ 18 | function mergeArrays(array1, array2) { 19 | return array2.length >= array1.length ? [...array2] : [...array2, ...array1.slice(array2.length)]; 20 | } 21 | 22 | /** 23 | * @function mergeObjects 24 | * 25 | * @description 26 | * shallowly merge two objects into a new object modeled after the first 27 | * 28 | * @param {Object} object1 the first object to merge 29 | * @param {Object} object2 the second object to merge 30 | * @returns {Object} the merged objects 31 | */ 32 | function mergeObjects(object1, object2) { 33 | return { 34 | ...object1, 35 | ...object2, 36 | }; 37 | } 38 | 39 | /** 40 | * @function merge 41 | * 42 | * @description 43 | * shallowly merge two items into a new collection modeled after the first 44 | * 45 | * @param {Array<*>|Object} collection1 the first collection to merge 46 | * @param {Array<*>|Object} collection2 the second collection to merge 47 | * @returns {Array<*>|Object} the merged collections 48 | */ 49 | export default curry(function merge(collection1, collection2) { 50 | return isObject(collection1) 51 | ? mergeObjects(collection1, collection2) 52 | : mergeArrays(coalesceToArray(collection1), coalesceToArray(collection2)); 53 | }); 54 | -------------------------------------------------------------------------------- /test/_utils/findInObject.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import findInObject from 'src/_utils/findInObject'; 6 | 7 | test('if findInObject will find the item in the array that matches when isKey is false', (t) => { 8 | const items = { 9 | foo: 'foo', 10 | bar: 'bar', 11 | baz: 'baz' 12 | }; 13 | const method = (item) => { 14 | return item === 'bar'; 15 | }; 16 | 17 | const result = findInObject(method, items, Object.keys(items), false); 18 | 19 | t.is(result, items.bar); 20 | }); 21 | 22 | test('if findInObject will find the index in the array that matches when isKey is true', (t) => { 23 | const items = { 24 | foo: 'foo', 25 | bar: 'bar', 26 | baz: 'baz' 27 | }; 28 | const method = (item) => { 29 | return item === 'bar'; 30 | }; 31 | 32 | const result = findInObject(method, items, Object.keys(items), true); 33 | 34 | t.is(result, 'bar'); 35 | }); 36 | 37 | test('if findInObject returns undefined if nothing matching is found and isKey is false', (t) => { 38 | const items = { 39 | foo: 'foo', 40 | bar: 'bar', 41 | baz: 'baz' 42 | }; 43 | const method = (item) => { 44 | return item === 'blarg'; 45 | }; 46 | 47 | const result = findInObject(method, items, Object.keys(items), false); 48 | 49 | t.is(result, undefined); 50 | }); 51 | 52 | test('if findInObject returns undefined if nothing matching is found and isKey is true', (t) => { 53 | const items = { 54 | foo: 'foo', 55 | bar: 'bar', 56 | baz: 'baz' 57 | }; 58 | const method = (item) => { 59 | return item === 'blarg'; 60 | }; 61 | 62 | const result = findInObject(method, items, Object.keys(items), true); 63 | 64 | t.is(result, undefined); 65 | }); 66 | -------------------------------------------------------------------------------- /test/modulo.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import modulo from 'src/modulo'; 6 | 7 | test('if modulo will get the modulo of the first number divided by the second number passed', (t) => { 8 | const first = 10; 9 | const second = 4; 10 | 11 | const result = modulo(first)(second); 12 | 13 | const jsMod = first % second; 14 | const expectedResult = ~~(jsMod >= 0 ? jsMod : jsMod + second); 15 | 16 | t.is(result, expectedResult); 17 | }); 18 | 19 | test('if modulo will get the modulo of the first number divided by the second number passed when the first number is negative', (t) => { 20 | const first = -10; 21 | const second = 4; 22 | 23 | const result = modulo(first)(second); 24 | 25 | const jsMod = first % second; 26 | const expectedResult = ~~(jsMod >= 0 ? jsMod : jsMod + second); 27 | 28 | t.is(result, expectedResult); 29 | }); 30 | 31 | test('if modulo will return NaN if the modulus is negative', (t) => { 32 | const first = 10; 33 | const second = -4; 34 | 35 | const result = modulo(first)(second); 36 | 37 | t.is(result, NaN); 38 | }); 39 | 40 | test('if modulo will return NaN if the modulus is zero', (t) => { 41 | const first = 10; 42 | const second = 0; 43 | 44 | const result = modulo(first)(second); 45 | 46 | t.is(result, NaN); 47 | }); 48 | 49 | test('if modulo will return NaN if the denominator is a decimal', (t) => { 50 | const first = 10.2; 51 | const second = 4; 52 | 53 | const result = modulo(first)(second); 54 | 55 | t.is(result, NaN); 56 | }); 57 | 58 | test('if modulo will return NaN if the modulus is a decimal', (t) => { 59 | const first = 10; 60 | const second = 4.2; 61 | 62 | const result = modulo(first)(second); 63 | 64 | t.is(result, NaN); 65 | }); 66 | -------------------------------------------------------------------------------- /src/_utils/getPath.js: -------------------------------------------------------------------------------- 1 | // utils 2 | import getKey from './getKey'; 3 | import isArray from './isArray'; 4 | import isString from './isString'; 5 | 6 | const QUOTES_REGEXP = /['|"|`]/; 7 | 8 | /** 9 | * @function getDotSeparatedPath 10 | * 11 | * @description 12 | * get the path separated by periods as an array of strings or numbers 13 | * 14 | * @param {string} path the string path to parse 15 | * @returns {Array} the parsed string path as an array path 16 | */ 17 | function getDotSeparatedPath(path) { 18 | return path.split('.').reduce((splitPath, pathItem) => !pathItem ? splitPath : [...splitPath, getKey(pathItem)], []); 19 | } 20 | 21 | /** 22 | * @function isQuotedKey 23 | * 24 | * @description 25 | * is the key passed a quoted key 26 | * 27 | * @param {string} key the key to test 28 | * @returns {boolean} is the key a quoted key 29 | */ 30 | function isQuotedKey(key) { 31 | return QUOTES_REGEXP.test(key[0]) && key[0] === key[key.length - 1]; 32 | } 33 | 34 | /** 35 | * @function getPath 36 | * 37 | * @description 38 | * the path to parsed into a valid array of keys / indices 39 | * 40 | * @param {Array|number|string} path the path to parse 41 | * @returns {Array} the parsed path 42 | */ 43 | export default function getPath(path) { 44 | if (isArray(path)) { 45 | return path; 46 | } 47 | 48 | if (isString(path)) { 49 | return path.split(/\[(.*?)\]/g).reduce((cleanPath, pathItem) => { 50 | if (!pathItem) { 51 | return cleanPath; 52 | } 53 | 54 | if (isQuotedKey(pathItem)) { 55 | return [...cleanPath, getKey(pathItem.slice(1, -1))]; 56 | } 57 | 58 | return [...cleanPath, ...getDotSeparatedPath(pathItem)]; 59 | }, []); 60 | } 61 | 62 | return [path]; 63 | } 64 | -------------------------------------------------------------------------------- /test/mergeDeep.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import mergeDeep from 'src/mergeDeep'; 6 | 7 | test('if mergeDeep will deeply merge nested arrays', (t) => { 8 | const first = [['foo', 'bar'], ['baz']]; 9 | const second = [['foo'], ['bar', 'baz']]; 10 | 11 | const result = mergeDeep(first, second); 12 | 13 | t.deepEqual(result, [['foo', 'bar'], ['bar', 'baz']]); 14 | }); 15 | 16 | test('if mergeDeep will deeply merge nested objects', (t) => { 17 | const first = { 18 | foo: { 19 | bar: 'baz' 20 | } 21 | }; 22 | const second = { 23 | foo: { 24 | baz: 'bar' 25 | } 26 | }; 27 | 28 | const result = mergeDeep(first, second); 29 | 30 | t.deepEqual(result, { 31 | foo: { 32 | bar: 'baz', 33 | baz: 'bar' 34 | } 35 | }); 36 | }); 37 | 38 | test('if mergeDeep will deeply merge with mixed values in an array', (t) => { 39 | const first = [ 40 | ['foo'], 41 | 'bar', 42 | { 43 | baz: 'baz' 44 | } 45 | ]; 46 | const second = [ 47 | 'foo', 48 | { 49 | bar: 'bar' 50 | }, 51 | ['baz'] 52 | ]; 53 | 54 | const result = mergeDeep(first, second); 55 | 56 | t.deepEqual(result, [ 57 | 'foo', 58 | { 59 | bar: 'bar' 60 | }, 61 | ['baz'] 62 | ]); 63 | }); 64 | 65 | test('if mergeDeep will deeply merge with mixed values in an object', (t) => { 66 | const first = { 67 | foo: ['foo'], 68 | bar: 'bar', 69 | baz: { 70 | baz: 'baz' 71 | } 72 | }; 73 | const second = { 74 | foo: 'foo', 75 | bar: { 76 | bar: 'bar' 77 | }, 78 | baz: ['baz'] 79 | }; 80 | 81 | const result = mergeDeep(first, second); 82 | 83 | t.deepEqual(result, { 84 | foo: 'foo', 85 | bar: { 86 | bar: 'bar' 87 | }, 88 | baz: ['baz'] 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /src/pick.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | import filter from './filter'; 4 | import reduce from './reduce'; 5 | 6 | // utils 7 | import isArray from './_utils/isArray'; 8 | import isObject from './_utils/isObject'; 9 | 10 | /** 11 | * @function pickArray 12 | * 13 | * @description 14 | * pick the values from the given array based on the existence of the indices 15 | * 16 | * @param {Array} indices the indices to match 17 | * @param {Array<*>} array the array to pick from 18 | * @returns {Array<*>} the array with the picked values 19 | */ 20 | function pickArray(indices, array) { 21 | return filter((ignored, index) => ~indices.indexOf(index), array); 22 | } 23 | 24 | /** 25 | * @function pickObject 26 | * 27 | * @description 28 | * pick the values from the given object based on the existence of the keys 29 | * 30 | * @param {Array} keys the keys to match 31 | * @param {Array<*>} object the object to pick from 32 | * @returns {Array<*>} the object with the picked values 33 | */ 34 | function pickObject(keys, object) { 35 | return reduce( 36 | (newObject, key) => { 37 | if (object.hasOwnProperty(key)) { 38 | newObject[key] = object[key]; 39 | } 40 | 41 | return newObject; 42 | }, 43 | {}, 44 | keys 45 | ); 46 | } 47 | 48 | /** 49 | * @function pick 50 | * 51 | * @description 52 | * pick specific keys from the object passed 53 | * 54 | * @param {Array} keys the keys to pick from the object 55 | * @param {Array<*>|Object} collection the object to pick the keys from 56 | * @returns {Array<*>|Object} the object with specific keys picked 57 | */ 58 | export default curry(function pick(keys, collection) { 59 | return isArray(collection) ? pickArray(keys, collection) : isObject(collection) ? pickObject(keys, collection) : {}; 60 | }); 61 | -------------------------------------------------------------------------------- /test/partition.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import partition from 'src/partition'; 6 | 7 | test('if partition will split the array based on truthy / falsy returns from the method', (t) => { 8 | const items = [1, 2, 3, 4, 5, 6, 7, 8]; 9 | const createMethod = (comparator) => { 10 | return (num) => { 11 | return num % 2 === comparator; 12 | }; 13 | }; 14 | 15 | const result = partition(createMethod(0))(items); 16 | 17 | const truthyResults = items.filter(createMethod(0)); 18 | const falsyResults = items.filter(createMethod(1)); 19 | 20 | t.deepEqual(result, [truthyResults, falsyResults]); 21 | }); 22 | 23 | test('if partition does the same for objects as it does for arrays', (t) => { 24 | const getKeyMappedValues = (array) => { 25 | return array.reduce((allItems, value) => { 26 | allItems[value] = value; 27 | 28 | return allItems; 29 | }, {}); 30 | }; 31 | 32 | const items = [1, 2, 3, 4, 5, 6, 7, 8]; 33 | const mappedItems = getKeyMappedValues(items); 34 | const createMethod = (comparator) => { 35 | return (num) => { 36 | return num % 2 === comparator; 37 | }; 38 | }; 39 | 40 | const result = partition(createMethod(0), mappedItems); 41 | 42 | const truthyResults = getKeyMappedValues(items.filter(createMethod(0))); 43 | const falsyResults = getKeyMappedValues(items.filter(createMethod(1))); 44 | 45 | t.deepEqual(result, { 46 | falsy: falsyResults, 47 | truthy: truthyResults 48 | }); 49 | }); 50 | 51 | test('if partition returns partition called on the item as the only value in the array if not an object or array', (t) => { 52 | const items = 1; 53 | const isEven = (number) => { 54 | return number % 2 === 0; 55 | }; 56 | 57 | const result = partition(isEven, items); 58 | 59 | t.deepEqual(result, [[], [items]]); 60 | }); 61 | -------------------------------------------------------------------------------- /src/forEach.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import coalesceToArray from './_utils/coalesceToArray'; 6 | import isObject from './_utils/isObject'; 7 | 8 | /** 9 | * @function forEachArray 10 | * 11 | * @description 12 | * iterate over each of the items 13 | * 14 | * @param {function(*, (number), (Array<*>)): *} fn the function to iterate over the items 15 | * @param {Array<*>} array the items to iterate over 16 | * @returns {Array<*>} the items passed 17 | */ 18 | function forEachArray(fn, array) { 19 | let index = -1; 20 | 21 | while (++index < array.length) { 22 | fn(array[index], index, array); 23 | } 24 | 25 | return array; 26 | } 27 | 28 | /** 29 | * @function forEachObject 30 | * 31 | * @description 32 | * iterate over each of the items 33 | * 34 | * @param {function(*, (string), (Object)): *} fn the function to iterate over the items 35 | * @param {Object} object the items to iterate over 36 | * @returns {Object} the items passed 37 | */ 38 | function forEachObject(fn, object) { 39 | const keys = Object.keys(object); 40 | 41 | let index = -1, 42 | key; 43 | 44 | while (++index < keys.length) { 45 | key = keys[index]; 46 | 47 | fn(object[key], key, object); 48 | } 49 | 50 | return object; 51 | } 52 | 53 | /** 54 | * @function forEach 55 | * 56 | * @description 57 | * iterate over each of the items in the collection 58 | * 59 | * @param {function(*, (number|string), (Array<*>|Object)): *} fn the function to iterate over the collection 60 | * @param {Array<*>|Object} collection the collection to iterate over 61 | * @returns {Array<*>|Object} the collection passed 62 | */ 63 | export default curry(function forEach(fn, collection) { 64 | return isObject(collection) ? forEachObject(fn, collection) : forEachArray(fn, coalesceToArray(collection)); 65 | }); 66 | -------------------------------------------------------------------------------- /src/take.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import coalesceToArray from './_utils/coalesceToArray'; 6 | import isObject from './_utils/isObject'; 7 | 8 | /** 9 | * @function takeObject 10 | * 11 | * @description 12 | * get the first n number of items in an object 13 | * 14 | * @param {number} size the number of items to get from the end of the object 15 | * @param {Object} object the object of items to get the first n items from 16 | * @param {Array} keys the keys of the object 17 | * @return {Object} the first n number of items 18 | */ 19 | function takeObject(size, object, keys) { 20 | let index = -1, 21 | newObject = {}, 22 | key; 23 | 24 | while (++index < size) { 25 | key = keys[index]; 26 | 27 | newObject[key] = object[key]; 28 | } 29 | 30 | return newObject; 31 | } 32 | 33 | /** 34 | * @function takeArray 35 | * 36 | * @description 37 | * get the first n number of items in an array 38 | * 39 | * @param {number} size the number of items to get from the end of the array 40 | * @param {Array<*>} array the array of items to get the first n items from 41 | * @return {Array<*>} the first n number of items 42 | */ 43 | function takeArray(size, array) { 44 | return size > 0 ? array.slice(0, size) : []; 45 | } 46 | 47 | /** 48 | * @function take 49 | * 50 | * @description 51 | * get the first n number of items in a collection 52 | * 53 | * @param {number} size the number of items to get from the end of the collection 54 | * @param {Array<*>|Object} collection the collection of items to get the first n items from 55 | * @return {Array<*>|Object} the first n number of items 56 | */ 57 | export default curry(function take(size, collection) { 58 | return isObject(collection) 59 | ? takeObject(size, collection, Object.keys(collection)) 60 | : takeArray(size, coalesceToArray(collection)); 61 | }); 62 | -------------------------------------------------------------------------------- /test/_utils/findInArray.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import findInArray from 'src/_utils/findInArray'; 6 | 7 | test('if findInArray will find the item in the array that matches when isIndex is false', (t) => { 8 | const items = [ 9 | { 10 | foo: 'foo' 11 | }, 12 | { 13 | foo: 'bar' 14 | }, 15 | { 16 | foo: 'baz' 17 | } 18 | ]; 19 | const method = (item) => { 20 | return item.foo === 'bar'; 21 | }; 22 | 23 | const result = findInArray(method, items, false); 24 | 25 | t.is(result, items[1]); 26 | }); 27 | 28 | test('if findInArray will find the index in the array that matches when isIndex is true', (t) => { 29 | const items = [ 30 | { 31 | foo: 'foo' 32 | }, 33 | { 34 | foo: 'bar' 35 | }, 36 | { 37 | foo: 'baz' 38 | } 39 | ]; 40 | const method = (item) => { 41 | return item.foo === 'bar'; 42 | }; 43 | 44 | const result = findInArray(method, items, true); 45 | 46 | t.is(result, 1); 47 | }); 48 | 49 | test('if findInArray returns undefined if nothing matching is found and isIndex is false', (t) => { 50 | const items = [ 51 | { 52 | foo: 'foo' 53 | }, 54 | { 55 | foo: 'bar' 56 | }, 57 | { 58 | foo: 'baz' 59 | } 60 | ]; 61 | const method = (item) => { 62 | return item.foo === 'blarg'; 63 | }; 64 | 65 | const result = findInArray(method, items, false); 66 | 67 | t.is(result, undefined); 68 | }); 69 | 70 | test('if findInArray returns -1 if nothing matching is found and isIndex is true', (t) => { 71 | const items = [ 72 | { 73 | foo: 'foo' 74 | }, 75 | { 76 | foo: 'bar' 77 | }, 78 | { 79 | foo: 'baz' 80 | } 81 | ]; 82 | const method = (item) => { 83 | return item.foo === 'blarg'; 84 | }; 85 | 86 | const result = findInArray(method, items, true); 87 | 88 | t.is(result, -1); 89 | }); 90 | -------------------------------------------------------------------------------- /src/map.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import coalesceToArray from './_utils/coalesceToArray'; 6 | import isObject from './_utils/isObject'; 7 | 8 | /** 9 | * @function mapArray 10 | * 11 | * @description 12 | * map the array to a new array based on the returns of the call with fn 13 | * 14 | * @param {function(*, number, Array<*>): *} fn the method to map with 15 | * @param {Array<*>} array the array of items to map 16 | * @returns {Array<*>} the mapped items 17 | */ 18 | function mapArray(fn, array) { 19 | let newArray = [], 20 | index = -1; 21 | 22 | while (++index < array.length) { 23 | newArray[index] = fn(array[index], index, array); 24 | } 25 | 26 | return newArray; 27 | } 28 | 29 | /** 30 | * @function mapObject 31 | * 32 | * @description 33 | * map the object to a new object based on the returns of the call with fn 34 | * 35 | * @param {function(*, string, Object): *} fn the method to map with 36 | * @param {Object} object the object of items to map 37 | * @returns {Object} the mapped items 38 | */ 39 | function mapObject(fn, object) { 40 | const keys = Object.keys(object); 41 | 42 | let newObject = {}, 43 | index = -1, 44 | key; 45 | 46 | while (++index < keys.length) { 47 | key = keys[index]; 48 | 49 | newObject[key] = fn(object[key], key, object); 50 | } 51 | 52 | return newObject; 53 | } 54 | 55 | /** 56 | * @function map 57 | * 58 | * @description 59 | * map the collection based on the returns of the call with fn 60 | * 61 | * @param {function(*, (number|string), (Array<*>|Object)): *} fn the method to map with 62 | * @param {Array<*>|Object} collection the collection of items to map 63 | * @returns {Array<*>|Object} the mapped collection 64 | */ 65 | export default curry(function map(fn, collection) { 66 | return isObject(collection) ? mapObject(fn, collection) : mapArray(fn, coalesceToArray(collection)); 67 | }); 68 | -------------------------------------------------------------------------------- /src/rest.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import coalesceToArray from './_utils/coalesceToArray'; 6 | import isObject from './_utils/isObject'; 7 | 8 | /** 9 | * @function restObject 10 | * 11 | * @description 12 | * get the last n number of items in an object 13 | * 14 | * @param {number} size the number of items to get from the end of the object 15 | * @param {Object} object the object of items to get the last n items from 16 | * @param {Array} keys the object's keys 17 | * @return {Object} the last n number of items in the object 18 | */ 19 | function restObject(size, object, keys) { 20 | let index = keys.length - size - 1, 21 | newObject = {}, 22 | key; 23 | 24 | while (++index < keys.length) { 25 | key = keys[index]; 26 | 27 | newObject[key] = object[key]; 28 | } 29 | 30 | return newObject; 31 | } 32 | 33 | /** 34 | * @function restArray 35 | * 36 | * @description 37 | * get the last n number of items in a array 38 | * 39 | * @param {number} size the number of items to get from the end of the array 40 | * @param {Array<*>} array the array of items to get the last n items from 41 | * @return {Array<*>} the last n number of items in the array 42 | */ 43 | function restArray(size, array) { 44 | return size > 0 ? array.slice(array.length - size) : []; 45 | } 46 | 47 | /** 48 | * @function rest 49 | * 50 | * @description 51 | * get the last n number of items in a collection 52 | * 53 | * @param {number} size the number of items to get from the end of the collection 54 | * @param {Array<*>|Object} collection the collection of items to get the last n items from 55 | * @return {Array<*>|Object} the last n number of items in the collection 56 | */ 57 | export default curry(function rest(size, collection) { 58 | return isObject(collection) 59 | ? restObject(size, collection, Object.keys(collection)) 60 | : restArray(size, coalesceToArray(collection)); 61 | }); 62 | -------------------------------------------------------------------------------- /test/entries.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import entries from 'src/entries'; 6 | 7 | test('if entries will return an empty array if the collection does not exist', (t) => { 8 | const result = entries(null); 9 | 10 | t.deepEqual(result, []); 11 | }); 12 | 13 | test('if entries will get the entries from the iterable if it is a map', (t) => { 14 | const value = new Map().set('foo', 'bar'); 15 | 16 | const result = entries(value); 17 | 18 | t.deepEqual(result, [['foo', 'bar']]); 19 | }); 20 | 21 | test('if entries will get the entries from the iterable if it is a set', (t) => { 22 | const value = new Set().add('foo'); 23 | 24 | const result = entries(value); 25 | 26 | t.deepEqual(result, [['foo', 'foo']]); 27 | }); 28 | 29 | test.serial('if entries will get the entries from the iterable if it is a map and Array.from does not exist', (t) => { 30 | const arrayFrom = Array.from; 31 | 32 | Array.from = undefined; 33 | 34 | const value = new Map().set('foo', 'bar'); 35 | 36 | const result = entries(value); 37 | 38 | t.deepEqual(result, [['foo', 'bar']]); 39 | 40 | Array.from = arrayFrom; 41 | }); 42 | 43 | test.serial('if entries will get the entries from the iterable if it is a set and Array.from does not exist', (t) => { 44 | const arrayFrom = Array.from; 45 | 46 | Array.from = undefined; 47 | 48 | const value = new Set().add('foo'); 49 | 50 | const result = entries(value); 51 | 52 | t.deepEqual(result, [['foo', 'foo']]); 53 | 54 | Array.from = arrayFrom; 55 | }); 56 | 57 | test('if entries will map the key / value combination if a standard array', (t) => { 58 | const value = ['foo', 'bar']; 59 | 60 | const result = entries(value); 61 | 62 | t.deepEqual(result, [[0, 'foo'], [1, 'bar']]); 63 | }); 64 | 65 | test('if entries will map the key / value combination if a standard object', (t) => { 66 | const value = { 67 | foo: 'bar', 68 | bar: 'baz' 69 | }; 70 | 71 | const result = entries(value); 72 | 73 | t.deepEqual(result, [['foo', 'bar'], ['bar', 'baz']]); 74 | }); 75 | -------------------------------------------------------------------------------- /test/find.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import sinon from 'sinon'; 4 | 5 | // src 6 | import find from 'src/find'; 7 | import * as array from 'src/_utils/findInArray'; 8 | import * as object from 'src/_utils/findInObject'; 9 | 10 | test('if find will find call findInArray when the item is an array', (t) => { 11 | const items = []; 12 | const method = () => {}; 13 | 14 | const findInArrayStub = sinon.stub(array, 'default'); 15 | const findInObjectStub = sinon.stub(object, 'default'); 16 | 17 | find(method)(items); 18 | 19 | t.true(findInArrayStub.calledOnce); 20 | t.true(findInArrayStub.calledWith(method, items, false)); 21 | 22 | t.true(findInObjectStub.notCalled); 23 | 24 | findInArrayStub.restore(); 25 | findInObjectStub.restore(); 26 | }); 27 | 28 | test('if find will find call findInObject when the item is an object', (t) => { 29 | const items = {}; 30 | const method = () => {}; 31 | 32 | const findInArrayStub = sinon.stub(array, 'default'); 33 | const findInObjectStub = sinon.stub(object, 'default'); 34 | 35 | find(method)(items); 36 | 37 | t.true(findInArrayStub.notCalled); 38 | 39 | t.true(findInObjectStub.calledOnce); 40 | 41 | const args = findInObjectStub.firstCall.args; 42 | 43 | t.is(args.length, 4); 44 | t.deepEqual(args, [method, items, Object.keys(items), false]); 45 | 46 | findInArrayStub.restore(); 47 | findInObjectStub.restore(); 48 | }); 49 | 50 | test('if find will find call findInArray when the item is neither an array or object', (t) => { 51 | const items = 'foo'; 52 | const method = () => {}; 53 | 54 | const findInArrayStub = sinon.stub(array, 'default'); 55 | const findInObjectStub = sinon.stub(object, 'default'); 56 | 57 | find(method)(items); 58 | 59 | t.true(findInArrayStub.calledOnce); 60 | 61 | const args = findInArrayStub.firstCall.args; 62 | 63 | t.is(args.length, 3); 64 | t.deepEqual(args, [method, [items], false]); 65 | 66 | t.true(findInObjectStub.notCalled); 67 | 68 | findInArrayStub.restore(); 69 | findInObjectStub.restore(); 70 | }); 71 | -------------------------------------------------------------------------------- /test/findIndex.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import sinon from 'sinon'; 4 | 5 | // src 6 | import findIndex from 'src/findIndex'; 7 | import * as array from 'src/_utils/findInArray'; 8 | import * as object from 'src/_utils/findInObject'; 9 | 10 | test('if findIndex will find call findInArray when the item is an array', (t) => { 11 | const items = []; 12 | const method = () => {}; 13 | 14 | const findInArrayStub = sinon.stub(array, 'default'); 15 | const findInObjectStub = sinon.stub(object, 'default'); 16 | 17 | findIndex(method)(items); 18 | 19 | t.true(findInArrayStub.calledOnce); 20 | t.true(findInArrayStub.calledWith(method, items, true)); 21 | 22 | t.true(findInObjectStub.notCalled); 23 | 24 | findInArrayStub.restore(); 25 | findInObjectStub.restore(); 26 | }); 27 | 28 | test('if findIndex will find call findInObject when the item is an object', (t) => { 29 | const items = {}; 30 | const method = () => {}; 31 | 32 | const findInArrayStub = sinon.stub(array, 'default'); 33 | const findInObjectStub = sinon.stub(object, 'default'); 34 | 35 | findIndex(method)(items); 36 | 37 | t.true(findInArrayStub.notCalled); 38 | 39 | t.true(findInObjectStub.calledOnce); 40 | 41 | const args = findInObjectStub.firstCall.args; 42 | 43 | t.is(args.length, 4); 44 | t.deepEqual(args, [method, items, Object.keys(items), true]); 45 | 46 | findInArrayStub.restore(); 47 | findInObjectStub.restore(); 48 | }); 49 | 50 | test('if findIndex will find call findInArray when the item is neither an array or object', (t) => { 51 | const items = 'foo'; 52 | const method = () => {}; 53 | 54 | const findInArrayStub = sinon.stub(array, 'default'); 55 | const findInObjectStub = sinon.stub(object, 'default'); 56 | 57 | findIndex(method)(items); 58 | 59 | t.true(findInArrayStub.calledOnce); 60 | 61 | const args = findInArrayStub.firstCall.args; 62 | 63 | t.is(args.length, 3); 64 | t.deepEqual(args, [method, [items], true]); 65 | 66 | t.true(findInObjectStub.notCalled); 67 | 68 | findInArrayStub.restore(); 69 | findInObjectStub.restore(); 70 | }); 71 | -------------------------------------------------------------------------------- /src/curry.js: -------------------------------------------------------------------------------- 1 | // utils 2 | import isPlaceholder from './_utils/isPlaceholder'; 3 | 4 | /** 5 | * @function getArgsToPass 6 | * 7 | * @description 8 | * get the complete args with previous placeholders being filled in 9 | * 10 | * @param {Array<*>} originalArgs the arguments from the previous run 11 | * @param {Array<*>} nextArgs the arguments from the next run 12 | * @returns {Array<*>} the complete list of args 13 | */ 14 | export function getArgsToPass(originalArgs, nextArgs) { 15 | let argsToPass = [], 16 | index = -1; 17 | 18 | while (++index < originalArgs.length) { 19 | argsToPass[index] = isPlaceholder(originalArgs[index]) && nextArgs.length ? nextArgs.shift() : originalArgs[index]; 20 | } 21 | 22 | return nextArgs.length ? argsToPass.concat(nextArgs) : argsToPass; 23 | } 24 | 25 | /** 26 | * @function getAreArgsFilled 27 | * 28 | * @description 29 | * determine if the args are considered filled based on matching arity and not having any placeholders 30 | * 31 | * @param {Array<*>} args the args passed to the function 32 | * @param {number} arity the arity of the function 33 | * @returns {boolean} are all of the args filled 34 | */ 35 | export function getAreArgsFilled(args, arity) { 36 | if (args.length < arity) { 37 | return false; 38 | } 39 | 40 | let index = -1; 41 | 42 | while (++index < arity) { 43 | if (isPlaceholder(args[index])) { 44 | return false; 45 | } 46 | } 47 | 48 | return true; 49 | } 50 | 51 | /** 52 | * @function curry 53 | * 54 | * @description 55 | * get the method passed as a curriable method based on its parameters 56 | * 57 | * @param {function} fn the method to make curriable 58 | * @param {number} [arity=fn.length] the arity of fn 59 | * @returns {function(*): *} the fn passed as a curriable method 60 | */ 61 | export default function curry(fn, arity = fn.length) { 62 | return function curried(...args) { 63 | return getAreArgsFilled(args, arity) 64 | ? fn.apply(this, args) 65 | : function(...nextArgs) { 66 | return curried.apply(this, getArgsToPass(args, nextArgs)); 67 | }; 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /src/some.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import coalesceToArray from './_utils/coalesceToArray'; 6 | import isObject from './_utils/isObject'; 7 | 8 | /** 9 | * @function someArray 10 | * 11 | * @description 12 | * does some of the calls to fn with the items in the array return truthy 13 | * 14 | * @param {function} fn the function to test with 15 | * @param {Array<*>} array the array to test 16 | * @returns {boolean} does any of the items in the array match fn 17 | */ 18 | function someArray(fn, array) { 19 | let index = 0; 20 | 21 | while (index < array.length) { 22 | if (fn(array[index], index, array)) { 23 | return true; 24 | } 25 | 26 | index++; 27 | } 28 | 29 | return false; 30 | } 31 | 32 | /** 33 | * @function someObject 34 | * 35 | * @description 36 | * does some of the calls to fn with the items in the object return truthy 37 | * 38 | * @param {function} fn the function to test with 39 | * @param {Array<*>} object the object to test 40 | * @param {Array} keys the keys of the object to iterate over 41 | * @returns {boolean} does any of the items in the object match fn 42 | */ 43 | function someObject(fn, object, keys) { 44 | let index = 0, 45 | key; 46 | 47 | while (index < keys.length) { 48 | key = keys[index]; 49 | 50 | if (fn(object[key], key, object)) { 51 | return true; 52 | } 53 | 54 | index++; 55 | } 56 | 57 | return false; 58 | } 59 | 60 | /** 61 | * @function some 62 | * 63 | * @description 64 | * does any of the iteration of collection return truthy based on passing to fn 65 | * 66 | * @param {function(*, (number|string), (Array<*>|Object)): *} fn the method to test with 67 | * @param {Array<*>|Object} collection the collection of items to test 68 | * @returns {boolean} does any iteration match 69 | */ 70 | export default curry(function some(fn, collection) { 71 | const keys = Object.keys(collection); 72 | 73 | if (!keys.length) { 74 | return true; 75 | } 76 | 77 | return isObject(collection) ? someObject(fn, collection, keys) : someArray(fn, coalesceToArray(collection)); 78 | }); 79 | -------------------------------------------------------------------------------- /src/every.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import coalesceToArray from './_utils/coalesceToArray'; 6 | import isObject from './_utils/isObject'; 7 | 8 | /** 9 | * @function everyArray 10 | * 11 | * @description 12 | * does all of the calls to fn with the items in the array return truthy 13 | * 14 | * @param {function} fn the function to test with 15 | * @param {Array<*>} array the array to test 16 | * @returns {boolean} does all of the items in the array match fn 17 | */ 18 | function everyArray(fn, array) { 19 | let index = 0; 20 | 21 | while (index < array.length) { 22 | if (!fn(array[index], index, array)) { 23 | return false; 24 | } 25 | 26 | index++; 27 | } 28 | 29 | return true; 30 | } 31 | 32 | /** 33 | * @function everyObject 34 | * 35 | * @description 36 | * does all of the calls to fn with the items in the object return truthy 37 | * 38 | * @param {function} fn the function to test with 39 | * @param {Array<*>} object the object to test 40 | * @param {Array} keys the keys of the object to iterate over 41 | * @returns {boolean} does all of the items in the object match fn 42 | */ 43 | function everyObject(fn, object, keys) { 44 | let index = 0, 45 | key; 46 | 47 | while (index < keys.length) { 48 | key = keys[index]; 49 | 50 | if (!fn(object[key], key, object)) { 51 | return false; 52 | } 53 | 54 | index++; 55 | } 56 | 57 | return true; 58 | } 59 | 60 | /** 61 | * @function every 62 | * 63 | * @description 64 | * does every iteration of collection return truthy based on passing to fn 65 | * 66 | * @param {function(*, (number|string), (Array<*>|Object)): *} fn the method to test with 67 | * @param {Array<*>|Object} collection the collection of items to test 68 | * @returns {boolean} does every iteration match 69 | */ 70 | export default curry(function every(fn, collection) { 71 | const keys = Object.keys(collection); 72 | 73 | if (!keys.length) { 74 | return true; 75 | } 76 | 77 | return isObject(collection) ? everyObject(fn, collection, keys) : everyArray(fn, coalesceToArray(collection)); 78 | }); 79 | -------------------------------------------------------------------------------- /src/filter.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import coalesceToArray from './_utils/coalesceToArray'; 6 | import isObject from './_utils/isObject'; 7 | 8 | /** 9 | * @function filterArray 10 | * 11 | * @description 12 | * filter down the items to those that return truthy based on the call of fn 13 | * 14 | * @param {function(*, (number|string), Array<*>): *} fn the method to filter with 15 | * @param {Array<*>} array the array of items to filter 16 | * @returns {Array<*>} the filtered items 17 | */ 18 | function filterArray(fn, array) { 19 | let filteredItems = [], 20 | index = -1; 21 | 22 | while (++index < array.length) { 23 | if (fn(array[index], index, array)) { 24 | filteredItems[filteredItems.length] = array[index]; 25 | } 26 | } 27 | 28 | return filteredItems; 29 | } 30 | 31 | /** 32 | * @function filterObject 33 | * 34 | * @description 35 | * filter down the items to those that return truthy based on the call of fn 36 | * 37 | * @param {function(*, string, Object): *} fn the method to filter with 38 | * @param {Object} object the array of items to filter 39 | * @returns {Object} the filtered items 40 | */ 41 | function filterObject(fn, object) { 42 | const keys = Object.keys(object); 43 | 44 | let filteredObject = {}, 45 | index = -1, 46 | key; 47 | 48 | while (++index < keys.length) { 49 | key = keys[index]; 50 | 51 | if (fn(object[key], key, object)) { 52 | filteredObject[key] = object[key]; 53 | } 54 | } 55 | 56 | return filteredObject; 57 | } 58 | 59 | /** 60 | * @function filter 61 | * 62 | * @description 63 | * filter down the collection to those that return truthy based on the call of fn 64 | * 65 | * @param {function(*, (number|string), (Array<*>|Object): *} fn the method to filter with 66 | * @param {Array<*>|Object} collection the collection of items to filter 67 | * @returns {Array<*>|Object} the filtered collection 68 | */ 69 | export default curry(function filter(fn, collection) { 70 | return isObject(collection) ? filterObject(fn, collection) : filterArray(fn, coalesceToArray(collection)); 71 | }); 72 | -------------------------------------------------------------------------------- /test/_utils/getPath.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import _ from 'lodash'; 4 | 5 | // src 6 | import getPath from 'src/_utils/getPath'; 7 | 8 | test('if getPath will return the path itself when it is an array', (t) => { 9 | const path = [0, 'foo']; 10 | 11 | const result = getPath(path); 12 | 13 | t.is(result, path); 14 | }); 15 | 16 | test('if when the path is a number, it will be coalesced to an array of that number', (t) => { 17 | const path = 0; 18 | 19 | const result = getPath(path); 20 | 21 | t.deepEqual(result, [path]); 22 | }); 23 | 24 | test('if when the path is a string, it will parse out the path based on dot and bracket notation', (t) => { 25 | const keys = ['foo', 0, 'bar', 'baz']; 26 | const path = keys 27 | .reduce((keyString, key) => { 28 | return `${keyString}${_.isNumber(key) ? `[${key}]` : `.${key}`}`; 29 | }, '') 30 | .substr(1); 31 | 32 | const result = getPath(path); 33 | 34 | t.deepEqual(result, keys); 35 | }); 36 | 37 | test('if the path will handle the bracket notation being first', (t) => { 38 | const path = '[0].foo'; 39 | 40 | const result = getPath(path); 41 | 42 | t.deepEqual(result, [0, 'foo']); 43 | }); 44 | 45 | test('if the path will handle the bracket notation being last', (t) => { 46 | const path = 'foo[0]'; 47 | 48 | const result = getPath(path); 49 | 50 | t.deepEqual(result, ['foo', 0]); 51 | }); 52 | 53 | test('if when the path is not an array or string, it will return the item in an array', (t) => { 54 | const path = 123; 55 | 56 | const result = getPath(path); 57 | 58 | t.deepEqual(result, [123]); 59 | }); 60 | 61 | test('if when the path has nested quoted strings, it will respect those strings as singular keys', (t) => { 62 | const simple = '["foo.bar"]'; 63 | const simplePath = getPath(simple); 64 | 65 | t.deepEqual(simplePath, ['foo.bar']); 66 | 67 | const complex = 'foo[`bar.baz`]'; 68 | const complexPath = getPath(complex); 69 | 70 | t.deepEqual(complexPath, ['foo', 'bar.baz']); 71 | 72 | const crazy = 'foo[\'bar.baz\'].blah[0]["super.blah"]'; 73 | const crazyPath = getPath(crazy); 74 | 75 | t.deepEqual(crazyPath, ['foo', 'bar.baz', 'blah', 0, 'super.blah']); 76 | }); 77 | -------------------------------------------------------------------------------- /test/reduce.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import sinon from 'sinon'; 4 | 5 | // src 6 | import reduce from 'src/reduce'; 7 | import * as array from 'src/_utils/reduceArray'; 8 | import * as object from 'src/_utils/reduceObject'; 9 | 10 | test('if reduce will call reduceArray if the items are an array', (t) => { 11 | const items = []; 12 | const method = () => {}; 13 | const initialValue = 0; 14 | 15 | const reduceArrayStub = sinon.stub(array, 'default'); 16 | const reduceObjectStub = sinon.stub(object, 'default'); 17 | 18 | reduce(method, initialValue, items); 19 | 20 | t.true(reduceArrayStub.calledOnce); 21 | t.true(reduceArrayStub.calledWith(method, items, initialValue)); 22 | 23 | t.true(reduceObjectStub.notCalled); 24 | 25 | reduceArrayStub.restore(); 26 | reduceObjectStub.restore(); 27 | }); 28 | 29 | test('if reduce will call reduceObject if the items are an object', (t) => { 30 | const items = {}; 31 | const method = () => {}; 32 | const initialValue = 0; 33 | 34 | const reduceArrayStub = sinon.stub(array, 'default'); 35 | const reduceObjectStub = sinon.stub(object, 'default'); 36 | 37 | reduce(method, initialValue, items); 38 | 39 | t.true(reduceArrayStub.notCalled); 40 | 41 | t.true(reduceObjectStub.calledOnce); 42 | 43 | const args = reduceObjectStub.firstCall.args; 44 | 45 | t.is(args.length, 4); 46 | 47 | t.deepEqual(args, [method, items, initialValue, Object.keys(items)]); 48 | 49 | reduceArrayStub.restore(); 50 | reduceObjectStub.restore(); 51 | }); 52 | 53 | test('if reduce will call reduceArray if the items are neither an array nor object', (t) => { 54 | const items = 'foo'; 55 | const method = () => {}; 56 | const initialValue = 0; 57 | 58 | const reduceArrayStub = sinon.stub(array, 'default'); 59 | const reduceObjectStub = sinon.stub(object, 'default'); 60 | 61 | reduce(method, initialValue, items); 62 | 63 | t.true(reduceArrayStub.calledOnce); 64 | 65 | const args = reduceArrayStub.firstCall.args; 66 | 67 | t.is(args.length, 3); 68 | 69 | t.deepEqual(args, [method, [items], initialValue]); 70 | 71 | t.true(reduceObjectStub.notCalled); 72 | 73 | reduceArrayStub.restore(); 74 | reduceObjectStub.restore(); 75 | }); 76 | -------------------------------------------------------------------------------- /test/reduceRight.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import sinon from 'sinon'; 4 | 5 | // src 6 | import reduceRight from 'src/reduceRight'; 7 | import * as array from 'src/_utils/reduceArray'; 8 | import * as object from 'src/_utils/reduceObject'; 9 | 10 | test('if reduceRight will call reduceArray if the items are an array', (t) => { 11 | const items = []; 12 | const method = () => {}; 13 | const initialValue = 0; 14 | 15 | const reduceArrayStub = sinon.stub(array, 'default'); 16 | const reduceObjectStub = sinon.stub(object, 'default'); 17 | 18 | reduceRight(method, initialValue, items); 19 | 20 | t.true(reduceArrayStub.calledOnce); 21 | 22 | const args = reduceArrayStub.firstCall.args; 23 | 24 | t.is(args.length, 3); 25 | 26 | t.deepEqual(args, [method, [...items].reverse(), initialValue]); 27 | 28 | t.true(reduceObjectStub.notCalled); 29 | 30 | reduceArrayStub.restore(); 31 | reduceObjectStub.restore(); 32 | }); 33 | 34 | test('if reduceRight will call reduceObject if the items are an object', (t) => { 35 | const items = {}; 36 | const method = () => {}; 37 | const initialValue = 0; 38 | 39 | const reduceArrayStub = sinon.stub(array, 'default'); 40 | const reduceObjectStub = sinon.stub(object, 'default'); 41 | 42 | reduceRight(method, initialValue, items); 43 | 44 | t.true(reduceArrayStub.notCalled); 45 | 46 | t.true(reduceObjectStub.calledOnce); 47 | 48 | const args = reduceObjectStub.firstCall.args; 49 | 50 | t.is(args.length, 4); 51 | 52 | t.deepEqual(args, [method, items, initialValue, Object.keys(items).reverse()]); 53 | 54 | reduceArrayStub.restore(); 55 | reduceObjectStub.restore(); 56 | }); 57 | 58 | test('if reduceRight will call reduceArray if the items are neither an array nor object', (t) => { 59 | const items = 'foo'; 60 | const method = () => {}; 61 | const initialValue = 0; 62 | 63 | const reduceArrayStub = sinon.stub(array, 'default'); 64 | const reduceObjectStub = sinon.stub(object, 'default'); 65 | 66 | reduceRight(method, initialValue, items); 67 | 68 | t.true(reduceArrayStub.calledOnce); 69 | 70 | const args = reduceArrayStub.firstCall.args; 71 | 72 | t.is(args.length, 3); 73 | 74 | t.deepEqual(args, [method, [items], initialValue]); 75 | 76 | t.true(reduceObjectStub.notCalled); 77 | 78 | reduceArrayStub.restore(); 79 | reduceObjectStub.restore(); 80 | }); 81 | -------------------------------------------------------------------------------- /test/set.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import set from 'src/set'; 6 | 7 | test('if set will set the shallow key to be the value passed', (t) => { 8 | const value = 'foo'; 9 | 10 | const key = 'shallow'; 11 | const object = { 12 | shallow: 'value', 13 | deeply: [ 14 | { 15 | nested: [ 16 | { 17 | value: 'value' 18 | } 19 | ] 20 | } 21 | ] 22 | }; 23 | 24 | const result = set(key)(value)(object); 25 | 26 | t.deepEqual(result, { 27 | ...object, 28 | shallow: value 29 | }); 30 | }); 31 | 32 | test('if set will set the deep key to be the value passed', (t) => { 33 | const value = 'foo'; 34 | 35 | const key = 'deeply[0].nested[0].value'; 36 | const object = { 37 | shallow: 'value', 38 | deeply: [ 39 | { 40 | nested: [ 41 | { 42 | value: 'value' 43 | } 44 | ] 45 | } 46 | ] 47 | }; 48 | 49 | const result = set(key)(value)(object); 50 | 51 | t.deepEqual(result, { 52 | ...object, 53 | deeply: [ 54 | { 55 | nested: [ 56 | { 57 | value 58 | } 59 | ] 60 | } 61 | ] 62 | }); 63 | }); 64 | 65 | test('if set will return the object itself when the path is empty', (t) => { 66 | const value = 'foo'; 67 | 68 | const key = []; 69 | const object = { 70 | shallow: 'value', 71 | deeply: [ 72 | { 73 | nested: [ 74 | { 75 | value: 'value' 76 | } 77 | ] 78 | } 79 | ] 80 | }; 81 | 82 | const result = set(key, value, object); 83 | 84 | t.is(result, object); 85 | }); 86 | 87 | test('if set will add an object when a descendant is not found and the next path item is a string', (t) => { 88 | const value = 'foo'; 89 | 90 | const key = 'bar.baz'; 91 | const object = { 92 | bar: 'baz' 93 | }; 94 | 95 | const result = set(key, value, object); 96 | 97 | t.deepEqual(result, { 98 | bar: { 99 | baz: value 100 | } 101 | }); 102 | }); 103 | 104 | test('if set will add an array when a descendant is not found and the next path item is a number', (t) => { 105 | const value = 'foo'; 106 | 107 | const key = 'bar[0]'; 108 | const object = { 109 | bar: 'baz' 110 | }; 111 | 112 | const result = set(key, value, object); 113 | 114 | t.deepEqual(result, { 115 | bar: [value] 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /src/partition.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | import reduce from './reduce'; 4 | 5 | // utils 6 | import coalesceToArray from './_utils/coalesceToArray'; 7 | import isObject from './_utils/isObject'; 8 | 9 | /** 10 | * @function getCorrectPartition 11 | * 12 | * @description 13 | * based on the result being truthy or not, return the correct 14 | * partition (either truthy or falsy) 15 | * 16 | * @param {{falsy: (Array<*>|Object), truthy: (Array<*>|Object)}} partitions the truthy and falsy partitions 17 | * @param {*} result the result to determine the partition 18 | * @returns {Array<*>|Object} either the truthy or falsy partition 19 | */ 20 | function getCorrectPartition(partitions, result) { 21 | return result ? partitions.truthy : partitions.falsy; 22 | } 23 | 24 | /** 25 | * @function partitionArray 26 | * 27 | * @description 28 | * partition the array into truthy and falsy values 29 | * 30 | * @param {function(*, number, Array<*>)} fn the function that will determine which partition to put the value into 31 | * @param {Array<*>} array the items to partition 32 | * @returns {Array>} the partitioned items 33 | */ 34 | function partitionArray(fn, array) { 35 | const reducedPartitions = reduce( 36 | (partitions, item, index) => { 37 | getCorrectPartition(partitions, fn(item, index, array)).push(item); 38 | 39 | return partitions; 40 | }, 41 | { 42 | falsy: [], 43 | truthy: [], 44 | }, 45 | array 46 | ); 47 | 48 | return [reducedPartitions.truthy, reducedPartitions.falsy]; 49 | } 50 | 51 | /** 52 | * @function partitionObject 53 | * 54 | * @description 55 | * partition the array into truthy and falsy values 56 | * 57 | * @param {function(*, string, Object)} fn the function that will determine which partition to put the value into 58 | * @param {Object} object the items to partition 59 | * @returns {Object} the partitioned items 60 | */ 61 | function partitionObject(fn, object) { 62 | return reduce( 63 | (partitions, item, key) => { 64 | getCorrectPartition(partitions, fn(item, key, object))[key] = item; 65 | 66 | return partitions; 67 | }, 68 | { 69 | falsy: {}, 70 | truthy: {}, 71 | }, 72 | object 73 | ); 74 | } 75 | 76 | /** 77 | * @function partition 78 | * 79 | * @description 80 | * partition the collection into truthy and falsy values 81 | * 82 | * @param {function(*, (number|string), (Array<*>|Object))} 83 | * fn the function that will determine which partition to put the value into 84 | * @param {Array<*>|Object} collection the collection to partition 85 | * @returns {Array>|Object} the partitioned collection 86 | */ 87 | export default curry(function partition(fn, collection) { 88 | return isObject(collection) ? partitionObject(fn, collection) : partitionArray(fn, coalesceToArray(collection)); 89 | }); 90 | -------------------------------------------------------------------------------- /test/findLastIndex.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import sinon from 'sinon'; 4 | 5 | // src 6 | import findLastIndex from 'src/findLastIndex'; 7 | import * as array from 'src/_utils/findInArray'; 8 | import * as object from 'src/_utils/findInObject'; 9 | import * as reverse from 'src/_utils/reverse'; 10 | 11 | test('if findLastIndex will find call findInArray and reverse when the item is an array', (t) => { 12 | const items = []; 13 | const method = () => {}; 14 | 15 | const reversedItems = []; 16 | 17 | const reverseStub = sinon.stub(reverse, 'default').returns(reversedItems); 18 | const findInArrayStub = sinon.stub(array, 'default'); 19 | const findInObjectStub = sinon.stub(object, 'default'); 20 | 21 | findLastIndex(method)(items); 22 | 23 | t.true(reverseStub.calledOnce); 24 | t.true(reverseStub.calledWith(items)); 25 | 26 | t.true(findInArrayStub.calledOnce); 27 | t.true(findInArrayStub.calledWith(method, reversedItems, true)); 28 | 29 | t.true(findInObjectStub.notCalled); 30 | 31 | reverseStub.restore(); 32 | findInArrayStub.restore(); 33 | findInObjectStub.restore(); 34 | }); 35 | 36 | test('if findLastIndex will find call findInObject when the item is an object', (t) => { 37 | const items = {}; 38 | const method = () => {}; 39 | 40 | const reversedItems = {}; 41 | 42 | const reverseStub = sinon.stub(reverse, 'default').returns(reversedItems); 43 | const findInArrayStub = sinon.stub(array, 'default'); 44 | const findInObjectStub = sinon.stub(object, 'default'); 45 | 46 | findLastIndex(method)(items); 47 | 48 | t.true(reverseStub.calledOnce); 49 | 50 | t.true(findInArrayStub.notCalled); 51 | 52 | t.true(findInObjectStub.calledOnce); 53 | 54 | const args = findInObjectStub.firstCall.args; 55 | 56 | t.is(args.length, 4); 57 | t.deepEqual(args, [method, items, Object.keys(items), true]); 58 | 59 | reverseStub.restore(); 60 | findInArrayStub.restore(); 61 | findInObjectStub.restore(); 62 | }); 63 | 64 | test('if findLastIndex will find call findInArray when the item is neither an array or object', (t) => { 65 | const items = 'foo'; 66 | const method = () => {}; 67 | 68 | const reversedItems = 'oof'; 69 | 70 | const reverseStub = sinon.stub(reverse, 'default').returns(reversedItems); 71 | const findInArrayStub = sinon.stub(array, 'default'); 72 | const findInObjectStub = sinon.stub(object, 'default'); 73 | 74 | findLastIndex(method)(items); 75 | 76 | t.true(reverseStub.calledOnce); 77 | t.true(reverseStub.calledWith(items)); 78 | 79 | t.true(findInArrayStub.calledOnce); 80 | 81 | const args = findInArrayStub.firstCall.args; 82 | 83 | t.is(args.length, 3); 84 | t.deepEqual(args, [method, [reversedItems], true]); 85 | 86 | t.true(findInObjectStub.notCalled); 87 | 88 | reverseStub.restore(); 89 | findInArrayStub.restore(); 90 | findInObjectStub.restore(); 91 | }); 92 | -------------------------------------------------------------------------------- /test/findLast.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import sinon from 'sinon'; 4 | 5 | // src 6 | import findLast from 'src/findLast'; 7 | import * as array from 'src/_utils/findInArray'; 8 | import * as object from 'src/_utils/findInObject'; 9 | import * as reverse from 'src/_utils/reverse'; 10 | 11 | test('if findLast will find call findInArray and reverse when the item is an array', (t) => { 12 | const items = []; 13 | const method = () => {}; 14 | 15 | const reversedItems = []; 16 | 17 | const reverseStub = sinon.stub(reverse, 'default').returns(reversedItems); 18 | const findInArrayStub = sinon.stub(array, 'default'); 19 | const findInObjectStub = sinon.stub(object, 'default'); 20 | 21 | findLast(method)(items); 22 | 23 | t.true(reverseStub.calledOnce); 24 | t.true(reverseStub.calledWith(items)); 25 | 26 | t.true(findInArrayStub.calledOnce); 27 | t.true(findInArrayStub.calledWith(method, reversedItems, false)); 28 | 29 | t.true(findInObjectStub.notCalled); 30 | 31 | reverseStub.restore(); 32 | findInArrayStub.restore(); 33 | findInObjectStub.restore(); 34 | }); 35 | 36 | test('if findLast will find call findInObject and reverse when the item is an object', (t) => { 37 | const items = {}; 38 | const method = () => {}; 39 | 40 | const reversedItems = {}; 41 | 42 | const reverseStub = sinon.stub(reverse, 'default').returns(reversedItems); 43 | const findInArrayStub = sinon.stub(array, 'default'); 44 | const findInObjectStub = sinon.stub(object, 'default'); 45 | 46 | findLast(method)(items); 47 | 48 | t.true(reverseStub.calledOnce); 49 | t.true(reverseStub.calledWith(items)); 50 | 51 | t.true(findInArrayStub.notCalled); 52 | 53 | t.true(findInObjectStub.calledOnce); 54 | 55 | const args = findInObjectStub.firstCall.args; 56 | 57 | t.is(args.length, 4); 58 | t.deepEqual(args, [method, items, Object.keys(items), false]); 59 | 60 | reverseStub.restore(); 61 | findInArrayStub.restore(); 62 | findInObjectStub.restore(); 63 | }); 64 | 65 | test('if findLast will find call findInArray and reverse when the item is neither an array or object', (t) => { 66 | const items = 'foo'; 67 | const method = () => {}; 68 | 69 | const reversedItems = 'oof'; 70 | 71 | const reverseStub = sinon.stub(reverse, 'default').returns(reversedItems); 72 | const findInArrayStub = sinon.stub(array, 'default'); 73 | const findInObjectStub = sinon.stub(object, 'default'); 74 | 75 | findLast(method)(items); 76 | 77 | t.true(reverseStub.calledOnce); 78 | t.true(reverseStub.calledWith(items)); 79 | 80 | t.true(findInArrayStub.calledOnce); 81 | 82 | const args = findInArrayStub.firstCall.args; 83 | 84 | t.is(args.length, 3); 85 | t.deepEqual(args, [method, [reversedItems], false]); 86 | 87 | t.true(findInObjectStub.notCalled); 88 | 89 | reverseStub.restore(); 90 | findInArrayStub.restore(); 91 | findInObjectStub.restore(); 92 | }); 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "tony.quetano@planttheidea.com", 3 | "ava": { 4 | "babel": "inherit", 5 | "failFast": true, 6 | "files": [ 7 | "test/**/*.js" 8 | ], 9 | "require": [ 10 | "babel-register" 11 | ], 12 | "source": [ 13 | "src/**/*.js" 14 | ], 15 | "verbose": true 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/planttheidea/kari/issues" 19 | }, 20 | "description": "Modular curriable utilities for modern JS", 21 | "devDependencies": { 22 | "ava": "^0.22.0", 23 | "babel-cli": "^6.2.0", 24 | "babel-eslint": "^8.2.5", 25 | "babel-loader": "^7.1.2", 26 | "babel-preset-env": "^1.6.0", 27 | "babel-preset-react": "^6.24.1", 28 | "babel-preset-stage-2": "^6.24.1", 29 | "benchmark": "^2.1.4", 30 | "cli-table2": "^0.2.0", 31 | "eslint": "^5.12.1", 32 | "eslint-config-rapid7": "^3.1.0", 33 | "eslint-friendly-formatter": "^4.0.1", 34 | "eslint-loader": "^2.1.1", 35 | "html-webpack-plugin": "^3.2.0", 36 | "in-publish": "^2.0.0", 37 | "lodash": "^4.17.4", 38 | "nyc": "^13.1.0", 39 | "optimize-js-plugin": "^0.0.4", 40 | "ora": "^3.0.0", 41 | "ramda": "^0.26.1", 42 | "react": "^16.7.0", 43 | "react-dom": "^16.7.0", 44 | "rimraf": "^2.6.1", 45 | "sinon": "^7.2.3", 46 | "webpack": "^4.29.0", 47 | "webpack-cli": "^3.2.1", 48 | "webpack-dev-server": "^3.1.14" 49 | }, 50 | "homepage": "https://github.com/planttheidea/kari#readme", 51 | "keywords": [ 52 | "curry", 53 | "functional", 54 | "fp" 55 | ], 56 | "license": "MIT", 57 | "main": "index.js", 58 | "module": "es/index.js", 59 | "name": "kari", 60 | "repository": { 61 | "type": "git", 62 | "url": "git+https://github.com/planttheidea/kari.git" 63 | }, 64 | "scripts": { 65 | "benchmark": "npm run transpile:benchmark && node benchmark/index.js && npm run clean:benchmark", 66 | "build": "NODE_ENV=development webpack --progress --colors --config=webpack/webpack.config.js", 67 | "build:minified": "NODE_ENV=production webpack --progress --colors --config=webpack/webpack.config.minified.js", 68 | "clean": "rimraf *.js && rimraf _utils && rimraf es && rimraf dist", 69 | "clean:benchmark": "rimraf lib", 70 | "dev": "NODE_ENV=development webpack-dev-server --progress --colors --config=webpack/webpack.config.dev.js", 71 | "lint": "NODE_ENV=test eslint --max-warnings 0 src", 72 | "lint:fix": "NODE_ENV=test eslint src --fix", 73 | "postpublish": "npm run clean", 74 | "prepublish": "in-publish && npm run prepublish:compile || echo ''", 75 | "prepublish:compile": "npm run lint && npm run test:coverage && npm run clean && npm run transpile:lib && npm run transpile:es && npm run build && npm run build:minified", 76 | "start": "npm run dev", 77 | "test": "NODE_PATH=. NODE_ENV=test ava", 78 | "test:coverage": "nyc --cache npm test", 79 | "test:watch": "NODE_PATH=. NODE_ENV=test ava --watch", 80 | "transpile:es": "BABEL_ENV=es babel src --out-dir es", 81 | "transpile:lib": "BABEL_ENV=lib babel src --out-dir .", 82 | "transpile:benchmark": "babel src --out-dir lib" 83 | }, 84 | "version": "0.7.1" 85 | } 86 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # kari CHANGELOG 2 | 3 | ## 0.7.1 4 | 5 | - Update dependencies to resolve security vulnerability from old version of `webpack-dev-server` and `react-dom` 6 | 7 | ## 0.7.0 8 | 9 | - Add [typeOf](#README#typeof) method 10 | 11 | ## 0.6.0 12 | 13 | - Add methods: 14 | - [empty](README.md#empty) 15 | - [entries](README.md#entries) 16 | - [equalsBy](README.md#equalsby) 17 | - [findLast](README.md#findlast) 18 | - [findLastIndex](README.md#findlastindex) 19 | - [findLastKey](README.md#findlastkey) 20 | - [flatten](README.md#flatten) 21 | - [max](README.md#max) 22 | - [min](README.md#min) 23 | - [not](README.md#not) 24 | - [notBy](README.md#notby) 25 | - [reject](README.md#reject) 26 | - [unique](README.md#unique) 27 | - [uniqueBy](README.md#uniqueby) 28 | - Add benchmarks 29 | 30 | ## 0.5.3 31 | 32 | - Make nested paths support quoted keys 33 | 34 | ## 0.5.2 35 | 36 | - Fix issue with `omit` not respecting original indices when multiple keys from the same nested array are declared 37 | 38 | ## 0.5.1 39 | 40 | - Make `has` and `omit` work with nested paths 41 | 42 | ## 0.5.0 43 | 44 | - Add methods: 45 | - [findKey](README.md#findkey) 46 | - [has](README.md#has) 47 | - [merge](README.md#merge) 48 | - [mergeDeep](README.md#mergedeep) 49 | - Greatly improve the performance of `filter`, `map`, and `reduce` (which are used for a number of other methods) 50 | 51 | ## 0.4.1 52 | 53 | - Make `insert` work with objects as well as arrays 54 | 55 | ## 0.4.0 56 | 57 | - Add function methods: 58 | - [always](README.md#always) 59 | - [apply](README.md#apply) 60 | - [arity](README.md#arity) 61 | - [bind](README.md#bind) 62 | - [identity](README.md#identity) 63 | - [instanceOf](README.md#instanceof) 64 | - [partialRight](README.md#partialright) 65 | - [tap](README.md#tap) 66 | - [tryCatch](README.md#trycatch) 67 | - [uncurry](README.md#uncurry) 68 | - Add more math methods: 69 | - [gt](README.md#gt) 70 | - [gte](README.md#gte) 71 | - [lt](README.md#lt) 72 | - [lte](README.md#lte) 73 | - Add equality comparison methods: 74 | - [equals](README.md#equals) 75 | - [is](README.md#is) 76 | - Add more collection test methods: 77 | - [endsWith](README.md#endswith) 78 | - [includes](README.md#includes) 79 | - [startsWith](README.md#startswith) 80 | 81 | ## 0.3.1 82 | 83 | - Fix issue where applying extra arguments to curried function would re-curry it 84 | 85 | ## 0.3.0 86 | 87 | - Add sort methods: 88 | - [ascend](README.md#ascend) 89 | - [descend](README.md#descend) 90 | - [sort](README.md#sort) 91 | - [sortBy](README.md#sortby) 92 | - [sortWith](README.md#sortwith) 93 | 94 | ## 0.2.0 95 | 96 | - Add math methods: 97 | - [add](README.md#add) 98 | - [divide](README.md#divide) 99 | - [modulo](README.md#modulo) (mathematical modulo) 100 | - [multiply](README.md#multiply) 101 | - [remainder](README.md#remainder) (JS modulo) 102 | - [subtract](README.md#subtract) 103 | 104 | ## 0.1.2 105 | 106 | - Fix explanation of placeholders in readme 107 | 108 | ## 0.1.1 109 | 110 | - Remove `.nyc_output` folder from publishes 111 | 112 | ## 0.1.0 113 | 114 | - Initial release 115 | -------------------------------------------------------------------------------- /src/mergeDeep.js: -------------------------------------------------------------------------------- 1 | // methods 2 | import curry from './curry'; 3 | 4 | // utils 5 | import coalesceToArray from './_utils/coalesceToArray'; 6 | import isArray from './_utils/isArray'; 7 | import isObject from './_utils/isObject'; 8 | 9 | /** 10 | * @function getDeeplyMergedValues 11 | * 12 | * @description 13 | * deeply merge the values passed based on their types 14 | * 15 | * @param {*} value1 the first value to merge 16 | * @param {*} value2 the second value to merge 17 | * @returns {*} the merged value 18 | */ 19 | function getDeeplyMergedValues(value1, value2) { 20 | if (isArray(value2)) { 21 | return !isArray(value1) ? value2 : mergeDeepArrays(value1, value2); // eslint-disable-line no-use-before-define 22 | } 23 | 24 | if (isObject(value2)) { 25 | return !isObject(value1) ? value2 : mergeDeepObjects(value1, value2); //eslint-disable-line no-use-before-define 26 | } 27 | 28 | return value2; 29 | } 30 | 31 | /** 32 | * @function mergeArrays 33 | * 34 | * @description 35 | * shallowly merge two arrays into a new object modeled after the first 36 | * 37 | * @param {Array<*>} array1 the first array to merge 38 | * @param {Array<*>} array2 the second array to merge 39 | * @returns {Array<*>} the merged arrays 40 | */ 41 | function mergeDeepArrays(array1, array2) { 42 | const length = Math.max(array1.length, array2.length); 43 | 44 | let index = -1, 45 | mergedArray = []; 46 | 47 | while (++index < length) { 48 | if (index < array2.length) { 49 | mergedArray[index] = getDeeplyMergedValues(array1[index], array2[index]); 50 | 51 | continue; 52 | } 53 | 54 | mergedArray[index] = array1[index]; 55 | } 56 | 57 | return mergedArray; 58 | } 59 | 60 | /** 61 | * @function mergeObjects 62 | * 63 | * @description 64 | * shallowly merge two objects into a new object modeled after the first 65 | * 66 | * @param {Object} object1 the first object to merge 67 | * @param {Object} object2 the second object to merge 68 | * @returns {Object} the merged objects 69 | */ 70 | function mergeDeepObjects(object1, object2) { 71 | const keys1 = Object.keys(object1); 72 | const keys2 = Object.keys(object2); 73 | 74 | let index = -1, 75 | mergedObject = {}, 76 | key, 77 | indexOfKey; 78 | 79 | while (++index < keys1.length) { 80 | key = keys1[index]; 81 | indexOfKey = keys2.indexOf(key); 82 | 83 | if (~indexOfKey) { 84 | mergedObject[key] = getDeeplyMergedValues(object1[key], object2[key]); 85 | 86 | keys2.splice(indexOfKey, 1); 87 | 88 | continue; 89 | } 90 | 91 | mergedObject[key] = object1[key]; 92 | } 93 | 94 | if (keys2.length) { 95 | index = -1; 96 | 97 | while (++index < keys2.length) { 98 | key = keys2[index]; 99 | 100 | mergedObject[key] = object2[key]; 101 | } 102 | } 103 | 104 | return mergedObject; 105 | } 106 | 107 | /** 108 | * @function mergeDeep 109 | * 110 | * @description 111 | * deeply merge two items into a new collection modeled after the first 112 | * 113 | * @param {Array<*>|Object} collection1 the first collection to merge 114 | * @param {Array<*>|Object} collection2 the second collection to merge 115 | * @returns {Array<*>|Object} the merged collections 116 | */ 117 | export default curry(function mergeDeep(collection1, collection2) { 118 | return isObject(collection1) 119 | ? mergeDeepObjects(collection1, collection2) 120 | : mergeDeepArrays(coalesceToArray(collection1), coalesceToArray(collection2)); 121 | }); 122 | -------------------------------------------------------------------------------- /test/_utils/isEquivalent.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import _ from 'lodash'; 4 | import sinon from 'sinon'; 5 | import {ARRAY, DATE, OBJECT, NAN, NULL, POSITIVE_INTEGER, STRING, UNDEFINED} from 'test/helpers/_typeCheckValues'; 6 | 7 | // src 8 | import isEquivalent from 'src/_utils/isEquivalent'; 9 | import * as type from 'src/_utils/getObjectClass'; 10 | 11 | test('if isEquivalent returns true when values are strictly equal', (t) => { 12 | t.true(isEquivalent(STRING, STRING)); 13 | t.true(isEquivalent(POSITIVE_INTEGER, POSITIVE_INTEGER)); 14 | t.true(isEquivalent(OBJECT, OBJECT)); 15 | t.true(isEquivalent(ARRAY, ARRAY)); 16 | t.true(isEquivalent(NULL, NULL)); 17 | t.true(isEquivalent(UNDEFINED, UNDEFINED)); 18 | }); 19 | 20 | test('if isEquivalent returns false when values are of different object classes', (t) => { 21 | t.false(isEquivalent(STRING, POSITIVE_INTEGER)); 22 | t.false(isEquivalent(ARRAY, OBJECT)); 23 | }); 24 | 25 | test('if isEquivalent returns true when values are both NaN', (t) => { 26 | t.true(isEquivalent(NAN, NAN)); 27 | }); 28 | 29 | test('if isEquivalent returns true if the valueOf is equal for dates, booleans, numbers, and strings', (t) => { 30 | t.true(isEquivalent(DATE, new Date(DATE.valueOf()))); 31 | t.false(isEquivalent(DATE, new Date(123))); 32 | 33 | t.true(isEquivalent(new Boolean('true'), new Boolean('true'))); 34 | t.false(isEquivalent(new Boolean('true'), new Boolean(''))); 35 | 36 | t.true(isEquivalent(new Number('123'), new Number('123'))); 37 | t.false(isEquivalent(new Number('123'), new Number('321'))); 38 | 39 | t.true(isEquivalent(new String('foo'), new String('foo'))); 40 | t.false(isEquivalent(new String('foo'), new String('bar'))); 41 | }); 42 | 43 | test('if isEquivalent returns true if the error has the same message and name', (t) => { 44 | t.true(isEquivalent(new Error('foo'), new Error('foo'))); 45 | t.false(isEquivalent(new Error('foo'), new Error('bar'))); 46 | }); 47 | 48 | test('if isEquivalent returns true if the regexp when converted to string are equal', (t) => { 49 | t.true(isEquivalent(/foo/, /foo/)); 50 | t.false(isEquivalent(/foo/g, /foo/)); 51 | }); 52 | 53 | test('if isEquivalent return true if the entries of maps and sets are equal in value', (t) => { 54 | const map1 = new Map(); 55 | const map2 = new Map(); 56 | 57 | map1.set('foo', 'bar'); 58 | map2.set('foo', 'bar'); 59 | 60 | map1.set('bar', {bar: 'baz'}); 61 | map2.set('bar', {bar: 'baz'}); 62 | 63 | t.true(isEquivalent(map1, map2)); 64 | t.false(isEquivalent(map1, new Map())); 65 | 66 | const set1 = new Set(); 67 | const set2 = new Set(); 68 | 69 | set1.add('foo'); 70 | set2.add('foo'); 71 | 72 | set1.add('bar'); 73 | set2.add('bar'); 74 | 75 | t.true(isEquivalent(set1, set2)); 76 | t.false(isEquivalent(set1, new Set())); 77 | }); 78 | 79 | test('if isEquivalent returns true if the object is equal in value', (t) => { 80 | const object = { 81 | foo: 'bar', 82 | bar: 'baz', 83 | baz: { 84 | foo: 'bar' 85 | } 86 | }; 87 | const clone = _.cloneDeep(object); 88 | 89 | t.true(isEquivalent(object, clone)); 90 | t.false( 91 | isEquivalent(object, { 92 | ...object, 93 | baz: 'foo' 94 | }) 95 | ); 96 | }); 97 | 98 | test('if isEquivalent returns true if the array is equal in value', (t) => { 99 | const array = [ 100 | 'foo', 101 | 'bar', 102 | { 103 | baz: { 104 | foo: 'bar' 105 | } 106 | } 107 | ]; 108 | const clone = _.cloneDeep(array); 109 | 110 | t.true(isEquivalent(array, clone)); 111 | }); 112 | 113 | test('if isEquivalent handles circular objects', (t) => { 114 | const object = { 115 | foo: 'bar', 116 | bar: 'baz' 117 | }; 118 | 119 | object.baz = object; 120 | 121 | const clone = {...object}; 122 | 123 | clone.baz = clone; 124 | 125 | t.true(isEquivalent(object, clone)); 126 | }); 127 | 128 | test('if isEquivalent returns false as an ultimate fallback when no object class match is found for comparison', (t) => { 129 | const getObjectClassStub = sinon.stub(type, 'default').returns('foo'); 130 | 131 | t.false(isEquivalent({}, {})); 132 | 133 | getObjectClassStub.restore(); 134 | }); 135 | -------------------------------------------------------------------------------- /test/curry.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import _ from 'lodash'; 4 | 5 | // src 6 | import curry, {getArgsToPass, getAreArgsFilled} from 'src/curry'; 7 | import __ from 'src/__'; 8 | 9 | test('if curry creates a method that will curry each of the arguments in the method arity', (t) => { 10 | const method = (a, b, c, d, e) => { 11 | return [a, b, c, d, e]; 12 | }; 13 | 14 | const curried = curry(method); 15 | 16 | t.true(_.isFunction(curried)); 17 | 18 | const args = [1, 2, 3, 4, 5]; 19 | 20 | const afterCurrying = args.reduce((afterLastArg, arg) => { 21 | t.true(_.isFunction(afterLastArg)); 22 | 23 | return afterLastArg(arg); 24 | }, curried); 25 | 26 | t.deepEqual(afterCurrying, args); 27 | }); 28 | 29 | test('if curry creates a method that will accept the full arity in the passing', (t) => { 30 | const method = (a, b, c, d, e) => { 31 | return [a, b, c, d, e]; 32 | }; 33 | 34 | const curried = curry(method); 35 | 36 | t.true(_.isFunction(curried)); 37 | 38 | const args = [1, 2, 3, 4, 5]; 39 | 40 | const result = curried(...args); 41 | 42 | t.deepEqual(result, args); 43 | }); 44 | 45 | test('if curry creates a method that will accept any combination of arity in the passing', (t) => { 46 | const method = (a, b, c, d, e) => { 47 | return [a, b, c, d, e]; 48 | }; 49 | 50 | const curried = curry(method); 51 | 52 | t.true(_.isFunction(curried)); 53 | 54 | const args = [1, 2, 3, 4, 5]; 55 | 56 | const results = [ 57 | curried(args[0])(...args.slice(1)), 58 | curried(args[0])(args[1])(...args.slice(2)), 59 | curried(args[0])(args[1])(args[2])(...args.slice(3)), 60 | curried(args[0])(args[1])(args[2])(args[3])(args[4]), 61 | curried(...args.slice(0, 2))(...args.slice(2)), 62 | curried(...args.slice(0, 2))(args[2])(...args.slice(3)), 63 | curried(...args.slice(0, 2))(args[2])(args[3])(args[4]), 64 | curried(...args.slice(0, 3))(...args.slice(3)), 65 | curried(...args.slice(0, 3))(args[3])(args[4]), 66 | curried(...args.slice(0, 4))(args[4]) 67 | ]; 68 | 69 | results.forEach((result, index) => { 70 | t.deepEqual(result, args, index); 71 | }); 72 | }); 73 | 74 | test('if curry allows for defaults via a custom arity not based on the function arity', (t) => { 75 | const add = (a, b, c = 1) => { 76 | return a + b + c; 77 | }; 78 | 79 | const args = [1, 2, 3]; 80 | const arity = args.length; 81 | 82 | const curried = curry(add, arity); 83 | 84 | const afterTwo = curried(...args.slice(0, arity - 1)); 85 | 86 | t.true(_.isFunction(afterTwo)); 87 | 88 | const result = afterTwo(args[args.length - 1]); 89 | const expectedResult = args.reduce((sum, num) => { 90 | return sum + num; 91 | }, 0); 92 | 93 | t.is(result, expectedResult); 94 | }); 95 | 96 | test('if getArgsToPass determines the complete args to pass when there are no remaining args after the placeholder', (t) => { 97 | const originalArgs = [1, __, 3]; 98 | const futureArgs = [2]; 99 | 100 | const result = getArgsToPass(originalArgs, [...futureArgs]); 101 | const expectedResult = originalArgs.map((arg) => { 102 | return arg !== __ ? arg : futureArgs.shift(); 103 | }); 104 | 105 | t.deepEqual(result, expectedResult); 106 | }); 107 | 108 | test('if getArgsToPass determines the complete args to pass when there are remaining args after the placeholder', (t) => { 109 | const originalArgs = [1, __, 3]; 110 | const futureArgs = [2, 4]; 111 | 112 | const result = getArgsToPass(originalArgs, [...futureArgs]); 113 | const expectedResult = originalArgs 114 | .map((arg) => { 115 | return arg !== __ ? arg : futureArgs.shift(); 116 | }) 117 | .concat(futureArgs); 118 | 119 | t.deepEqual(result, expectedResult); 120 | }); 121 | 122 | test('if getAreArgsFilled returns false if args length is less than arity', (t) => { 123 | const args = [1, 2]; 124 | const arity = 3; 125 | 126 | t.false(getAreArgsFilled(args, arity)); 127 | }); 128 | 129 | test('if getAreArgsFilled returns false if args has a placeholder in it', (t) => { 130 | const args = [1, __, 2]; 131 | const arity = 3; 132 | 133 | t.false(getAreArgsFilled(args, arity)); 134 | }); 135 | test('if getAreArgsFilled returns true if arity is reached and no placeholders exist', (t) => { 136 | const args = [1, 2, 3]; 137 | const arity = 3; 138 | 139 | t.true(getAreArgsFilled(args, arity)); 140 | }); 141 | -------------------------------------------------------------------------------- /src/_utils/isEquivalent.js: -------------------------------------------------------------------------------- 1 | // utils 2 | import getObjectClass from './getObjectClass'; 3 | import isNAN from './isNAN'; 4 | 5 | /** 6 | * @constant {RegExp} ITERATOR_COMPARE_REGEXP 7 | */ 8 | const ITERATOR_COMPARE_REGEXP = /(Map|Set)/; 9 | 10 | /** 11 | * @constant {RegExp} OBJECT_OR_ARRAY_COMPARE_REGEXP 12 | */ 13 | const OBJECT_OR_ARRAY_COMPARE_REGEXP = /(Object|Array)/; 14 | 15 | /** 16 | * @constant {RegExp} VALUE_OF_COMPARE_REGEXP 17 | */ 18 | const VALUE_OF_COMPARE_REGEXP = /(Date|Boolean|Number|String)/; 19 | 20 | /** 21 | * @function getArrayFromIterator 22 | * 23 | * @description 24 | * convert an Iterator into an array based on its values 25 | * 26 | * @param {Iterator} iterator the iterator to call 27 | * @returns {Array<*>} the array of values from the iterator 28 | */ 29 | function getArrayFromIterator(iterator) { 30 | let array = [], 31 | iteration; 32 | 33 | while ((iteration = iterator.next()) && !iteration.done) { 34 | array.push(iteration.value); 35 | } 36 | 37 | return array; 38 | } 39 | 40 | /** 41 | * @function isObjectOrArrayEquivalent 42 | * 43 | * @description 44 | * are the two objects or arrays equal in value, handling circular references 45 | * 46 | * @param {*} firstValue the first value to compare 47 | * @param {*} secondValue the second value to compare 48 | * @param {Array<*>} firstStack the first stack of objects already compared to support circular objects 49 | * @param {Array<*>} secondStack the second stack of objects already compared to support circular objects 50 | * @returns {boolean} are the first and second value equivalent 51 | */ 52 | function isObjectOrArrayEquivalent(firstValue, secondValue, firstStack, secondStack) { 53 | const firstKeys = Object.keys(firstValue); 54 | const secondKeys = Object.keys(secondValue); 55 | 56 | if (firstKeys.length !== secondKeys.length) { 57 | return false; 58 | } 59 | 60 | let index = -1; 61 | 62 | while (++index < firstStack.length) { 63 | if (firstStack[index] === firstValue) { 64 | return secondStack[index] === secondValue; 65 | } 66 | } 67 | 68 | firstStack.push(firstValue); 69 | secondStack.push(secondValue); 70 | 71 | index = -1; 72 | 73 | let key; 74 | 75 | while (++index < firstKeys.length) { 76 | key = firstKeys[index]; 77 | 78 | /* eslint-disable no-use-before-define */ 79 | if (!isEquivalent(firstValue[key], secondValue[key], firstStack, secondStack)) { 80 | /* eslint-enable */ 81 | return false; 82 | } 83 | } 84 | 85 | firstStack.pop(); 86 | secondStack.pop(); 87 | 88 | return true; 89 | } 90 | 91 | /** 92 | * @function isEquivalent 93 | * 94 | * @description 95 | * are the two values passed equal in value 96 | * 97 | * @param {*} firstValue the first value to compare 98 | * @param {*} secondValue the second value to compare 99 | * @param {Array<*>} firstStack the first stack of objects already compared to support circular objects 100 | * @param {Array<*>} secondStack the second stack of objects already compared to support circular objects 101 | * @returns {boolean} are the first and second value equivalent 102 | */ 103 | export default function isEquivalent(firstValue, secondValue, firstStack = [], secondStack = []) { 104 | if (firstValue === secondValue) { 105 | return true; 106 | } 107 | 108 | const objectClass = getObjectClass(firstValue); 109 | 110 | if (objectClass !== getObjectClass(secondValue)) { 111 | return false; 112 | } 113 | 114 | if (isNAN(firstValue)) { 115 | return isNAN(secondValue); 116 | } 117 | 118 | if (VALUE_OF_COMPARE_REGEXP.test(objectClass)) { 119 | return firstValue.valueOf() === secondValue.valueOf(); 120 | } 121 | 122 | if (objectClass === 'Error') { 123 | return firstValue.name === secondValue.name && firstValue.message === secondValue.message; 124 | } 125 | 126 | if (objectClass === 'RegExp') { 127 | return firstValue.toString() === secondValue.toString(); 128 | } 129 | 130 | if (ITERATOR_COMPARE_REGEXP.test(objectClass)) { 131 | return isEquivalent( 132 | getArrayFromIterator(firstValue.entries()), 133 | getArrayFromIterator(secondValue.entries()), 134 | firstStack, 135 | secondStack 136 | ); 137 | } 138 | 139 | if (OBJECT_OR_ARRAY_COMPARE_REGEXP.test(objectClass)) { 140 | return isObjectOrArrayEquivalent(firstValue, secondValue, firstStack, secondStack); 141 | } 142 | 143 | return false; 144 | } 145 | --------------------------------------------------------------------------------