@ebflat9/fp
Advanced tools
Comparing version 1.1.17 to 1.1.19
18
index.js
@@ -69,6 +69,5 @@ import { | ||
match, | ||
maybe, | ||
memoize, | ||
multipy, | ||
multipyRight, | ||
multiply, | ||
multiplyRight, | ||
not, | ||
@@ -83,3 +82,2 @@ once, | ||
pluck, | ||
pop, | ||
pow, | ||
@@ -89,3 +87,2 @@ prepend, | ||
props, | ||
push, | ||
range, | ||
@@ -99,3 +96,2 @@ reduceAsync, | ||
setPropM, | ||
shift, | ||
some, | ||
@@ -119,3 +115,2 @@ sortBy, | ||
unary, | ||
unshift, | ||
zipMap, | ||
@@ -268,7 +263,6 @@ } from './src/combinators.js' | ||
Maybe, | ||
maybe, | ||
memoize, | ||
memoizeIter, | ||
multipy, | ||
multipyRight, | ||
multiply, | ||
multiplyRight, | ||
not, | ||
@@ -287,3 +281,2 @@ Nothing, | ||
pluck, | ||
pop, | ||
pow, | ||
@@ -295,3 +288,2 @@ Prepend, | ||
provided, | ||
push, | ||
range, | ||
@@ -310,3 +302,2 @@ reactivize, | ||
setPropM, | ||
shift, | ||
some, | ||
@@ -337,3 +328,2 @@ sortBy, | ||
unless, | ||
unshift, | ||
untilWith, | ||
@@ -340,0 +330,0 @@ wrapWith, |
{ | ||
"name": "@ebflat9/fp", | ||
"version": "1.1.17", | ||
"version": "1.1.19", | ||
"description": "my fp utils", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -1,4 +0,12 @@ | ||
# My lil functional programming collection | ||
# fp | ||
Just a few functions I don't like re-writing. I am slowly adding tests, run with | ||
`npm run test`. | ||
My little functional programming library. Just a few functions I don't like | ||
re-writing. I am slowly adding tests, run with `npm run test` and you should see | ||
82 test passing. | ||
Features: | ||
- Many utility functions, such as `compose`, `pipe`, and `curry`. | ||
- Some ADTs such as `Maybe`, `Result`, and `IO` | ||
- A simple reactive library for `Observable`, including methods like `map`, | ||
`filter`, and `reduce`. |
@@ -1,8 +0,21 @@ | ||
// identity x returns x | ||
/** | ||
* Identity | ||
* @param {any} x | ||
* @return {any} x | ||
*/ | ||
export const identity = x => x | ||
// constant () => a | ||
/** | ||
* Constant | ||
* @param {any} a | ||
* @returns {any} a | ||
*/ | ||
export const constant = a => b => a | ||
// arity functions | ||
/** | ||
* Arity | ||
* @param {function} fn | ||
* @param {number} n - desired arity | ||
* @returns {function} arity - Function fn with new arity | ||
*/ | ||
export const arity = (fn, n) => | ||
@@ -12,7 +25,30 @@ function arity(...args) { | ||
} | ||
/** | ||
* Unary | ||
* @param {function} fn | ||
* @returns {function} arity - Function with arity of 1 | ||
*/ | ||
export const unary = fn => arity(fn, 1) | ||
/** | ||
* Binary | ||
* @param {function} fn | ||
* @returns {function} arity - Function with arity of 2 | ||
*/ | ||
export const binary = fn => arity(fn, 2) | ||
/** | ||
* Ternary | ||
* @param {function} fn | ||
* @returns {function} arity - Function with arity of 3 | ||
*/ | ||
export const ternary = fn => arity(fn, 3) | ||
// partial application | ||
/** | ||
* Call First | ||
* @param {function} fn - Function to partially apply | ||
* @param {any} larg - Leftmost argument | ||
* @returns {function} callFirst - Function fn partially applied with larg | ||
*/ | ||
export const callFirst = (fn, larg) => | ||
@@ -22,2 +58,9 @@ function callFirst(...args) { | ||
} | ||
/** | ||
* Call Last | ||
* @param {function} fn - Function to partially apply | ||
* @param {any} rarg - Rightmost argument | ||
* @returns {function} callLast - Function fn partially applied with rarg | ||
*/ | ||
export const callLast = (fn, rarg) => | ||
@@ -28,19 +71,14 @@ function callLast(...args) { | ||
// de-methodize | ||
/** | ||
* Demethodize | ||
* @param {method} method - Method to demethodize | ||
* @returns {function} method bound to use as regular function | ||
*/ | ||
export const demethodize = Function.prototype.bind.bind(Function.prototype.call) | ||
// typeof functions | ||
const isTypeOf = a => b => typeof b === a | ||
export const isNumber = isTypeOf('number') | ||
export const isBoolean = isTypeOf('boolean') | ||
export const isNull = x => x === null | ||
export const isString = isTypeOf('string') | ||
export const isObject = x => x !== null && typeof x === 'object' | ||
export const isArray = a => Array.isArray(a) | ||
export const isInstanceOf = a => b => b instanceof a | ||
export const isFunction = f => f && typeof f === 'function' | ||
export const isSet = s => s instanceof Set | ||
export const isMap = m => m instanceof Map | ||
// Len gets the length argument a | ||
/** | ||
* Len - provides a simple way to get the length/size of something | ||
* @param {any} a | ||
* @returns {number} {undefined} The length or size of the argument | ||
*/ | ||
export const len = a => | ||
@@ -60,6 +98,24 @@ isString(a) || isArray(a) || isFunction(a) | ||
} | ||
/** | ||
* Compose | ||
* @param {function} Any number of functions fns to compose | ||
* @returns {function} A function composed of fns | ||
*/ | ||
export const compose = (...fns) => fns.reduce(compose2) | ||
/** | ||
* Pipe | ||
* @param {function} fns to pipe | ||
* @returns {function} A function pipe of fns | ||
*/ | ||
export const pipe = (...fns) => fns.reduceRight(compose2) | ||
// Autocurry | ||
/** | ||
* Curry | ||
* @param {function} fn - Function to curry | ||
* @returns {function} Partially applied function, or result of calling | ||
* function fn if arguments are greater than or equal to total arity of | ||
* function fn. | ||
*/ | ||
export const curry = fn => | ||
@@ -76,8 +132,44 @@ function curryInner(...args1) { | ||
// run a side effect with tap | ||
/** | ||
* Typeof Functions | ||
* Provides several functions to test whether x is of type y | ||
*/ | ||
const isTypeOf = a => b => typeof b === a | ||
export const isNumber = isTypeOf('number') | ||
export const isBoolean = isTypeOf('boolean') | ||
export const isNull = x => x === null | ||
export const isString = isTypeOf('string') | ||
export const isObject = x => x !== null && typeof x === 'object' | ||
export const isArray = a => Array.isArray(a) | ||
export const isInstanceOf = curry((a, b) => b instanceof a) | ||
export const isFunction = f => f && typeof f === 'function' | ||
export const isSet = s => s instanceof Set | ||
export const isMap = m => m instanceof Map | ||
/** | ||
* Tap | ||
* @param {function} fn - Side effect to run | ||
* @param {any} x - Value to return | ||
*/ | ||
export const tap = curry((fn, x) => (fn(x), x)) | ||
// not and invert | ||
/** | ||
* Not | ||
* @param {function} f - Function to negate | ||
* @param {any} a - Argument for function f | ||
*/ | ||
export const not = curry((f, a) => !f(a)) | ||
/** | ||
* Invert | ||
* @param {function} f - Function to reverse the sign of result | ||
* @param {any} a - Argument for function f | ||
*/ | ||
export const invert = curry((f, a) => -f(a)) | ||
/** | ||
* Flip2 | ||
* @param {function} f - Function to flip arguments | ||
* @returns {function} flip - Function f with arguments a and b flipped | ||
*/ | ||
export const flip2 = f => | ||
@@ -87,2 +179,9 @@ curry(function flip(a, b) { | ||
}) | ||
/** | ||
* Flip3 | ||
* @param {function} f - Function to flip arguments | ||
* @returns {function} flip - Function f with | ||
* arguments a, b, c flipped to b, c, a. | ||
*/ | ||
export const flip3 = f => | ||
@@ -93,4 +192,15 @@ curry(function flip(a, b, c) { | ||
// Logging | ||
/** | ||
* Tee - Logs argument and returns it | ||
* @param {any} | ||
* @returns {any} | ||
*/ | ||
export const tee = tap(console.log.bind(console)) | ||
/** | ||
* Log | ||
* @param {function} fn - Function to log | ||
* @param {function} logger - Logging function | ||
* @returns {function} log - Function fn with enhanced logging | ||
*/ | ||
export const log = (fn, logger = console.log.bind(console)) => | ||
@@ -104,11 +214,32 @@ function log(...args) { | ||
// creates a Transducer function | ||
/** | ||
* Transduce | ||
* @param {array} arr - Array to reduce | ||
* @param {array} fns - Array of functions to apply to arr | ||
* @param {function} reducer - Reducer function to apply to arr | ||
* @param {any} initial - Initial value to pass to reducer | ||
*/ | ||
export const transduce = curry((arr, fns, reducer, initial) => | ||
arr.reduce(compose(...fns)(reducer), initial) | ||
) | ||
// Transducers | ||
/** | ||
* MapTR | ||
* @param {function} fn - Create a transducer from map function | ||
* @returns {function} | ||
*/ | ||
export const mapTR = fn => reducer => (acc, val) => reducer(acc, fn(val)) | ||
/** | ||
* filterTR | ||
* @param {function} fn - Create a transducer from a filter function | ||
* @returns {function} | ||
*/ | ||
export const filterTR = fn => reducer => (acc, val) => fn(val) ? reducer(acc, val) : acc | ||
// prop & props get object properties | ||
/** | ||
* Prop | ||
* @param {string} name - Property name | ||
* @param {object} a - Object to get property in | ||
*/ | ||
export const prop = curry( | ||
@@ -118,2 +249,10 @@ (name, a) => | ||
) | ||
/** | ||
* Send | ||
* @param {string} name - Property name | ||
* @param {any} args - Arguments to send to instance method | ||
* @returns {function} send - Function send takes an instance and calls | ||
* instance#name with args | ||
*/ | ||
export const send = | ||
@@ -123,2 +262,10 @@ (name, ...args) => | ||
instance[name].apply(instance, args) | ||
/** | ||
* Bound | ||
* @param {name} name - Property name | ||
* @param {any} args - Arguments to send to bound method | ||
* @returns {function} {any} Returns bound method or bound method called with | ||
* args | ||
*/ | ||
export const bound = (name, ...args) => | ||
@@ -128,9 +275,40 @@ args === [] | ||
: instance => Function.prototype.bind.apply(instance[name], [instance].concat(args)) | ||
/** | ||
* SetPropM | ||
* @param {name} name - Property name | ||
* @param {value} value - New value to set | ||
* @param {object} a - Object to mutate with new value | ||
* @returns {object} a | ||
*/ | ||
export const setPropM = curry((name, value, a) => | ||
a && name in a ? ((a[name] = value), a) : a | ||
) | ||
/** | ||
* SetProp | ||
* @param {name} name - Property name | ||
* @param {value} value - New value to set | ||
* @param {object} a - Object to set value in | ||
* @returns {object} Copy of a with new value set | ||
*/ | ||
export const setProp = curry((name, value, a) => | ||
a && name in a ? { ...a, [name]: value } : { ...a } | ||
) | ||
/** | ||
* Props | ||
* @param {array} names - Array of property names | ||
* @param {object} a - Object to get property names from | ||
* @returns {array} Array of values | ||
*/ | ||
export const props = curry((names, a) => names.map(n => prop(n, a))) | ||
/** | ||
* Invoke | ||
* @param {function} fn - Function to invoke in new context | ||
* @param {any} args - Argument for function fn | ||
* @returns {function} invoke - Function which takes instance and calls fn with | ||
* args in context of instance | ||
*/ | ||
export const invoke = | ||
@@ -141,2 +319,9 @@ (fn, ...args) => | ||
/** | ||
* DeepProp | ||
* @param {string} {array} path - A path of properties or an Array of | ||
* properties to get | ||
* @param {object} a - Object to get properties from | ||
* @returns {any} Value of property access | ||
*/ | ||
export const deepProp = curry((path, a) => { | ||
@@ -148,2 +333,7 @@ if (!Array.isArray(path)) path = path.split('.') | ||
/** | ||
* Stringifying functions | ||
* Provides helper functions to stringify and parse JSON, along with numbers | ||
* and strings | ||
*/ | ||
export const toJSON = x => JSON.stringify(x) | ||
@@ -162,3 +352,6 @@ export const fromJSON = x => JSON.parse(x) | ||
// map, filter, reduce | ||
/** | ||
* Monad-related functions | ||
* Provides functions to help when working with Monads, such as Array | ||
*/ | ||
export const forEach = curry((f, M) => M.forEach(f)) | ||
@@ -176,4 +369,2 @@ export const map = curry((f, M) => M.map(f)) | ||
} | ||
// compose monads | ||
export const composeM2 = (f, g) => | ||
@@ -184,3 +375,6 @@ function innerComposeM2(...args) { | ||
export const composeM = (...Ms) => Ms.reduce(composeM2) | ||
export const liftA2 = curry((fn, a1, a2) => a1.map(fn).ap(a2)) | ||
export const liftA3 = curry((fn, a1, a2, a3) => a1.map(fn).ap(a2).ap(a3)) | ||
export const liftA4 = curry((fn, a1, a2, a3, a4) => a1.map(fn).ap(a2).ap(a3).ap(a4)) | ||
export const apply = curry((fn, F) => map.call(F, fn)) | ||
export const composeAsync2 = (f, g) => | ||
@@ -190,7 +384,2 @@ async function innerComposeAsync(...args) { | ||
} | ||
export const liftA2 = curry((fn, a1, a2) => a1.map(fn).ap(a2)) | ||
export const liftA3 = curry((fn, a1, a2, a3) => a1.map(fn).ap(a2).ap(a3)) | ||
export const liftA4 = curry((fn, a1, a2, a3, a4) => a1.map(fn).ap(a2).ap(a3).ap(a4)) | ||
export const apply = curry((fn, F) => map.call(F, fn)) | ||
export const composeAsync = (...fns) => fns.reduce(composeAsync2) | ||
@@ -203,4 +392,2 @@ export const pipeAsync = (...fns) => fns.reduceRight(composeAsync2) | ||
await mapAsync(f, a).then(bools => a.filter((_, i) => Boolean(bools[i]))) | ||
// flat | ||
export const flat = M => M.flat() | ||
@@ -211,3 +398,6 @@ export const flatMap = curry((f, M) => M.flatMap(f)) | ||
// math functions | ||
/** | ||
* Math functions | ||
* Provides a set of functions for common math operations | ||
*/ | ||
export const eq = curry((a, b) => a === b) | ||
@@ -218,4 +408,4 @@ export const add = curry((x, y) => x + y) | ||
export const subtractRight = curry((x, y) => y - x) | ||
export const multipy = curry((x, y) => x * y) | ||
export const multipyRight = curry((x, y) => y * x) | ||
export const multiply = curry((x, y) => x * y) | ||
export const multiplyRight = curry((x, y) => y * x) | ||
export const divide = curry((x, y) => x / y) | ||
@@ -227,3 +417,6 @@ export const divideRight = curry((x, y) => y / x) | ||
// array functions | ||
/** | ||
* Array functions | ||
* Provides a set of functions for common array operations | ||
*/ | ||
export const head = a => a[0] | ||
@@ -236,6 +429,2 @@ export const last = a => a[a.length - 1] | ||
export const average = ns => sum(...ns) / ns.length | ||
export const shift = arr => [arr[0], arr.slice(1)] | ||
export const pop = arr => [arr.slice(0, -1), arr[arr.length - 1]] | ||
export const unshift = curry((arr, v) => [v].concat(arr)) | ||
export const push = curry((arr, v) => arr.concat(v)) | ||
export const partition = (arr, a, b) => | ||
@@ -263,2 +452,8 @@ arr.reduce( | ||
/** | ||
* TryCatch | ||
* @param {function} f - Try function, may throw | ||
* @param {function} g - Catch function, to catch error | ||
* @returns {any} Calls g if function f throws | ||
*/ | ||
export const tryCatch = curry((f, g) => { | ||
@@ -272,10 +467,10 @@ try { | ||
export const maybe = fn => | ||
function maybe(...args) { | ||
return args.reduce((acc, cv) => acc && cv != null, true) | ||
? fn.apply(this, args) | ||
: void 0 | ||
} | ||
// range | ||
/** | ||
* Range | ||
* @param {number} start | ||
* @param {number} end | ||
* @param {number} step | ||
* @returns {array} result - An array of numbers from start to end, spaced by | ||
* step | ||
*/ | ||
export const range = (start, end, step = start < end ? 1 : -1) => { | ||
@@ -292,3 +487,8 @@ let index = -1 | ||
// once only runs a function once, then returns cached result | ||
/** | ||
* Once | ||
* @param {function} fn - Function to run only once | ||
* @returns {function} once - Function fn will be called once, and thereafter | ||
* will return the cached result of the call | ||
*/ | ||
export function once(fn) { | ||
@@ -302,3 +502,7 @@ let done = false | ||
// memoize a function | ||
/** | ||
* Memoize | ||
* @param {function} fn - Function to memoize | ||
* @returns {function} memorize - Memoized function fn | ||
*/ | ||
export function memoize(fn) { | ||
@@ -315,3 +519,8 @@ const cache = Object.create(null) | ||
// debounce | ||
/** | ||
* Debounce | ||
* @param {number} delay - Amount of time to debounce | ||
* @returns {function} debounce - Function which takes an argument fn, which is | ||
* a function to debounce | ||
*/ | ||
export const debounce = delay => { | ||
@@ -325,3 +534,8 @@ let pending = false | ||
// accumulate | ||
/** | ||
* Accumulate | ||
* @param {number} delay - Amount of time to delay result | ||
* @returns {function} accumulate - Function which takes argument fn, | ||
* a function that will be called with all accumulated events after delay | ||
*/ | ||
export const accumulate = delay => { | ||
@@ -343,3 +557,9 @@ const stack = [] | ||
// Object functions | ||
/** | ||
* FunctionalMixin | ||
* @param {object} behaviour - Desired mixin behaviour | ||
* @param {object} sharedBehaviour - Desired behaviour to add to prototype | ||
* @returns {function} mixin - Function which takes argument target, which is | ||
* the object to mix behaviour into | ||
*/ | ||
export const FunctionalMixin = (behaviour, sharedBehaviour = {}) => { | ||
@@ -434,2 +654,7 @@ const instanceKeys = Reflect.ownKeys(behaviour) | ||
/** | ||
* DeepFreeze | ||
* @param {object} obj - Object to deep freeze | ||
* @returns {object} obj - Object that was deep frozen | ||
*/ | ||
export const deepFreeze = obj => { | ||
@@ -443,2 +668,7 @@ if (obj && typeof obj === 'object' && !Object.isFrozen(obj)) { | ||
/** | ||
* DeepCopy | ||
* @param {object} obj - Object to deep copy | ||
* @returns {object} aux - Copy of Object obj | ||
*/ | ||
export const deepCopy = obj => { | ||
@@ -453,2 +683,8 @@ let aux = obj | ||
Object.deepFreeze = Object.deepFreeze || deepFreeze | ||
/** | ||
* Immutate | ||
* @param {object} Object to seal and deep freeze | ||
* @returns {object} Object that is sealed and deep frozen | ||
*/ | ||
export const immutable = compose(Object.seal, Object.deepFreeze) |
import * as combinators from '../src/combinators.js' | ||
import { describe, it } from 'mocha' | ||
import { strict as assert } from 'assert' | ||
import { AssertionError } from 'assert/strict' | ||
@@ -366,2 +367,244 @@ describe('Combinators', function () { | ||
}) | ||
describe('padStart', function () { | ||
it('should pad the start of a string', function () { | ||
assert.equal(combinators.padStart(1, 3, ' '), ' 1') | ||
assert.equal(combinators.padStart('', 5, 'hi'), 'hihih') | ||
}) | ||
}) | ||
describe('padEnd', function () { | ||
it('should pad the end of a string', function () { | ||
assert.equal(combinators.padEnd(1, 3, ' '), '1 ') | ||
assert.equal(combinators.padEnd('', 3, 'a'), 'aaa') | ||
}) | ||
}) | ||
describe('forEach', function () { | ||
it('should call M#forEach', function () { | ||
combinators.forEach(num => assert.equal(num, 1))([1, 1, 1, 1]) | ||
}) | ||
}) | ||
describe('map', function () { | ||
it('should call M#map', function () { | ||
const square = combinators.map(num => num * num)([1, 2, 3]) | ||
assert.deepEqual(square, [1, 4, 9]) | ||
}) | ||
}) | ||
describe('filter', function () { | ||
it('should call M#filter', function () { | ||
const evens = combinators.filter(n => n % 2 === 0)([1, 2, 3, 4, 5]) | ||
assert.deepEqual(evens, [2, 4]) | ||
}) | ||
}) | ||
describe('reduce', function () { | ||
it('should call M#reduce', function () { | ||
const result = combinators.reduce((acc, cv) => acc + cv, 0)([1, 2, 3, 4, 5]) | ||
assert.equal(result, 15) | ||
}) | ||
it('should call M#reduceRight', function () { | ||
const result = combinators.reduceRight((acc, cv) => acc + cv, 0)([1, 2, 3, 4, 5]) | ||
assert.equal(result, 15) | ||
}) | ||
}) | ||
describe('pluck', function () { | ||
it('should plunk object keys', function () { | ||
const arr = [{ name: 'bob' }, { name: 'tim' }] | ||
assert.deepEqual(combinators.pluck('name')(arr), ['bob', 'tim']) | ||
}) | ||
}) | ||
describe('deepMap', function () { | ||
it('should deeply map nested arrays', function () { | ||
const arr = [[1], [[3], [4, [5]]]] | ||
assert.deepEqual(combinators.deepMap(x => x * x)(arr), [[1], [[9], [16, [25]]]]) | ||
}) | ||
}) | ||
describe('composeM2', function () { | ||
it('should compose two monads', function () { | ||
const fn = x => [x] | ||
assert.deepEqual(combinators.composeM2(fn, fn)('hi'), ['hi']) | ||
}) | ||
}) | ||
describe('composeAsync2', function () { | ||
it('should compose two async functions', function (done) { | ||
const a = x => new Promise(resolve => setTimeout(() => resolve(x), 0)) | ||
const b = x => new Promise(resolve => setTimeout(() => resolve(x), 1)) | ||
const c = async x => await combinators.composeAsync2(a, b) | ||
;(async () => assert.equal(await c(5), 5), done())() | ||
}) | ||
}) | ||
describe('math functions', function () { | ||
it('should perform strict equality test', function () { | ||
assert.equal(combinators.eq(1, 1), true) | ||
assert.equal(combinators.eq({}, {}), false) | ||
}) | ||
it('should add numbers', function () { | ||
assert.equal(combinators.add(5)(6), 11) | ||
assert.equal(combinators.addRight(5)(6), 11) | ||
}) | ||
it('should subtract numbers', function () { | ||
assert.equal(combinators.subtract(5)(4), 1) | ||
assert.equal(combinators.subtract(4)(5), -1) | ||
assert.equal(combinators.subtractRight(4)(5), 1) | ||
assert.equal(combinators.subtractRight(5)(4), -1) | ||
}) | ||
it('should multiply numbers', function () { | ||
assert.equal(combinators.multiply(3)(3), 9) | ||
assert.equal(combinators.multiplyRight(5)(3), 15) | ||
}) | ||
it('should divide numbers', function () { | ||
assert.equal(combinators.divide(5)(2), 2.5) | ||
assert.equal(combinators.divide(2)(2), 1) | ||
assert.equal(combinators.divide(2)(0), Infinity) | ||
}) | ||
}) | ||
describe('roundTo', function () { | ||
it('should round to N places', function () { | ||
assert.equal(combinators.roundTo(2)(5.234), 5.23) | ||
}) | ||
}) | ||
describe('pow', function () { | ||
it('should multiply base by itself exponent times', function () { | ||
assert.equal(combinators.pow(10, 2), 100) | ||
assert.equal(combinators.pow(2, 10), 1024) | ||
}) | ||
}) | ||
describe('array functions', function () { | ||
it('should take the head of an array', function () { | ||
assert.equal(combinators.head([1, 2, 3]), 1) | ||
}) | ||
it('should take the last of an array', function () { | ||
assert.equal(combinators.last([1, 2, 3]), 3) | ||
}) | ||
it('should call A#every', function () { | ||
assert.equal(combinators.every(x => x > 1)([2, 3, 4]), true) | ||
}) | ||
it('should call A#some', function () { | ||
assert.equal(combinators.some(x => x > 2)([1, 2, 3]), true) | ||
}) | ||
it('should call A#find', function () { | ||
const arr = [{ name: 'bob' }, { name: 'tim' }] | ||
assert.equal(combinators.find(x => x.name === 'tim')(arr), arr[1]) | ||
}) | ||
it('should sum all arguments', function () { | ||
assert.equal(combinators.sum(1, 2, 3, 4, 5), 15) | ||
}) | ||
it('should average array', function () { | ||
assert.equal(combinators.average([1, 2, 3]), 2) | ||
}) | ||
it('should partition an array based on two functions', function () { | ||
const odd = x => x % 2 !== 0 | ||
const even = x => !odd(x) | ||
assert.deepEqual(combinators.partition([1, 2, 3, 4], even, odd), [ | ||
[2, 4], | ||
[1, 3], | ||
]) | ||
}) | ||
}) | ||
describe('zipMap', function () { | ||
it('should zip up iterables and map', function () { | ||
assert.deepEqual( | ||
combinators.zipMap((...args) => args.map(x => x * x), [1, 2, 3], [4, 5, 6]), | ||
[ | ||
[1, 16], | ||
[4, 25], | ||
[9, 36], | ||
] | ||
) | ||
}) | ||
}) | ||
describe('sortBy', function () { | ||
it('should sortBy function f without mutating array', function () { | ||
const arr = [1, 2, 3] | ||
const sorter = (a, b) => b - a | ||
assert.deepEqual(combinators.sortBy(sorter)(arr), [3, 2, 1]) | ||
assert.notEqual(combinators.sortBy(sorter)(arr), arr) | ||
}) | ||
}) | ||
describe('match', function () { | ||
it('should match a regexp to a string', function () { | ||
const re = new RegExp('\\s+') | ||
const str = ' ' | ||
assert.equal(combinators.match(re, str), true) | ||
}) | ||
}) | ||
describe('replace', function () { | ||
it('should replace a match with replacer', function () { | ||
assert.equal(combinators.replace('hi', 'hello')('hi world'), 'hello world') | ||
}) | ||
}) | ||
describe('split', function () { | ||
it('should split by sep', function () { | ||
assert.deepEqual(combinators.split('|', 'hi|there'), ['hi', 'there']) | ||
}) | ||
}) | ||
describe('tryCatch', function () { | ||
it('should call catcher function if try function throws', function () { | ||
let result | ||
combinators.tryCatch( | ||
() => { | ||
throw new Error('testing') | ||
}, | ||
err => (result = err.message) | ||
) | ||
assert.equal(result, 'testing') | ||
// try one without throwing error | ||
combinators.tryCatch( | ||
() => (result = 'hello'), | ||
() => {} | ||
) | ||
assert.equal(result, 'hello') | ||
}) | ||
}) | ||
describe('range', function () { | ||
it('should eagerly provide a range of numbers', function () { | ||
const nums = combinators.range(1, 10) | ||
assert.deepEqual(nums, [1, 2, 3, 4, 5, 6, 7, 8, 9]) | ||
}) | ||
it('should count down if negative step', function () { | ||
const nums = combinators.range(9, 0, -1) | ||
assert.deepEqual(nums, [9, 8, 7, 6, 5, 4, 3, 2, 1]) | ||
}) | ||
}) | ||
describe('once', function () { | ||
it('should call function only once', function () { | ||
let result = 1 | ||
const x = combinators.once(n => (result += n)) | ||
x(5) | ||
x(4) | ||
assert.equal(result, 6) | ||
assert.equal(x(), 6) | ||
}) | ||
}) | ||
describe('immutate', function () { | ||
it('should not allow changing properties', function () { | ||
const obj = combinators.immutable({ greeting: 'hi' }) | ||
assert.throws(() => (obj.greeting = 'hello')) | ||
}) | ||
it('should not allow extending object', function () { | ||
const obj = combinators.immutable({ greeting: 'hi' }) | ||
assert.throws(() => (obj.method = () => {})) | ||
}) | ||
}) | ||
}) |
Sorry, the diff of this file is too big to display
222190
7116
13