UNPKG

jsverify

Version:

Property-based testing for JavaScript.

275 lines (226 loc) 5.75 kB
/* @flow weak */ "use strict"; var isArray = Array.isArray; function isObject(o) { /* eslint-disable no-new-object */ return new Object(o) === o; /* eslint-enable no-new-object */ } /* undefined-safe isNaN */ function isNaN(n) { return typeof n === "number" && n !== n; } /** ### Utility functions Utility functions are exposed (and documented) only to make contributions to jsverify more easy. The changes here don't follow semver, i.e. there might be backward-incompatible changes even in patch releases. Use [underscore.js](http://underscorejs.org/), [lodash](https://lodash.com/), [ramda](http://ramda.github.io/ramdocs/docs/), [lazy.js](http://danieltao.com/lazy.js/) or some other utility belt. */ /* Simple sort */ function sort(arr) { var res = arr.slice(); res.sort(); return res; } /** - `utils.isEqual(x: json, y: json): bool` Equality test for `json` objects. */ function isEqual(a, b) { var i; if (isNaN(a) && isNaN(b)) { return true; } if (a === b) { return true; } else if (isArray(a) && isArray(b) && a.length === b.length) { for (i = 0; i < a.length; i++) { if (!isEqual(a[i], b[i])) { return false; } } return true; } else if (isObject(a) && isObject(b) && !isArray(a) && !isArray(b)) { var akeys = Object.keys(a); var bkeys = Object.keys(b); if (!isEqual(sort(akeys), sort(bkeys))) { return false; } for (i = 0; i < akeys.length; i++) { if (!isEqual(a[akeys[i]], b[akeys[i]])) { return false; } } return true; } return false; } /** - `utils.isApproxEqual(x: a, y: b, opts: obj): bool` Tests whether two objects are approximately and optimistically equal. Returns `false` only if they are distinguishable not equal. Returns `true` when `x` and `y` are `NaN`. This function works with cyclic data. Takes optional 'opts' parameter with properties: - `fnEqual` - whether all functions are considered equal (default: yes) - `depth` - how deep to recurse until treating as equal (default: 5) */ function isApproxEqual(x, y, opts) { opts = opts || {}; var fnEqual = opts.fnEqual !== false; var depth = opts.depth || 5; // totally arbitrary // state contains pairs we checked (or are still checking, but assume equal!) var state = []; // eslint-disable-next-line complexity function loop(a, b, n) { if (isNaN(a) && isNaN(b)) { return true; } // trivial check if (a === b) { return true; } // depth check if (n >= depth) { return true; } var i; // check if pair already occured for (i = 0; i < state.length; i++) { if (state[i][0] === a && state[i][1] === b) { return true; } } // add to state state.push([a, b]); if (typeof a === "function" && typeof b === "function") { return fnEqual; } if (isArray(a) && isArray(b) && a.length === b.length) { for (i = 0; i < a.length; i++) { if (!loop(a[i], b[i], n + 1)) { return false; } } return true; } else if (isObject(a) && isObject(b) && !isArray(a) && !isArray(b)) { var akeys = Object.keys(a); var bkeys = Object.keys(b); if (!loop(sort(akeys), sort(bkeys), n + 1)) { return false; } for (i = 0; i < akeys.length; i++) { if (!loop(a[akeys[i]], b[akeys[i]], n + 1)) { return false; } } return true; } return false; } return loop(x, y, 0); } function identity(x) { return x; } function pluck(arr, key) { return arr.map(function (e) { return e[key]; }); } /** - `utils.force(x: a | () -> a) : a` Evaluate `x` as nullary function, if it is one. */ function force(arb) { return (typeof arb === "function") ? arb() : arb; } /** - `utils.merge(x... : obj): obj` Merge two objects, a bit like `_.extend({}, x, y)`. */ function merge() { var res = {}; for (var i = 0; i < arguments.length; i++) { var arg = arguments[i]; var keys = Object.keys(arg); for (var j = 0; j < keys.length; j++) { var key = keys[j]; res[key] = arg[key]; } } return res; } function div2(x) { return Math.floor(x / 2); } function log2(x) { return Math.log(x) / Math.log(2); } function ilog2(x) { return x <= 0 ? 0 : Math.floor(log2(x)); } function curriedN(n) { var n1 = n - 1; return function curriedNInstance(result, args) { if (args.length === n) { return result(args[n1]); } else { return result; } }; } var curried2 = curriedN(2); var curried3 = curriedN(3); function charArrayToString(arr) { return arr.join(""); } function stringToCharArray(str) { return str.split(""); } function pairArrayToDict(arrayOfPairs) { var res = {}; arrayOfPairs.forEach(function (p) { res[p[0]] = p[1]; }); return res; } function dictToPairArray(m) { var res = []; Object.keys(m).forEach(function (k) { res.push([k, m[k]]); }); return res; } function partition(arr, pred) { var truthy = []; var falsy = []; for (var i = 0; i < arr.length; i++) { var x = arr[i]; if (pred(x)) { truthy.push(x); } else { falsy.push(x); } } return [truthy, falsy]; } module.exports = { isArray: isArray, isObject: isObject, isEqual: isEqual, isApproxEqual: isApproxEqual, identity: identity, pluck: pluck, force: force, merge: merge, div2: div2, ilog2: ilog2, curried2: curried2, curried3: curried3, charArrayToString: charArrayToString, stringToCharArray: stringToCharArray, pairArrayToDict: pairArrayToDict, dictToPairArray: dictToPairArray, partition: partition, };