diff --git a/package-lock.json b/package-lock.json index f926175f2..eb1b879e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1228,6 +1228,12 @@ "repeat-element": "^1.1.2" } }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, "browserify-aes": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-0.4.0.tgz", @@ -1877,6 +1883,12 @@ "repeating": "^2.0.0" } }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, "docdown": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/docdown/-/docdown-0.7.3.tgz", @@ -2252,6 +2264,12 @@ } } }, + "esm": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.1.3.tgz", + "integrity": "sha512-W5DKpMvvvtdn4Wzwl9scBUlsdiy4sXALG5oKIrCgnalg6db6vAztKVHWfOW4R8TlFvXlZDjipXSTrqhJDHpdRg==", + "dev": true + }, "espree": { "version": "3.5.4", "resolved": "http://backoffice-npm-proxy.prod:4873/espree/-/espree-3.5.4.tgz", @@ -3209,6 +3227,12 @@ "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", "dev": true }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, "handlebars": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", @@ -4154,6 +4178,51 @@ } } }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", diff --git a/package.json b/package.json index 64a10f542..0fc75e3d1 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "test": "npm run test:main && npm run test:fp", "test:doc": "markdown-doctest doc/*.md", "test:fp": "node test/test-fp", + "test:wip": "mocha -r esm test/*.test.js", "test:main": "node test/test", "validate": "npm run style && npm run test" }, @@ -39,6 +40,7 @@ "ecstatic": "^2.1.0", "eslint": "^3.15.0", "eslint-plugin-import": "^2.14.0", + "esm": "^3.1.3", "fs-extra": "~1.0.0", "glob": "^7.1.1", "istanbul": "0.4.5", @@ -46,6 +48,7 @@ "lodash": "4.17.11", "lodash-doc-globals": "^0.1.1", "markdown-doctest": "^0.9.1", + "mocha": "^5.2.0", "optional-dev-dependency": "^2.0.0", "platform": "^1.3.3", "qunit-extras": "^3.0.0", diff --git a/test/Arrays-category-methods.js b/test/Arrays-category-methods.js new file mode 100644 index 000000000..79cd15954 --- /dev/null +++ b/test/Arrays-category-methods.js @@ -0,0 +1,97 @@ +import assert from 'assert'; +import { args, toArgs, identity } from './utils.js'; +import difference from '../difference.js'; +import union from '../union.js'; +import compact from '../compact.js'; +import drop from '../drop.js'; +import dropRight from '../dropRight.js'; +import dropRightWhile from '../dropRightWhile.js'; +import dropWhile from '../dropWhile.js'; +import findIndex from '../findIndex.js'; +import findLastIndex from '../findLastIndex.js'; +import flatten from '../flatten.js'; +import head from '../head.js'; +import indexOf from '../indexOf.js'; +import initial from '../initial.js'; +import intersection from '../intersection.js'; +import last from '../last.js'; +import lastIndexOf from '../lastIndexOf.js'; +import sortedIndex from '../sortedIndex.js'; +import sortedIndexOf from '../sortedIndexOf.js'; +import sortedLastIndex from '../sortedLastIndex.js'; +import sortedLastIndexOf from '../sortedLastIndexOf.js'; +import tail from '../tail.js'; +import take from '../take.js'; +import takeRight from '../takeRight.js'; +import takeRightWhile from '../takeRightWhile.js'; +import takeWhile from '../takeWhile.js'; +import uniq from '../uniq.js'; +import without from '../without.js'; +import zip from '../zip.js'; +import xor from '../xor.js'; + +describe('"Arrays" category methods', function() { + var args = toArgs([1, null, [3], null, 5]), + sortedArgs = toArgs([1, [3], 5, null, null]), + array = [1, 2, 3, 4, 5, 6]; + + it('should work with `arguments` objects', function() { + function message(methodName) { + return '`_.' + methodName + '` should work with `arguments` objects'; + } + + assert.deepStrictEqual(difference(args, [null]), [1, [3], 5], message('difference')); + assert.deepStrictEqual(difference(array, args), [2, 3, 4, 6], '_.difference should work with `arguments` objects as secondary arguments'); + + assert.deepStrictEqual(union(args, [null, 6]), [1, null, [3], 5, 6], message('union')); + assert.deepStrictEqual(union(array, args), array.concat([null, [3]]), '_.union should work with `arguments` objects as secondary arguments'); + + assert.deepStrictEqual(compact(args), [1, [3], 5], message('compact')); + assert.deepStrictEqual(drop(args, 3), [null, 5], message('drop')); + assert.deepStrictEqual(dropRight(args, 3), [1, null], message('dropRight')); + assert.deepStrictEqual(dropRightWhile(args,identity), [1, null, [3], null], message('dropRightWhile')); + assert.deepStrictEqual(dropWhile(args,identity), [null, [3], null, 5], message('dropWhile')); + assert.deepStrictEqual(findIndex(args, identity), 0, message('findIndex')); + assert.deepStrictEqual(findLastIndex(args, identity), 4, message('findLastIndex')); + assert.deepStrictEqual(flatten(args), [1, null, 3, null, 5], message('flatten')); + assert.deepStrictEqual(head(args), 1, message('head')); + assert.deepStrictEqual(indexOf(args, 5), 4, message('indexOf')); + assert.deepStrictEqual(initial(args), [1, null, [3], null], message('initial')); + assert.deepStrictEqual(intersection(args, [1]), [1], message('intersection')); + assert.deepStrictEqual(last(args), 5, message('last')); + assert.deepStrictEqual(lastIndexOf(args, 1), 0, message('lastIndexOf')); + assert.deepStrictEqual(sortedIndex(sortedArgs, 6), 3, message('sortedIndex')); + assert.deepStrictEqual(sortedIndexOf(sortedArgs, 5), 2, message('sortedIndexOf')); + assert.deepStrictEqual(sortedLastIndex(sortedArgs, 5), 3, message('sortedLastIndex')); + assert.deepStrictEqual(sortedLastIndexOf(sortedArgs, 1), 0, message('sortedLastIndexOf')); + assert.deepStrictEqual(tail(args, 4), [null, [3], null, 5], message('tail')); + assert.deepStrictEqual(take(args, 2), [1, null], message('take')); + assert.deepStrictEqual(takeRight(args, 1), [5], message('takeRight')); + assert.deepStrictEqual(takeRightWhile(args, identity), [5], message('takeRightWhile')); + assert.deepStrictEqual(takeWhile(args, identity), [1], message('takeWhile')); + assert.deepStrictEqual(uniq(args), [1, null, [3], 5], message('uniq')); + assert.deepStrictEqual(without(args, null), [1, [3], 5], message('without')); + assert.deepStrictEqual(zip(args, args), [[1, 1], [null, null], [[3], [3]], [null, null], [5, 5]], message('zip')); + }); + + it('should accept falsey primary arguments', function() { + function message(methodName) { + return '`_.' + methodName + '` should accept falsey primary arguments'; + } + + assert.deepStrictEqual(difference(null, array), [], message('difference')); + assert.deepStrictEqual(intersection(null, array), [], message('intersection')); + assert.deepStrictEqual(union(null, array), array, message('union')); + assert.deepStrictEqual(xor(null, array), array, message('xor')); + }); + + it('should accept falsey secondary arguments', function() { + function message(methodName) { + return '`_.' + methodName + '` should accept falsey secondary arguments'; + } + + assert.deepStrictEqual(difference(array, null), array, message('difference')); + assert.deepStrictEqual(intersection(array, null), [], message('intersection')); + assert.deepStrictEqual(union(array, null), array, message('union')); + }); +}); diff --git a/test/Strings-category-methods.js b/test/Strings-category-methods.js new file mode 100644 index 000000000..9e391a56e --- /dev/null +++ b/test/Strings-category-methods.js @@ -0,0 +1,43 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, stubString } from './utils.js'; + +describe('"Strings" category methods', function() { + var stringMethods = [ + 'camelCase', + 'capitalize', + 'escape', + 'kebabCase', + 'lowerCase', + 'lowerFirst', + 'pad', + 'padEnd', + 'padStart', + 'repeat', + 'snakeCase', + 'toLower', + 'toUpper', + 'trim', + 'trimEnd', + 'trimStart', + 'truncate', + 'unescape', + 'upperCase', + 'upperFirst' + ]; + + lodashStable.each(stringMethods, function(methodName) { + var func = _[methodName]; + + it('`_.' + methodName + '` should return an empty string for empty values', function() { + var values = [, null, undefined, ''], + expected = lodashStable.map(values, stubString); + + var actual = lodashStable.map(values, function(value, index) { + return index ? func(value) : func(); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); +}); diff --git a/test/__proto__-property-bugs.js b/test/__proto__-property-bugs.js new file mode 100644 index 000000000..4814e441d --- /dev/null +++ b/test/__proto__-property-bugs.js @@ -0,0 +1,87 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { LARGE_ARRAY_SIZE, isEven, _, create, stubFalse, objectProto, funcProto } from './utils.js'; +import difference from '../difference.js'; +import intersection from '../intersection.js'; +import uniq from '../uniq.js'; +import without from '../without.js'; +import groupBy from '../groupBy.js'; +import merge from '../merge.js'; + +describe('`__proto__` property bugs', function() { + it('should work with the "__proto__" key in internal data objects', function() { + var stringLiteral = '__proto__', + stringObject = Object(stringLiteral), + expected = [stringLiteral, stringObject]; + + var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, function(count) { + return isEven(count) ? stringLiteral : stringObject; + }); + + assert.deepStrictEqual(difference(largeArray, largeArray), []); + assert.deepStrictEqual(intersection(largeArray, largeArray), expected); + assert.deepStrictEqual(uniq(largeArray), expected); + assert.deepStrictEqual(without.apply(_, [largeArray].concat(largeArray)), []); + }); + + it('should treat "__proto__" as a regular key in assignments', function() { + var methods = [ + 'assign', + 'assignIn', + 'defaults', + 'defaultsDeep', + 'merge' + ]; + + var source = create(null); + source.__proto__ = []; + + var expected = lodashStable.map(methods, stubFalse); + + var actual = lodashStable.map(methods, function(methodName) { + var result = _[methodName]({}, source); + return result instanceof Array; + }); + + assert.deepStrictEqual(actual, expected); + + actual = groupBy([{ 'a': '__proto__' }], 'a'); + assert.ok(!(actual instanceof Array)); + }); + + it('should not merge "__proto__" properties', function() { + if (JSON) { + merge({}, JSON.parse('{"__proto__":{"a":1}}')); + + var actual = 'a' in objectProto; + delete objectProto.a; + + assert.ok(!actual); + } + }); + + it('should not indirectly merge builtin prototype properties', function() { + merge({}, { 'toString': { 'constructor': { 'prototype': { 'a': 1 } } } }); + + var actual = 'a' in funcProto; + delete funcProto.a; + + assert.ok(!actual); + + merge({}, { 'constructor': { 'prototype': { 'a': 1 } } }); + + actual = 'a' in objectProto; + delete objectProto.a; + + assert.ok(!actual); + }); + + it('should not indirectly merge `Object` properties', function() { + merge({}, { 'constructor': { 'a': 1 } }); + + var actual = 'a' in Object; + delete Object.a; + + assert.ok(!actual); + }); +}); diff --git a/test/add.test.js b/test/add.test.js new file mode 100644 index 000000000..cbc12a0bc --- /dev/null +++ b/test/add.test.js @@ -0,0 +1,15 @@ +import assert from 'assert'; +import add from '../add.js'; + +describe('add', function() { + it('should add two numbers', function() { + assert.strictEqual(add(6, 4), 10); + assert.strictEqual(add(-6, 4), -2); + assert.strictEqual(add(-6, -4), -10); + }); + + it('should not coerce arguments to numbers', function() { + assert.strictEqual(add('6', '4'), '64'); + assert.strictEqual(add('x', 'y'), 'xy'); + }); +}); diff --git a/test/after.js b/test/after.js new file mode 100644 index 000000000..cc7678f45 --- /dev/null +++ b/test/after.js @@ -0,0 +1,31 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _ } from './utils.js'; + +describe('after', function() { + function after(n, times) { + var count = 0; + lodashStable.times(times, _.after(n, function() { count++; })); + return count; + } + + it('should create a function that invokes `func` after `n` calls', function() { + assert.strictEqual(after(5, 5), 1, 'after(n) should invoke `func` after being called `n` times'); + assert.strictEqual(after(5, 4), 0, 'after(n) should not invoke `func` before being called `n` times'); + assert.strictEqual(after(0, 0), 0, 'after(0) should not invoke `func` immediately'); + assert.strictEqual(after(0, 1), 1, 'after(0) should invoke `func` when called once'); + }); + + it('should coerce `n` values of `NaN` to `0`', function() { + assert.strictEqual(after(NaN, 1), 1); + }); + + it('should use `this` binding of function', function() { + var after = _.after(1, function() { return ++this.count; }), + object = { 'after': after, 'count': 0 }; + + object.after(); + assert.strictEqual(object.after(), 2); + assert.strictEqual(object.count, 2); + }); +}); diff --git a/test/ary.js b/test/ary.js new file mode 100644 index 000000000..80e5fb286 --- /dev/null +++ b/test/ary.js @@ -0,0 +1,91 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice, _ } from './utils.js'; +import ary from '../ary.js'; +import curry from '../curry.js'; +import rearg from '../rearg.js'; + +describe('ary', function() { + function fn(a, b, c) { + return slice.call(arguments); + } + + it('should cap the number of arguments provided to `func`', function() { + var actual = lodashStable.map(['6', '8', '10'], ary(parseInt, 1)); + assert.deepStrictEqual(actual, [6, 8, 10]); + + var capped = ary(fn, 2); + assert.deepStrictEqual(capped('a', 'b', 'c', 'd'), ['a', 'b']); + }); + + it('should use `func.length` if `n` is not given', function() { + var capped = ary(fn); + assert.deepStrictEqual(capped('a', 'b', 'c', 'd'), ['a', 'b', 'c']); + }); + + it('should treat a negative `n` as `0`', function() { + var capped = ary(fn, -1); + + try { + var actual = capped('a'); + } catch (e) {} + + assert.deepStrictEqual(actual, []); + }); + + it('should coerce `n` to an integer', function() { + var values = ['1', 1.6, 'xyz'], + expected = [['a'], ['a'], []]; + + var actual = lodashStable.map(values, function(n) { + var capped = ary(fn, n); + return capped('a', 'b'); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should not force a minimum argument count', function() { + var args = ['a', 'b', 'c'], + capped = ary(fn, 3); + + var expected = lodashStable.map(args, function(arg, index) { + return args.slice(0, index); + }); + + var actual = lodashStable.map(expected, function(array) { + return capped.apply(undefined, array); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should use `this` binding of function', function() { + var capped = ary(function(a, b) { return this; }, 1), + object = { 'capped': capped }; + + assert.strictEqual(object.capped(), object); + }); + + it('should use the existing `ary` if smaller', function() { + var capped = ary(ary(fn, 1), 2); + assert.deepStrictEqual(capped('a', 'b', 'c'), ['a']); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var funcs = lodashStable.map([fn], ary), + actual = funcs[0]('a', 'b', 'c'); + + assert.deepStrictEqual(actual, ['a', 'b', 'c']); + }); + + it('should work when combined with other methods that use metadata', function() { + var array = ['a', 'b', 'c'], + includes = curry(rearg(ary(_.includes, 2), 1, 0), 2); + + assert.strictEqual(includes('b')(array, 2), true); + + includes = _(_.includes).ary(2).rearg(1, 0).curry(2).value(); + assert.strictEqual(includes('b')(array, 2), true); + }); +}); diff --git a/test/assign-and-assignIn.js b/test/assign-and-assignIn.js new file mode 100644 index 000000000..251d199ce --- /dev/null +++ b/test/assign-and-assignIn.js @@ -0,0 +1,88 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, defineProperty, stubOne, noop, stubNaN } from './utils.js'; + +describe('assign and assignIn', function() { + lodashStable.each(['assign', 'assignIn'], function(methodName) { + var func = _[methodName]; + + it('`_.' + methodName + '` should assign source properties to `object`', function() { + assert.deepStrictEqual(func({ 'a': 1 }, { 'b': 2 }), { 'a': 1, 'b': 2 }); + }); + + it('`_.' + methodName + '` should accept multiple sources', function() { + var expected = { 'a': 1, 'b': 2, 'c': 3 }; + assert.deepStrictEqual(func({ 'a': 1 }, { 'b': 2 }, { 'c': 3 }), expected); + assert.deepStrictEqual(func({ 'a': 1 }, { 'b': 2, 'c': 2 }, { 'c': 3 }), expected); + }); + + it('`_.' + methodName + '` should overwrite destination properties', function() { + var expected = { 'a': 3, 'b': 2, 'c': 1 }; + assert.deepStrictEqual(func({ 'a': 1, 'b': 2 }, expected), expected); + }); + + it('`_.' + methodName + '` should assign source properties with nullish values', function() { + var expected = { 'a': null, 'b': undefined, 'c': null }; + assert.deepStrictEqual(func({ 'a': 1, 'b': 2 }, expected), expected); + }); + + it('`_.' + methodName + '` should skip assignments if values are the same', function() { + var object = {}; + + var descriptor = { + 'configurable': true, + 'enumerable': true, + 'set': function() { throw new Error; } + }; + + var source = { + 'a': 1, + 'b': undefined, + 'c': NaN, + 'd': undefined, + 'constructor': Object, + 'toString': lodashStable.constant('source') + }; + + defineProperty(object, 'a', lodashStable.assign({}, descriptor, { + 'get': stubOne + })); + + defineProperty(object, 'b', lodashStable.assign({}, descriptor, { + 'get': noop + })); + + defineProperty(object, 'c', lodashStable.assign({}, descriptor, { + 'get': stubNaN + })); + + defineProperty(object, 'constructor', lodashStable.assign({}, descriptor, { + 'get': lodashStable.constant(Object) + })); + + try { + var actual = func(object, source); + } catch (e) {} + + assert.deepStrictEqual(actual, source); + }); + + it('`_.' + methodName + '` should treat sparse array sources as dense', function() { + var array = [1]; + array[2] = 3; + + assert.deepStrictEqual(func({}, array), { '0': 1, '1': undefined, '2': 3 }); + }); + + it('`_.' + methodName + '` should assign values of prototype objects', function() { + function Foo() {} + Foo.prototype.a = 1; + + assert.deepStrictEqual(func({}, Foo.prototype), { 'a': 1 }); + }); + + it('`_.' + methodName + '` should coerce string sources to objects', function() { + assert.deepStrictEqual(func({}, 'a'), { '0': 'a' }); + }); + }); +}); diff --git a/test/assignIn.js b/test/assignIn.js new file mode 100644 index 000000000..03a93beab --- /dev/null +++ b/test/assignIn.js @@ -0,0 +1,9 @@ +import assert from 'assert'; +import extend from '../extend.js'; +import assignIn from '../assignIn.js'; + +describe('assignIn', function() { + it('should be aliased', function() { + assert.strictEqual(extend, assignIn); + }); +}); diff --git a/test/assignInWith.js b/test/assignInWith.js new file mode 100644 index 000000000..e253b3f1e --- /dev/null +++ b/test/assignInWith.js @@ -0,0 +1,9 @@ +import assert from 'assert'; +import extendWith from '../extendWith.js'; +import assignInWith from '../assignInWith.js'; + +describe('assignInWith', function() { + it('should be aliased', function() { + assert.strictEqual(extendWith, assignInWith); + }); +}); diff --git a/test/assignWith-and-assignInWith.js b/test/assignWith-and-assignInWith.js new file mode 100644 index 000000000..70ec24a6b --- /dev/null +++ b/test/assignWith-and-assignInWith.js @@ -0,0 +1,22 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, noop } from './utils.js'; + +describe('assignWith and assignInWith', function() { + lodashStable.each(['assignWith', 'assignInWith'], function(methodName) { + var func = _[methodName]; + + it('`_.' + methodName + '` should work with a `customizer` callback', function() { + var actual = func({ 'a': 1, 'b': 2 }, { 'a': 3, 'c': 3 }, function(a, b) { + return a === undefined ? b : a; + }); + + assert.deepStrictEqual(actual, { 'a': 1, 'b': 2, 'c': 3 }); + }); + + it('`_.' + methodName + '` should work with a `customizer` that returns `undefined`', function() { + var expected = { 'a': 1 }; + assert.deepStrictEqual(func({}, expected, noop), expected); + }); + }); +}); diff --git a/test/at.js b/test/at.js new file mode 100644 index 000000000..e319fcccb --- /dev/null +++ b/test/at.js @@ -0,0 +1,124 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { empties, stubOne, falsey, args, LARGE_ARRAY_SIZE, square, identity } from './utils.js'; +import at from '../at.js'; + +describe('at', function() { + var array = ['a', 'b', 'c'], + object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; + + it('should return the elements corresponding to the specified keys', function() { + var actual = at(array, [0, 2]); + assert.deepStrictEqual(actual, ['a', 'c']); + }); + + it('should return `undefined` for nonexistent keys', function() { + var actual = at(array, [2, 4, 0]); + assert.deepStrictEqual(actual, ['c', undefined, 'a']); + }); + + it('should work with non-index keys on array values', function() { + var values = lodashStable.reject(empties, function(value) { + return (value === 0) || lodashStable.isArray(value); + }).concat(-1, 1.1); + + var array = lodashStable.transform(values, function(result, value) { + result[value] = 1; + }, []); + + var expected = lodashStable.map(values, stubOne), + actual = at(array, values); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return an empty array when no keys are given', function() { + assert.deepStrictEqual(at(array), []); + assert.deepStrictEqual(at(array, [], []), []); + }); + + it('should accept multiple key arguments', function() { + var actual = at(['a', 'b', 'c', 'd'], 3, 0, 2); + assert.deepStrictEqual(actual, ['d', 'a', 'c']); + }); + + it('should work with a falsey `object` when keys are given', function() { + var expected = lodashStable.map(falsey, lodashStable.constant(Array(4))); + + var actual = lodashStable.map(falsey, function(object) { + try { + return at(object, 0, 1, 'pop', 'push'); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with an `arguments` object for `object`', function() { + var actual = at(args, [2, 0]); + assert.deepStrictEqual(actual, [3, 1]); + }); + + it('should work with `arguments` object as secondary arguments', function() { + var actual = at([1, 2, 3, 4, 5], args); + assert.deepStrictEqual(actual, [2, 3, 4]); + }); + + it('should work with an object for `object`', function() { + var actual = at(object, ['a[0].b.c', 'a[1]']); + assert.deepStrictEqual(actual, [3, 4]); + }); + + it('should pluck inherited property values', function() { + function Foo() { + this.a = 1; + } + Foo.prototype.b = 2; + + var actual = at(new Foo, 'b'); + assert.deepStrictEqual(actual, [2]); + }); + + it('should work in a lazy sequence', function() { + var largeArray = lodashStable.range(LARGE_ARRAY_SIZE), + smallArray = array; + + lodashStable.each([[2], ['2'], [2, 1]], function(paths) { + lodashStable.times(2, function(index) { + var array = index ? largeArray : smallArray, + wrapped = _(array).map(identity).at(paths); + + assert.deepEqual(wrapped.value(), at(_.map(array, identity), paths)); + }); + }); + }); + + it('should support shortcut fusion', function() { + var array = lodashStable.range(LARGE_ARRAY_SIZE), + count = 0, + iteratee = function(value) { count++; return square(value); }, + lastIndex = LARGE_ARRAY_SIZE - 1; + + lodashStable.each([lastIndex, lastIndex + '', LARGE_ARRAY_SIZE, []], function(n, index) { + count = 0; + var actual = _(array).map(iteratee).at(n).value(), + expected = index < 2 ? 1 : 0; + + assert.strictEqual(count, expected); + + expected = index == 3 ? [] : [index == 2 ? undefined : square(lastIndex)]; + assert.deepEqual(actual, expected); + }); + }); + + it('work with an object for `object` when chaining', function() { + var paths = ['a[0].b.c', 'a[1]'], + actual = _(object).map(identity).at(paths).value(); + + assert.deepEqual(actual, at(_.map(object, identity), paths)); + + var indexObject = { '0': 1 }; + actual = _(indexObject).at(0).value(); + assert.deepEqual(actual, at(indexObject, 0)); + }); +}); diff --git a/test/attempt.test.js b/test/attempt.test.js new file mode 100644 index 000000000..d453ce0ac --- /dev/null +++ b/test/attempt.test.js @@ -0,0 +1,55 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice, errors, stubTrue, CustomError, realm } from './utils.js'; +import attempt from '../attempt.js'; + +describe('attempt', function() { + it('should return the result of `func`', function() { + assert.strictEqual(attempt(lodashStable.constant('x')), 'x'); + }); + + it('should provide additional arguments to `func`', function() { + var actual = attempt(function() { return slice.call(arguments); }, 1, 2); + assert.deepStrictEqual(actual, [1, 2]); + }); + + it('should return the caught error', function() { + var expected = lodashStable.map(errors, stubTrue); + + var actual = lodashStable.map(errors, function(error) { + return attempt(function() { throw error; }) === error; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should coerce errors to error objects', function() { + var actual = attempt(function() { throw 'x'; }); + assert.ok(lodashStable.isEqual(actual, Error('x'))); + }); + + it('should preserve custom errors', function() { + var actual = attempt(function() { throw new CustomError('x'); }); + assert.ok(actual instanceof CustomError); + }); + + it('should work with an error object from another realm', function() { + if (realm.errors) { + var expected = lodashStable.map(realm.errors, stubTrue); + + var actual = lodashStable.map(realm.errors, function(error) { + return attempt(function() { throw error; }) === error; + }); + + assert.deepStrictEqual(actual, expected); + } + }); + + it('should return an unwrapped value when implicitly chaining', function() { + assert.strictEqual(_(lodashStable.constant('x')).attempt(), 'x'); + }); + + it('should return a wrapped value when explicitly chaining', function() { + assert.ok(_(lodashStable.constant('x')).chain().attempt() instanceof _); + }); +}); diff --git a/test/basename.js b/test/basename.js new file mode 100644 index 000000000..19f6a3280 --- /dev/null +++ b/test/basename.js @@ -0,0 +1,151 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; + +import { + basename, + amd, + ui, + Worker, + QUnit, + lodashBizarro, + LARGE_ARRAY_SIZE, + symbol, + setProperty, +} from './utils.js'; + +import _VERSION from '../.internal/VERSION.js'; +import VERSION from '../VERSION.js'; + +describe(basename, function() { + it('should support loading ' + basename + ' as the "lodash" module', function() { + if (amd) { + assert.strictEqual((lodashModule || {}).moduleName, 'lodash'); + } + }); + + it('should support loading ' + basename + ' with the Require.js "shim" configuration option', function() { + if (amd && lodashStable.includes(ui.loaderPath, 'requirejs')) { + assert.strictEqual((shimmedModule || {}).moduleName, 'shimmed'); + } + }); + + it('should support loading ' + basename + ' as the "underscore" module', function() { + if (amd) { + assert.strictEqual((underscoreModule || {}).moduleName, 'underscore'); + } + }); + + it('should support loading ' + basename + ' in a web worker', function(done) { + if (Worker) { + var limit = 30000 / QUnit.config.asyncRetries, + start = +new Date; + + var attempt = function() { + var actual = _VERSION; + if ((new Date - start) < limit && typeof actual != 'string') { + setTimeout(attempt, 16); + return; + } + assert.strictEqual(actual, VERSION); + done(); + }; + + attempt(); + } + else { + done(); + } + }); + + it('should not add `Function.prototype` extensions to lodash', function() { + if (lodashBizarro) { + assert.ok(!('_method' in lodashBizarro)); + } + }); + + it('should avoid non-native built-ins', function() { + function message(lodashMethod, nativeMethod) { + return '`' + lodashMethod + '` should avoid overwritten native `' + nativeMethod + '`'; + } + + function Foo() { + this.a = 1; + } + Foo.prototype.b = 2; + + var object = { 'a': 1 }, + otherObject = { 'b': 2 }, + largeArray = lodashStable.times(LARGE_ARRAY_SIZE, lodashStable.constant(object)); + + if (lodashBizarro) { + try { + var actual = lodashBizarro.create(Foo.prototype); + } catch (e) { + actual = null; + } + var label = message('_.create', 'Object.create'); + assert.ok(actual instanceof Foo, label); + + try { + actual = [ + lodashBizarro.difference([object, otherObject], largeArray), + lodashBizarro.intersection(largeArray, [object]), + lodashBizarro.uniq(largeArray) + ]; + } catch (e) { + actual = null; + } + label = message('_.difference`, `_.intersection`, and `_.uniq', 'Map'); + assert.deepStrictEqual(actual, [[otherObject], [object], [object]], label); + + try { + if (Symbol) { + object[symbol] = {}; + } + actual = [ + lodashBizarro.clone(object), + lodashBizarro.cloneDeep(object) + ]; + } catch (e) { + actual = null; + } + label = message('_.clone` and `_.cloneDeep', 'Object.getOwnPropertySymbols'); + assert.deepStrictEqual(actual, [object, object], label); + + try { + // Avoid buggy symbol detection in Babel's `_typeof` helper. + var symObject = setProperty(Object(symbol), 'constructor', Object); + actual = [ + Symbol ? lodashBizarro.clone(symObject) : {}, + Symbol ? lodashBizarro.isEqual(symObject, Object(symbol)) : false, + Symbol ? lodashBizarro.toString(symObject) : '' + ]; + } catch (e) { + actual = null; + } + label = message('_.clone`, `_.isEqual`, and `_.toString', 'Symbol'); + assert.deepStrictEqual(actual, [{}, false, ''], label); + + try { + var map = new lodashBizarro.memoize.Cache; + actual = map.set('a', 1).get('a'); + } catch (e) { + actual = null; + } + label = message('_.memoize.Cache', 'Map'); + assert.deepStrictEqual(actual, 1, label); + + try { + map = new (Map || Object); + if (Symbol && Symbol.iterator) { + map[Symbol.iterator] = null; + } + actual = lodashBizarro.toArray(map); + } catch (e) { + actual = null; + } + label = message('_.toArray', 'Map'); + assert.deepStrictEqual(actual, [], label); + } + }); +}); diff --git a/test/before.js b/test/before.js new file mode 100644 index 000000000..d1a678b1e --- /dev/null +++ b/test/before.js @@ -0,0 +1,31 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _ } from './utils.js'; + +describe('before', function() { + function before(n, times) { + var count = 0; + lodashStable.times(times, _.before(n, function() { count++; })); + return count; + } + + it('should create a function that invokes `func` after `n` calls', function() { + assert.strictEqual(before(5, 4), 4, 'before(n) should invoke `func` before being called `n` times'); + assert.strictEqual(before(5, 6), 4, 'before(n) should not invoke `func` after being called `n - 1` times'); + assert.strictEqual(before(0, 0), 0, 'before(0) should not invoke `func` immediately'); + assert.strictEqual(before(0, 1), 0, 'before(0) should not invoke `func` when called'); + }); + + it('should coerce `n` values of `NaN` to `0`', function() { + assert.strictEqual(before(NaN, 1), 0); + }); + + it('should use `this` binding of function', function() { + var before = _.before(2, function() { return ++this.count; }), + object = { 'before': before, 'count': 0 }; + + object.before(); + assert.strictEqual(object.before(), 1); + assert.strictEqual(object.count, 1); + }); +}); diff --git a/test/bind.js b/test/bind.js new file mode 100644 index 000000000..efc16bf91 --- /dev/null +++ b/test/bind.js @@ -0,0 +1,231 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { push, falsey, stubTrue } from './utils.js'; +import bind from '../bind.js'; +import placeholder from '../placeholder.js'; + +describe('bind', function() { + function fn() { + var result = [this]; + push.apply(result, arguments); + return result; + } + + it('should bind a function to an object', function() { + var object = {}, + bound = bind(fn, object); + + assert.deepStrictEqual(bound('a'), [object, 'a']); + }); + + it('should accept a falsey `thisArg`', function() { + var values = lodashStable.reject(falsey.slice(1), function(value) { return value == null; }), + expected = lodashStable.map(values, function(value) { return [value]; }); + + var actual = lodashStable.map(values, function(value) { + try { + var bound = bind(fn, value); + return bound(); + } catch (e) {} + }); + + assert.ok(lodashStable.every(actual, function(value, index) { + return lodashStable.isEqual(value, expected[index]); + })); + }); + + it('should bind a function to nullish values', function() { + var bound = bind(fn, null), + actual = bound('a'); + + assert.ok((actual[0] === null) || (actual[0] && actual[0].Array)); + assert.strictEqual(actual[1], 'a'); + + lodashStable.times(2, function(index) { + bound = index ? bind(fn, undefined) : bind(fn); + actual = bound('b'); + + assert.ok((actual[0] === undefined) || (actual[0] && actual[0].Array)); + assert.strictEqual(actual[1], 'b'); + }); + }); + + it('should partially apply arguments ', function() { + var object = {}, + bound = bind(fn, object, 'a'); + + assert.deepStrictEqual(bound(), [object, 'a']); + + bound = bind(fn, object, 'a'); + assert.deepStrictEqual(bound('b'), [object, 'a', 'b']); + + bound = bind(fn, object, 'a', 'b'); + assert.deepStrictEqual(bound(), [object, 'a', 'b']); + assert.deepStrictEqual(bound('c', 'd'), [object, 'a', 'b', 'c', 'd']); + }); + + it('should support placeholders', function() { + var object = {}, + ph = bind.placeholder, + bound = bind(fn, object, ph, 'b', ph); + + assert.deepStrictEqual(bound('a', 'c'), [object, 'a', 'b', 'c']); + assert.deepStrictEqual(bound('a'), [object, 'a', 'b', undefined]); + assert.deepStrictEqual(bound('a', 'c', 'd'), [object, 'a', 'b', 'c', 'd']); + assert.deepStrictEqual(bound(), [object, undefined, 'b', undefined]); + }); + + it('should use `_.placeholder` when set', function() { + var _ph = placeholder = {}, + ph = bind.placeholder, + object = {}, + bound = bind(fn, object, _ph, 'b', ph); + + assert.deepEqual(bound('a', 'c'), [object, 'a', 'b', ph, 'c']); + delete placeholder; + }); + + it('should create a function with a `length` of `0`', function() { + var fn = function(a, b, c) {}, + bound = bind(fn, {}); + + assert.strictEqual(bound.length, 0); + + bound = bind(fn, {}, 1); + assert.strictEqual(bound.length, 0); + }); + + it('should ignore binding when called with the `new` operator', function() { + function Foo() { + return this; + } + + var bound = bind(Foo, { 'a': 1 }), + newBound = new bound; + + assert.strictEqual(bound().a, 1); + assert.strictEqual(newBound.a, undefined); + assert.ok(newBound instanceof Foo); + }); + + it('should handle a number of arguments when called with the `new` operator', function() { + function Foo() { + return this; + } + + function Bar() {} + + var thisArg = { 'a': 1 }, + boundFoo = bind(Foo, thisArg), + boundBar = bind(Bar, thisArg), + count = 9, + expected = lodashStable.times(count, lodashStable.constant([undefined, undefined])); + + var actual = lodashStable.times(count, function(index) { + try { + switch (index) { + case 0: return [new boundFoo().a, new boundBar().a]; + case 1: return [new boundFoo(1).a, new boundBar(1).a]; + case 2: return [new boundFoo(1, 2).a, new boundBar(1, 2).a]; + case 3: return [new boundFoo(1, 2, 3).a, new boundBar(1, 2, 3).a]; + case 4: return [new boundFoo(1, 2, 3, 4).a, new boundBar(1, 2, 3, 4).a]; + case 5: return [new boundFoo(1, 2, 3, 4, 5).a, new boundBar(1, 2, 3, 4, 5).a]; + case 6: return [new boundFoo(1, 2, 3, 4, 5, 6).a, new boundBar(1, 2, 3, 4, 5, 6).a]; + case 7: return [new boundFoo(1, 2, 3, 4, 5, 6, 7).a, new boundBar(1, 2, 3, 4, 5, 6, 7).a]; + case 8: return [new boundFoo(1, 2, 3, 4, 5, 6, 7, 8).a, new boundBar(1, 2, 3, 4, 5, 6, 7, 8).a]; + } + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should ensure `new bound` is an instance of `func`', function() { + function Foo(value) { + return value && object; + } + + var bound = bind(Foo), + object = {}; + + assert.ok(new bound instanceof Foo); + assert.strictEqual(new bound(true), object); + }); + + it('should append array arguments to partially applied arguments', function() { + var object = {}, + bound = bind(fn, object, 'a'); + + assert.deepStrictEqual(bound(['b'], 'c'), [object, 'a', ['b'], 'c']); + }); + + it('should not rebind functions', function() { + var object1 = {}, + object2 = {}, + object3 = {}; + + var bound1 = bind(fn, object1), + bound2 = bind(bound1, object2, 'a'), + bound3 = bind(bound1, object3, 'b'); + + assert.deepStrictEqual(bound1(), [object1]); + assert.deepStrictEqual(bound2(), [object1, 'a']); + assert.deepStrictEqual(bound3(), [object1, 'b']); + }); + + it('should not error when instantiating bound built-ins', function() { + var Ctor = bind(Date, null), + expected = new Date(2012, 4, 23, 0, 0, 0, 0); + + try { + var actual = new Ctor(2012, 4, 23, 0, 0, 0, 0); + } catch (e) {} + + assert.deepStrictEqual(actual, expected); + + Ctor = bind(Date, null, 2012, 4, 23); + + try { + actual = new Ctor(0, 0, 0, 0); + } catch (e) {} + + assert.deepStrictEqual(actual, expected); + }); + + it('should not error when calling bound class constructors with the `new` operator', function() { + var createCtor = lodashStable.attempt(Function, '"use strict";return class A{}'); + + if (typeof createCtor == 'function') { + var bound = bind(createCtor()), + count = 8, + expected = lodashStable.times(count, stubTrue); + + var actual = lodashStable.times(count, function(index) { + try { + switch (index) { + case 0: return !!(new bound); + case 1: return !!(new bound(1)); + case 2: return !!(new bound(1, 2)); + case 3: return !!(new bound(1, 2, 3)); + case 4: return !!(new bound(1, 2, 3, 4)); + case 5: return !!(new bound(1, 2, 3, 4, 5)); + case 6: return !!(new bound(1, 2, 3, 4, 5, 6)); + case 7: return !!(new bound(1, 2, 3, 4, 5, 6, 7)); + } + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + } + }); + + it('should return a wrapped value when chaining', function() { + var object = {}, + bound = _(fn).bind({}, 'a', 'b'); + + assert.ok(bound instanceof _); + + var actual = bound.value()('c'); + assert.deepEqual(actual, [object, 'a', 'b', 'c']); + }); +}); diff --git a/test/bindAll.js b/test/bindAll.js new file mode 100644 index 000000000..a085bd97b --- /dev/null +++ b/test/bindAll.js @@ -0,0 +1,74 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { args, toArgs, arrayProto } from './utils.js'; +import bindAll from '../bindAll.js'; + +describe('bindAll', function() { + var args = toArgs(['a']); + + var source = { + '_n0': -2, + '_p0': -1, + '_a': 1, + '_b': 2, + '_c': 3, + '_d': 4, + '-0': function() { return this._n0; }, + '0': function() { return this._p0; }, + 'a': function() { return this._a; }, + 'b': function() { return this._b; }, + 'c': function() { return this._c; }, + 'd': function() { return this._d; } + }; + + it('should accept individual method names', function() { + var object = lodashStable.cloneDeep(source); + bindAll(object, 'a', 'b'); + + var actual = lodashStable.map(['a', 'b', 'c'], function(key) { + return object[key].call({}); + }); + + assert.deepStrictEqual(actual, [1, 2, undefined]); + }); + + it('should accept arrays of method names', function() { + var object = lodashStable.cloneDeep(source); + bindAll(object, ['a', 'b'], ['c']); + + var actual = lodashStable.map(['a', 'b', 'c', 'd'], function(key) { + return object[key].call({}); + }); + + assert.deepStrictEqual(actual, [1, 2, 3, undefined]); + }); + + it('should preserve the sign of `0`', function() { + var props = [-0, Object(-0), 0, Object(0)]; + + var actual = lodashStable.map(props, function(key) { + var object = lodashStable.cloneDeep(source); + bindAll(object, key); + return object[lodashStable.toString(key)].call({}); + }); + + assert.deepStrictEqual(actual, [-2, -2, -1, -1]); + }); + + it('should work with an array `object`', function() { + var array = ['push', 'pop']; + bindAll(array); + assert.strictEqual(array.pop, arrayProto.pop); + }); + + it('should work with `arguments` objects as secondary arguments', function() { + var object = lodashStable.cloneDeep(source); + bindAll(object, args); + + var actual = lodashStable.map(args, function(key) { + return object[key].call({}); + }); + + assert.deepStrictEqual(actual, [1]); + }); +}); diff --git a/test/bindKey.js b/test/bindKey.js new file mode 100644 index 000000000..1489f30d3 --- /dev/null +++ b/test/bindKey.js @@ -0,0 +1,66 @@ +import assert from 'assert'; +import { slice } from './utils.js'; +import bindKey from '../bindKey.js'; + +describe('bindKey', function() { + it('should work when the target function is overwritten', function() { + var object = { + 'user': 'fred', + 'greet': function(greeting) { + return this.user + ' says: ' + greeting; + } + }; + + var bound = bindKey(object, 'greet', 'hi'); + assert.strictEqual(bound(), 'fred says: hi'); + + object.greet = function(greeting) { + return this.user + ' says: ' + greeting + '!'; + }; + + assert.strictEqual(bound(), 'fred says: hi!'); + }); + + it('should support placeholders', function() { + var object = { + 'fn': function() { + return slice.call(arguments); + } + }; + + var ph = bindKey.placeholder, + bound = bindKey(object, 'fn', ph, 'b', ph); + + assert.deepStrictEqual(bound('a', 'c'), ['a', 'b', 'c']); + assert.deepStrictEqual(bound('a'), ['a', 'b', undefined]); + assert.deepStrictEqual(bound('a', 'c', 'd'), ['a', 'b', 'c', 'd']); + assert.deepStrictEqual(bound(), [undefined, 'b', undefined]); + }); + + it('should use `_.placeholder` when set', function() { + var object = { + 'fn': function() { + return slice.call(arguments); + } + }; + + var _ph = _.placeholder = {}, + ph = bindKey.placeholder, + bound = bindKey(object, 'fn', _ph, 'b', ph); + + assert.deepEqual(bound('a', 'c'), ['a', 'b', ph, 'c']); + delete _.placeholder; + }); + + it('should ensure `new bound` is an instance of `object[key]`', function() { + function Foo(value) { + return value && object; + } + + var object = { 'Foo': Foo }, + bound = bindKey(object, 'Foo'); + + assert.ok(new bound instanceof Foo); + assert.strictEqual(new bound(true), object); + }); +}); diff --git a/test/camelCase.js b/test/camelCase.js new file mode 100644 index 000000000..89a36b02c --- /dev/null +++ b/test/camelCase.js @@ -0,0 +1,28 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import camelCase from '../camelCase.js'; + +describe('camelCase', function() { + it('should work with numbers', function() { + assert.strictEqual(camelCase('12 feet'), '12Feet'); + assert.strictEqual(camelCase('enable 6h format'), 'enable6HFormat'); + assert.strictEqual(camelCase('enable 24H format'), 'enable24HFormat'); + assert.strictEqual(camelCase('too legit 2 quit'), 'tooLegit2Quit'); + assert.strictEqual(camelCase('walk 500 miles'), 'walk500Miles'); + assert.strictEqual(camelCase('xhr2 request'), 'xhr2Request'); + }); + + it('should handle acronyms', function() { + lodashStable.each(['safe HTML', 'safeHTML'], function(string) { + assert.strictEqual(camelCase(string), 'safeHtml'); + }); + + lodashStable.each(['escape HTML entities', 'escapeHTMLEntities'], function(string) { + assert.strictEqual(camelCase(string), 'escapeHtmlEntities'); + }); + + lodashStable.each(['XMLHttpRequest', 'XmlHTTPRequest'], function(string) { + assert.strictEqual(camelCase(string), 'xmlHttpRequest'); + }); + }); +}); diff --git a/test/capitalize.test.js b/test/capitalize.test.js new file mode 100644 index 000000000..0aeb2be92 --- /dev/null +++ b/test/capitalize.test.js @@ -0,0 +1,10 @@ +import assert from 'assert'; +import capitalize from '../capitalize.js'; + +describe('capitalize', function() { + it('should capitalize the first character of a string', function() { + assert.strictEqual(capitalize('fred'), 'Fred'); + assert.strictEqual(capitalize('Fred'), 'Fred'); + assert.strictEqual(capitalize(' fred'), ' fred'); + }); +}); diff --git a/test/case-methods.js b/test/case-methods.js new file mode 100644 index 000000000..b490d97fe --- /dev/null +++ b/test/case-methods.js @@ -0,0 +1,119 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, stubTrue, burredLetters, deburredLetters } from './utils.js'; +import camelCase from '../camelCase.js'; +import kebabCase from '../kebabCase.js'; +import lowerCase from '../lowerCase.js'; +import snakeCase from '../snakeCase.js'; +import startCase from '../startCase.js'; + +describe('case methods', function() { + lodashStable.each(['camel', 'kebab', 'lower', 'snake', 'start', 'upper'], function(caseName) { + var methodName = caseName + 'Case', + func = _[methodName]; + + var strings = [ + 'foo bar', 'Foo bar', 'foo Bar', 'Foo Bar', + 'FOO BAR', 'fooBar', '--foo-bar--', '__foo_bar__' + ]; + + var converted = (function() { + switch (caseName) { + case 'camel': return 'fooBar'; + case 'kebab': return 'foo-bar'; + case 'lower': return 'foo bar'; + case 'snake': return 'foo_bar'; + case 'start': return 'Foo Bar'; + case 'upper': return 'FOO BAR'; + } + }()); + + it('`_.' + methodName + '` should convert `string` to ' + caseName + ' case', function() { + var actual = lodashStable.map(strings, function(string) { + var expected = (caseName == 'start' && string == 'FOO BAR') ? string : converted; + return func(string) === expected; + }); + + assert.deepStrictEqual(actual, lodashStable.map(strings, stubTrue)); + }); + + it('`_.' + methodName + '` should handle double-converting strings', function() { + var actual = lodashStable.map(strings, function(string) { + var expected = (caseName == 'start' && string == 'FOO BAR') ? string : converted; + return func(func(string)) === expected; + }); + + assert.deepStrictEqual(actual, lodashStable.map(strings, stubTrue)); + }); + + it('`_.' + methodName + '` should deburr letters', function() { + var actual = lodashStable.map(burredLetters, function(burred, index) { + var letter = deburredLetters[index].replace(/['\u2019]/g, ''); + if (caseName == 'start') { + letter = letter == 'IJ' ? letter : lodashStable.capitalize(letter); + } else if (caseName == 'upper') { + letter = letter.toUpperCase(); + } else { + letter = letter.toLowerCase(); + } + return func(burred) === letter; + }); + + assert.deepStrictEqual(actual, lodashStable.map(burredLetters, stubTrue)); + }); + + it('`_.' + methodName + '` should remove contraction apostrophes', function() { + var postfixes = ['d', 'll', 'm', 're', 's', 't', 've']; + + lodashStable.each(["'", '\u2019'], function(apos) { + var actual = lodashStable.map(postfixes, function(postfix) { + return func('a b' + apos + postfix + ' c'); + }); + + var expected = lodashStable.map(postfixes, function(postfix) { + switch (caseName) { + case 'camel': return 'aB' + postfix + 'C'; + case 'kebab': return 'a-b' + postfix + '-c'; + case 'lower': return 'a b' + postfix + ' c'; + case 'snake': return 'a_b' + postfix + '_c'; + case 'start': return 'A B' + postfix + ' C'; + case 'upper': return 'A B' + postfix.toUpperCase() + ' C'; + } + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('`_.' + methodName + '` should remove Latin mathematical operators', function() { + var actual = lodashStable.map(['\xd7', '\xf7'], func); + assert.deepStrictEqual(actual, ['', '']); + }); + + it('`_.' + methodName + '` should coerce `string` to a string', function() { + var string = 'foo bar'; + assert.strictEqual(func(Object(string)), converted); + assert.strictEqual(func({ 'toString': lodashStable.constant(string) }), converted); + }); + + it('`_.' + methodName + '` should return an unwrapped value implicitly when chaining', function() { + assert.strictEqual(_('foo bar')[methodName](), converted); + }); + + it('`_.' + methodName + '` should return a wrapped value when explicitly chaining', function() { + assert.ok(_('foo bar').chain()[methodName]() instanceof _); + }); + }); + + (function() { + it('should get the original value after cycling through all case methods', function() { + var funcs = [camelCase, kebabCase, lowerCase, snakeCase, startCase, lowerCase, camelCase]; + + var actual = lodashStable.reduce(funcs, function(result, func) { + return func(result); + }, 'enable 6h format'); + + assert.strictEqual(actual, 'enable6HFormat'); + }); + })(); +}); diff --git a/test/castArray.test.js b/test/castArray.test.js new file mode 100644 index 000000000..be874b5e0 --- /dev/null +++ b/test/castArray.test.js @@ -0,0 +1,23 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey } from './utils.js'; +import castArray from '../castArray.js'; + +describe('castArray', function() { + it('should wrap non-array items in an array', function() { + var values = falsey.concat(true, 1, 'a', { 'a': 1 }), + expected = lodashStable.map(values, function(value) { return [value]; }), + actual = lodashStable.map(values, castArray); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return array values by reference', function() { + var array = [1]; + assert.strictEqual(castArray(array), array); + }); + + it('should return an empty array when no arguments are given', function() { + assert.deepStrictEqual(castArray(), []); + }); +}); diff --git a/test/chain.js b/test/chain.js new file mode 100644 index 000000000..d02142071 --- /dev/null +++ b/test/chain.js @@ -0,0 +1,74 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { square } from './utils.js'; +import chain from '../chain.js'; + +describe('chain', function() { + it('should return a wrapped value', function() { + var actual = chain({ 'a': 0 }); + assert.ok(actual instanceof _); + }); + + it('should return existing wrapped values', function() { + var wrapped = _({ 'a': 0 }); + assert.strictEqual(chain(wrapped), wrapped); + assert.strictEqual(wrapped.chain(), wrapped); + }); + + it('should enable chaining for methods that return unwrapped values', function() { + var array = ['c', 'b', 'a']; + + assert.ok(chain(array).head() instanceof _); + assert.ok(_(array).chain().head() instanceof _); + + assert.ok(chain(array).isArray() instanceof _); + assert.ok(_(array).chain().isArray() instanceof _); + + assert.ok(chain(array).sortBy().head() instanceof _); + assert.ok(_(array).chain().sortBy().head() instanceof _); + }); + + it('should chain multiple methods', function() { + lodashStable.times(2, function(index) { + var array = ['one two three four', 'five six seven eight', 'nine ten eleven twelve'], + expected = { ' ': 9, 'e': 14, 'f': 2, 'g': 1, 'h': 2, 'i': 4, 'l': 2, 'n': 6, 'o': 3, 'r': 2, 's': 2, 't': 5, 'u': 1, 'v': 4, 'w': 2, 'x': 1 }, + wrapped = index ? _(array).chain() : chain(array); + + var actual = wrapped + .chain() + .map(function(value) { return value.split(''); }) + .flatten() + .reduce(function(object, chr) { + object[chr] || (object[chr] = 0); + object[chr]++; + return object; + }, {}) + .value(); + + assert.deepStrictEqual(actual, expected); + + array = [1, 2, 3, 4, 5, 6]; + wrapped = index ? _(array).chain() : chain(array); + actual = wrapped + .chain() + .filter(function(n) { return n % 2 != 0; }) + .reject(function(n) { return n % 3 == 0; }) + .sortBy(function(n) { return -n; }) + .value(); + + assert.deepStrictEqual(actual, [5, 1]); + + array = [3, 4]; + wrapped = index ? _(array).chain() : chain(array); + actual = wrapped + .reverse() + .concat([2, 1]) + .unshift(5) + .tap(function(value) { value.pop(); }) + .map(square) + .value(); + + assert.deepStrictEqual(actual, [25, 16, 9, 4]); + }); + }); +}); diff --git a/test/chunk.js b/test/chunk.js new file mode 100644 index 000000000..74121e10d --- /dev/null +++ b/test/chunk.js @@ -0,0 +1,50 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubArray } from './utils.js'; +import chunk from '../chunk.js'; + +describe('chunk', function() { + var array = [0, 1, 2, 3, 4, 5]; + + it('should return chunked arrays', function() { + var actual = chunk(array, 3); + assert.deepStrictEqual(actual, [[0, 1, 2], [3, 4, 5]]); + }); + + it('should return the last chunk as remaining elements', function() { + var actual = chunk(array, 4); + assert.deepStrictEqual(actual, [[0, 1, 2, 3], [4, 5]]); + }); + + it('should treat falsey `size` values, except `undefined`, as `0`', function() { + var expected = lodashStable.map(falsey, function(value) { + return value === undefined ? [[0], [1], [2], [3], [4], [5]] : []; + }); + + var actual = lodashStable.map(falsey, function(size, index) { + return index ? chunk(array, size) : chunk(array); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should ensure the minimum `size` is `0`', function() { + var values = lodashStable.reject(falsey, lodashStable.isUndefined).concat(-1, -Infinity), + expected = lodashStable.map(values, stubArray); + + var actual = lodashStable.map(values, function(n) { + return chunk(array, n); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should coerce `size` to an integer', function() { + assert.deepStrictEqual(chunk(array, array.length / 4), [[0], [1], [2], [3], [4], [5]]); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var actual = lodashStable.map([[1, 2], [3, 4]], chunk); + assert.deepStrictEqual(actual, [[[1], [2]], [[3], [4]]]); + }); +}); diff --git a/test/clamp.js b/test/clamp.js new file mode 100644 index 000000000..911f57e9d --- /dev/null +++ b/test/clamp.js @@ -0,0 +1,58 @@ +import assert from 'assert'; +import clamp from '../clamp.js'; + +describe('clamp', function() { + it('should work with a `max`', function() { + assert.strictEqual(clamp(5, 3), 3); + assert.strictEqual(clamp(1, 3), 1); + }); + + it('should clamp negative numbers', function() { + assert.strictEqual(clamp(-10, -5, 5), -5); + assert.strictEqual(clamp(-10.2, -5.5, 5.5), -5.5); + assert.strictEqual(clamp(-Infinity, -5, 5), -5); + }); + + it('should clamp positive numbers', function() { + assert.strictEqual(clamp(10, -5, 5), 5); + assert.strictEqual(clamp(10.6, -5.6, 5.4), 5.4); + assert.strictEqual(clamp(Infinity, -5, 5), 5); + }); + + it('should not alter negative numbers in range', function() { + assert.strictEqual(clamp(-4, -5, 5), -4); + assert.strictEqual(clamp(-5, -5, 5), -5); + assert.strictEqual(clamp(-5.5, -5.6, 5.6), -5.5); + }); + + it('should not alter positive numbers in range', function() { + assert.strictEqual(clamp(4, -5, 5), 4); + assert.strictEqual(clamp(5, -5, 5), 5); + assert.strictEqual(clamp(4.5, -5.1, 5.2), 4.5); + }); + + it('should not alter `0` in range', function() { + assert.strictEqual(1 / clamp(0, -5, 5), Infinity); + }); + + it('should clamp to `0`', function() { + assert.strictEqual(1 / clamp(-10, 0, 5), Infinity); + }); + + it('should not alter `-0` in range', function() { + assert.strictEqual(1 / clamp(-0, -5, 5), -Infinity); + }); + + it('should clamp to `-0`', function() { + assert.strictEqual(1 / clamp(-10, -0, 5), -Infinity); + }); + + it('should return `NaN` when `number` is `NaN`', function() { + assert.deepStrictEqual(clamp(NaN, -5, 5), NaN); + }); + + it('should coerce `min` and `max` of `NaN` to `0`', function() { + assert.deepStrictEqual(clamp(1, -5, NaN), 0); + assert.deepStrictEqual(clamp(-1, NaN, 5), 0); + }); +}); diff --git a/test/clone-methods.js b/test/clone-methods.js new file mode 100644 index 000000000..b3f086d4d --- /dev/null +++ b/test/clone-methods.js @@ -0,0 +1,429 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; + +import { + map, + set, + realm, + body, + asyncFunc, + genFunc, + errors, + _, + LARGE_ARRAY_SIZE, + isNpm, + mapCaches, + arrayBuffer, + stubTrue, + objectProto, + symbol, + defineProperty, + getSymbols, + document, + arrayViews, + slice, + noop, +} from './utils.js'; + +import cloneDeep from '../cloneDeep.js'; +import cloneDeepWith from '../cloneDeepWith.js'; +import last from '../last.js'; + +describe('clone methods', function() { + function Foo() { + this.a = 1; + } + Foo.prototype.b = 1; + Foo.c = function() {}; + + if (Map) { + var map = new Map; + map.set('a', 1); + map.set('b', 2); + } + if (Set) { + var set = new Set; + set.add(1); + set.add(2); + } + var objects = { + '`arguments` objects': arguments, + 'arrays': ['a', ''], + 'array-like objects': { '0': 'a', 'length': 1 }, + 'booleans': false, + 'boolean objects': Object(false), + 'date objects': new Date, + 'Foo instances': new Foo, + 'objects': { 'a': 0, 'b': 1, 'c': 2 }, + 'objects with object values': { 'a': /a/, 'b': ['B'], 'c': { 'C': 1 } }, + 'objects from another document': realm.object || {}, + 'maps': map, + 'null values': null, + 'numbers': 0, + 'number objects': Object(0), + 'regexes': /a/gim, + 'sets': set, + 'strings': 'a', + 'string objects': Object('a'), + 'undefined values': undefined + }; + + objects.arrays.length = 3; + + var uncloneable = { + 'DOM elements': body, + 'functions': Foo, + 'async functions': asyncFunc, + 'generator functions': genFunc, + 'the `Proxy` constructor': Proxy + }; + + lodashStable.each(errors, function(error) { + uncloneable[error.name + 's'] = error; + }); + + it('`_.clone` should perform a shallow clone', function() { + var array = [{ 'a': 0 }, { 'b': 1 }], + actual = _.clone(array); + + assert.deepStrictEqual(actual, array); + assert.ok(actual !== array && actual[0] === array[0]); + }); + + it('`_.cloneDeep` should deep clone objects with circular references', function() { + var object = { + 'foo': { 'b': { 'c': { 'd': {} } } }, + 'bar': {} + }; + + object.foo.b.c.d = object; + object.bar.b = object.foo.b; + + var actual = cloneDeep(object); + assert.ok(actual.bar.b === actual.foo.b && actual === actual.foo.b.c.d && actual !== object); + }); + + it('`_.cloneDeep` should deep clone objects with lots of circular references', function() { + var cyclical = {}; + lodashStable.times(LARGE_ARRAY_SIZE + 1, function(index) { + cyclical['v' + index] = [index ? cyclical['v' + (index - 1)] : cyclical]; + }); + + var clone = cloneDeep(cyclical), + actual = clone['v' + LARGE_ARRAY_SIZE][0]; + + assert.strictEqual(actual, clone['v' + (LARGE_ARRAY_SIZE - 1)]); + assert.notStrictEqual(actual, cyclical['v' + (LARGE_ARRAY_SIZE - 1)]); + }); + + it('`_.cloneDeepWith` should provide `stack` to `customizer`', function() { + var actual; + + cloneDeepWith({ 'a': 1 }, function() { + actual = last(arguments); + }); + + assert.ok(isNpm + ? actual.constructor.name == 'Stack' + : actual instanceof mapCaches.Stack + ); + }); + + lodashStable.each(['clone', 'cloneDeep'], function(methodName) { + var func = _[methodName], + isDeep = methodName == 'cloneDeep'; + + lodashStable.forOwn(objects, function(object, kind) { + it('`_.' + methodName + '` should clone ' + kind, function() { + var actual = func(object); + assert.ok(lodashStable.isEqual(actual, object)); + + if (lodashStable.isObject(object)) { + assert.notStrictEqual(actual, object); + } else { + assert.strictEqual(actual, object); + } + }); + }); + + it('`_.' + methodName + '` should clone array buffers', function() { + if (ArrayBuffer) { + var actual = func(arrayBuffer); + assert.strictEqual(actual.byteLength, arrayBuffer.byteLength); + assert.notStrictEqual(actual, arrayBuffer); + } + }); + + it('`_.' + methodName + '` should clone buffers', function() { + if (Buffer) { + var buffer = new Buffer([1, 2]), + actual = func(buffer); + + assert.strictEqual(actual.byteLength, buffer.byteLength); + assert.strictEqual(actual.inspect(), buffer.inspect()); + assert.notStrictEqual(actual, buffer); + + buffer[0] = 2; + assert.strictEqual(actual[0], isDeep ? 2 : 1); + } + }); + + it('`_.' + methodName + '` should clone `index` and `input` array properties', function() { + var array = /c/.exec('abcde'), + actual = func(array); + + assert.strictEqual(actual.index, 2); + assert.strictEqual(actual.input, 'abcde'); + }); + + it('`_.' + methodName + '` should clone `lastIndex` regexp property', function() { + var regexp = /c/g; + regexp.exec('abcde'); + + assert.strictEqual(func(regexp).lastIndex, 3); + }); + + it('`_.' + methodName + '` should clone expando properties', function() { + var values = lodashStable.map([false, true, 1, 'a'], function(value) { + var object = Object(value); + object.a = 1; + return object; + }); + + var expected = lodashStable.map(values, stubTrue); + + var actual = lodashStable.map(values, function(value) { + return func(value).a === 1; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should clone prototype objects', function() { + var actual = func(Foo.prototype); + + assert.ok(!(actual instanceof Foo)); + assert.deepStrictEqual(actual, { 'b': 1 }); + }); + + it('`_.' + methodName + '` should set the `[[Prototype]]` of a clone', function() { + assert.ok(func(new Foo) instanceof Foo); + }); + + it('`_.' + methodName + '` should set the `[[Prototype]]` of a clone even when the `constructor` is incorrect', function() { + Foo.prototype.constructor = Object; + assert.ok(func(new Foo) instanceof Foo); + Foo.prototype.constructor = Foo; + }); + + it('`_.' + methodName + '` should ensure `value` constructor is a function before using its `[[Prototype]]`', function() { + Foo.prototype.constructor = null; + assert.ok(!(func(new Foo) instanceof Foo)); + Foo.prototype.constructor = Foo; + }); + + it('`_.' + methodName + '` should clone properties that shadow those on `Object.prototype`', function() { + var object = { + 'constructor': objectProto.constructor, + 'hasOwnProperty': objectProto.hasOwnProperty, + 'isPrototypeOf': objectProto.isPrototypeOf, + 'propertyIsEnumerable': objectProto.propertyIsEnumerable, + 'toLocaleString': objectProto.toLocaleString, + 'toString': objectProto.toString, + 'valueOf': objectProto.valueOf + }; + + var actual = func(object); + + assert.deepStrictEqual(actual, object); + assert.notStrictEqual(actual, object); + }); + + it('`_.' + methodName + '` should clone symbol properties', function() { + function Foo() { + this[symbol] = { 'c': 1 }; + } + + if (Symbol) { + var symbol2 = Symbol('b'); + Foo.prototype[symbol2] = 2; + + var symbol3 = Symbol('c'); + defineProperty(Foo.prototype, symbol3, { + 'configurable': true, + 'enumerable': false, + 'writable': true, + 'value': 3 + }); + + var object = { 'a': { 'b': new Foo } }; + object[symbol] = { 'b': 1 }; + + var actual = func(object); + if (isDeep) { + assert.notStrictEqual(actual[symbol], object[symbol]); + assert.notStrictEqual(actual.a, object.a); + } else { + assert.strictEqual(actual[symbol], object[symbol]); + assert.strictEqual(actual.a, object.a); + } + assert.deepStrictEqual(actual[symbol], object[symbol]); + assert.deepStrictEqual(getSymbols(actual.a.b), [symbol]); + assert.deepStrictEqual(actual.a.b[symbol], object.a.b[symbol]); + assert.deepStrictEqual(actual.a.b[symbol2], object.a.b[symbol2]); + assert.deepStrictEqual(actual.a.b[symbol3], object.a.b[symbol3]); + } + }); + + it('`_.' + methodName + '` should clone symbol objects', function() { + if (Symbol) { + assert.strictEqual(func(symbol), symbol); + + var object = Object(symbol), + actual = func(object); + + assert.strictEqual(typeof actual, 'object'); + assert.strictEqual(typeof actual.valueOf(), 'symbol'); + assert.notStrictEqual(actual, object); + } + }); + + it('`_.' + methodName + '` should not clone symbol primitives', function() { + if (Symbol) { + assert.strictEqual(func(symbol), symbol); + } + }); + + it('`_.' + methodName + '` should not error on DOM elements', function() { + if (document) { + var element = document.createElement('div'); + + try { + assert.deepStrictEqual(func(element), {}); + } catch (e) { + assert.ok(false, e.message); + } + } + }); + + it('`_.' + methodName + '` should create an object from the same realm as `value`', function() { + var props = []; + + var objects = lodashStable.transform(_, function(result, value, key) { + if (lodashStable.startsWith(key, '_') && lodashStable.isObject(value) && + !lodashStable.isArguments(value) && !lodashStable.isElement(value) && + !lodashStable.isFunction(value)) { + props.push(lodashStable.capitalize(lodashStable.camelCase(key))); + result.push(value); + } + }, []); + + var expected = lodashStable.map(objects, stubTrue); + + var actual = lodashStable.map(objects, function(object) { + var Ctor = object.constructor, + result = func(object); + + return result !== object && ((result instanceof Ctor) || !(new Ctor instanceof Ctor)); + }); + + assert.deepStrictEqual(actual, expected, props.join(', ')); + }); + + it('`_.' + methodName + '` should perform a ' + (isDeep ? 'deep' : 'shallow') + ' clone when used as an iteratee for methods like `_.map`', function() { + var expected = [{ 'a': [0] }, { 'b': [1] }], + actual = lodashStable.map(expected, func); + + assert.deepStrictEqual(actual, expected); + + if (isDeep) { + assert.ok(actual[0] !== expected[0] && actual[0].a !== expected[0].a && actual[1].b !== expected[1].b); + } else { + assert.ok(actual[0] !== expected[0] && actual[0].a === expected[0].a && actual[1].b === expected[1].b); + } + }); + + it('`_.' + methodName + '` should return a unwrapped value when chaining', function() { + var object = objects.objects, + actual = _(object)[methodName](); + + assert.deepEqual(actual, object); + assert.notStrictEqual(actual, object); + }); + + lodashStable.each(arrayViews, function(type) { + it('`_.' + methodName + '` should clone ' + type + ' values', function() { + var Ctor = root[type]; + + lodashStable.times(2, function(index) { + if (Ctor) { + var buffer = new ArrayBuffer(24), + view = index ? new Ctor(buffer, 8, 1) : new Ctor(buffer), + actual = func(view); + + assert.deepStrictEqual(actual, view); + assert.notStrictEqual(actual, view); + assert.strictEqual(actual.buffer === view.buffer, !isDeep); + assert.strictEqual(actual.byteOffset, view.byteOffset); + assert.strictEqual(actual.length, view.length); + } + }); + }); + }); + + lodashStable.forOwn(uncloneable, function(value, key) { + it('`_.' + methodName + '` should not clone ' + key, function() { + if (value) { + var object = { 'a': value, 'b': { 'c': value } }, + actual = func(object), + expected = value === Foo ? { 'c': Foo.c } : {}; + + assert.deepStrictEqual(actual, object); + assert.notStrictEqual(actual, object); + assert.deepStrictEqual(func(value), expected); + } + }); + }); + }); + + lodashStable.each(['cloneWith', 'cloneDeepWith'], function(methodName) { + var func = _[methodName], + isDeep = methodName == 'cloneDeepWith'; + + it('`_.' + methodName + '` should provide correct `customizer` arguments', function() { + var argsList = [], + object = new Foo; + + func(object, function() { + var length = arguments.length, + args = slice.call(arguments, 0, length - (length > 1 ? 1 : 0)); + + argsList.push(args); + }); + + assert.deepStrictEqual(argsList, isDeep ? [[object], [1, 'a', object]] : [[object]]); + }); + + it('`_.' + methodName + '` should handle cloning when `customizer` returns `undefined`', function() { + var actual = func({ 'a': { 'b': 'c' } }, noop); + assert.deepStrictEqual(actual, { 'a': { 'b': 'c' } }); + }); + + lodashStable.forOwn(uncloneable, function(value, key) { + it('`_.' + methodName + '` should work with a `customizer` callback and ' + key, function() { + var customizer = function(value) { + return lodashStable.isPlainObject(value) ? undefined : value; + }; + + var actual = func(value, customizer); + assert.strictEqual(actual, value); + + var object = { 'a': value, 'b': { 'c': value } }; + actual = func(object, customizer); + + assert.deepStrictEqual(actual, object); + assert.notStrictEqual(actual, object); + }); + }); + }); +}); diff --git a/test/compact.js b/test/compact.js new file mode 100644 index 000000000..2c3935863 --- /dev/null +++ b/test/compact.js @@ -0,0 +1,42 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { LARGE_ARRAY_SIZE, _, falsey } from './utils.js'; +import compact from '../compact.js'; +import slice from '../slice.js'; + +describe('compact', function() { + var largeArray = lodashStable.range(LARGE_ARRAY_SIZE).concat(null); + + it('should filter falsey values', function() { + var array = ['0', '1', '2']; + assert.deepStrictEqual(compact(falsey.concat(array)), array); + }); + + it('should work when in-between lazy operators', function() { + var actual = _(falsey).thru(slice).compact().thru(slice).value(); + assert.deepEqual(actual, []); + + actual = _(falsey).thru(slice).push(true, 1).compact().push('a').value(); + assert.deepEqual(actual, [true, 1, 'a']); + }); + + it('should work in a lazy sequence', function() { + var actual = _(largeArray).slice(1).compact().reverse().take().value(); + assert.deepEqual(actual, _.take(compact(slice(largeArray, 1)).reverse())); + }); + + it('should work in a lazy sequence with a custom `_.iteratee`', function() { + var iteratee = _.iteratee, + pass = false; + + _.iteratee = identity; + + try { + var actual = _(largeArray).slice(1).compact().value(); + pass = lodashStable.isEqual(actual, compact(slice(largeArray, 1))); + } catch (e) {console.log(e);} + + assert.ok(pass); + _.iteratee = iteratee; + }); +}); diff --git a/test/concat.js b/test/concat.js new file mode 100644 index 000000000..d66561982 --- /dev/null +++ b/test/concat.js @@ -0,0 +1,65 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import concat from '../concat.js'; + +describe('concat', function() { + it('should shallow clone `array`', function() { + var array = [1, 2, 3], + actual = concat(array); + + assert.deepStrictEqual(actual, array); + assert.notStrictEqual(actual, array); + }); + + it('should concat arrays and values', function() { + var array = [1], + actual = concat(array, 2, [3], [[4]]); + + assert.deepStrictEqual(actual, [1, 2, 3, [4]]); + assert.deepStrictEqual(array, [1]); + }); + + it('should cast non-array `array` values to arrays', function() { + var values = [, null, undefined, false, true, 1, NaN, 'a']; + + var expected = lodashStable.map(values, function(value, index) { + return index ? [value] : []; + }); + + var actual = lodashStable.map(values, function(value, index) { + return index ? concat(value) : concat(); + }); + + assert.deepStrictEqual(actual, expected); + + expected = lodashStable.map(values, function(value) { + return [value, 2, [3]]; + }); + + actual = lodashStable.map(values, function(value) { + return concat(value, [2], [[3]]); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should treat sparse arrays as dense', function() { + var expected = [], + actual = concat(Array(1), Array(1)); + + expected.push(undefined, undefined); + + assert.ok('0'in actual); + assert.ok('1' in actual); + assert.deepStrictEqual(actual, expected); + }); + + it('should return a new wrapped array', function() { + var array = [1], + wrapped = _(array).concat([2, 3]), + actual = wrapped.value(); + + assert.deepEqual(array, [1]); + assert.deepEqual(actual, [1, 2, 3]); + }); +}); diff --git a/test/cond.js b/test/cond.js new file mode 100644 index 000000000..e3594ab46 --- /dev/null +++ b/test/cond.js @@ -0,0 +1,65 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, stubA, stubB, stubC, slice, stubFalse, stubTrue } from './utils.js'; + +describe('cond', function() { + it('should create a conditional function', function() { + var cond = _.cond([ + [lodashStable.matches({ 'a': 1 }), stubA], + [lodashStable.matchesProperty('b', 1), stubB], + [lodashStable.property('c'), stubC] + ]); + + assert.strictEqual(cond({ 'a': 1, 'b': 2, 'c': 3 }), 'a'); + assert.strictEqual(cond({ 'a': 0, 'b': 1, 'c': 2 }), 'b'); + assert.strictEqual(cond({ 'a': -1, 'b': 0, 'c': 1 }), 'c'); + }); + + it('should provide arguments to functions', function() { + var args1, + args2, + expected = ['a', 'b', 'c']; + + var cond = _.cond([[ + function() { args1 || (args1 = slice.call(arguments)); return true; }, + function() { args2 || (args2 = slice.call(arguments)); } + ]]); + + cond('a', 'b', 'c'); + + assert.deepStrictEqual(args1, expected); + assert.deepStrictEqual(args2, expected); + }); + + it('should work with predicate shorthands', function() { + var cond = _.cond([ + [{ 'a': 1 }, stubA], + [['b', 1], stubB], + ['c', stubC] + ]); + + assert.strictEqual(cond({ 'a': 1, 'b': 2, 'c': 3 }), 'a'); + assert.strictEqual(cond({ 'a': 0, 'b': 1, 'c': 2 }), 'b'); + assert.strictEqual(cond({ 'a': -1, 'b': 0, 'c': 1 }), 'c'); + }); + + it('should return `undefined` when no condition is met', function() { + var cond = _.cond([[stubFalse, stubA]]); + assert.strictEqual(cond({ 'a': 1 }), undefined); + }); + + it('should throw a TypeError if `pairs` is not composed of functions', function() { + lodashStable.each([false, true], function(value) { + assert.throws(function() { _.cond([[stubTrue, value]])(); }, TypeError); + }); + }); + + it('should use `this` binding of function for `pairs`', function() { + var cond = _.cond([ + [function(a) { return this[a]; }, function(a, b) { return this[b]; }] + ]); + + var object = { 'cond': cond, 'a': 1, 'b': 2 }; + assert.strictEqual(object.cond('a', 'b'), 2); + }); +}); diff --git a/test/conforms-methods.js b/test/conforms-methods.js new file mode 100644 index 000000000..c28e8f8b5 --- /dev/null +++ b/test/conforms-methods.js @@ -0,0 +1,153 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, stubFalse, stubTrue, empties } from './utils.js'; +import conformsTo from '../conformsTo.js'; + +describe('conforms methods', function() { + lodashStable.each(['conforms', 'conformsTo'], function(methodName) { + var isConforms = methodName == 'conforms'; + + function conforms(source) { + return isConforms ? _.conforms(source) : function(object) { + return conformsTo(object, source); + }; + } + + it('`_.' + methodName + '` should check if `object` conforms to `source`', function() { + var objects = [ + { 'a': 1, 'b': 8 }, + { 'a': 2, 'b': 4 }, + { 'a': 3, 'b': 16 } + ]; + + var par = conforms({ + 'b': function(value) { return value > 4; } + }); + + var actual = lodashStable.filter(objects, par); + assert.deepStrictEqual(actual, [objects[0], objects[2]]); + + par = conforms({ + 'b': function(value) { return value > 8; }, + 'a': function(value) { return value > 1; } + }); + + actual = lodashStable.filter(objects, par); + assert.deepStrictEqual(actual, [objects[2]]); + }); + + it('`_.' + methodName + '` should not match by inherited `source` properties', function() { + function Foo() { + this.a = function(value) { + return value > 1; + }; + } + Foo.prototype.b = function(value) { + return value > 8; + }; + + var objects = [ + { 'a': 1, 'b': 8 }, + { 'a': 2, 'b': 4 }, + { 'a': 3, 'b': 16 } + ]; + + var par = conforms(new Foo), + actual = lodashStable.filter(objects, par); + + assert.deepStrictEqual(actual, [objects[1], objects[2]]); + }); + + it('`_.' + methodName + '` should not invoke `source` predicates for missing `object` properties', function() { + var count = 0; + + var par = conforms({ + 'a': function() { count++; return true; } + }); + + assert.strictEqual(par({}), false); + assert.strictEqual(count, 0); + }); + + it('`_.' + methodName + '` should work with a function for `object`', function() { + function Foo() {} + Foo.a = 1; + + function Bar() {} + Bar.a = 2; + + var par = conforms({ + 'a': function(value) { return value > 1; } + }); + + assert.strictEqual(par(Foo), false); + assert.strictEqual(par(Bar), true); + }); + + it('`_.' + methodName + '` should work with a function for `source`', function() { + function Foo() {} + Foo.a = function(value) { return value > 1; }; + + var objects = [{ 'a': 1 }, { 'a': 2 }], + actual = lodashStable.filter(objects, conforms(Foo)); + + assert.deepStrictEqual(actual, [objects[1]]); + }); + + it('`_.' + methodName + '` should work with a non-plain `object`', function() { + function Foo() { + this.a = 1; + } + Foo.prototype.b = 2; + + var par = conforms({ + 'b': function(value) { return value > 1; } + }); + + assert.strictEqual(par(new Foo), true); + }); + + it('`_.' + methodName + '` should return `false` when `object` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, stubFalse); + + var par = conforms({ + 'a': function(value) { return value > 1; } + }); + + var actual = lodashStable.map(values, function(value, index) { + try { + return index ? par(value) : par(); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should return `true` when comparing an empty `source` to a nullish `object`', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, stubTrue), + par = conforms({}); + + var actual = lodashStable.map(values, function(value, index) { + try { + return index ? par(value) : par(); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should return `true` when comparing an empty `source`', function() { + var object = { 'a': 1 }, + expected = lodashStable.map(empties, stubTrue); + + var actual = lodashStable.map(empties, function(value) { + var par = conforms(value); + return par(object); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); +}); diff --git a/test/conforms.js b/test/conforms.js new file mode 100644 index 000000000..204694a37 --- /dev/null +++ b/test/conforms.js @@ -0,0 +1,15 @@ +import assert from 'assert'; +import conforms from '../conforms.js'; + +describe('conforms', function() { + it('should not change behavior if `source` is modified', function() { + var object = { 'a': 2 }, + source = { 'a': function(value) { return value > 1; } }, + par = conforms(source); + + assert.strictEqual(par(object), true); + + source.a = function(value) { return value < 2; }; + assert.strictEqual(par(object), true); + }); +}); diff --git a/test/constant.js b/test/constant.js new file mode 100644 index 000000000..00151ad67 --- /dev/null +++ b/test/constant.js @@ -0,0 +1,40 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { empties, _, falsey, stubTrue } from './utils.js'; + +describe('constant', function() { + it('should create a function that returns `value`', function() { + var object = { 'a': 1 }, + values = Array(2).concat(empties, true, 1, 'a'), + constant = _.constant(object); + + var results = lodashStable.map(values, function(value, index) { + if (index < 2) { + return index ? constant.call({}) : constant(); + } + return constant(value); + }); + + assert.ok(lodashStable.every(results, function(result) { + return result === object; + })); + }); + + it('should work with falsey values', function() { + var expected = lodashStable.map(falsey, stubTrue); + + var actual = lodashStable.map(falsey, function(value, index) { + var constant = index ? _.constant(value) : _.constant(), + result = constant(); + + return (result === value) || (result !== result && value !== value); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return a wrapped value when chaining', function() { + var wrapped = _(true).constant(); + assert.ok(wrapped instanceof _); + }); +}); diff --git a/test/countBy.js b/test/countBy.js new file mode 100644 index 000000000..b3e7a2779 --- /dev/null +++ b/test/countBy.js @@ -0,0 +1,66 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { LARGE_ARRAY_SIZE } from './utils.js'; +import countBy from '../countBy.js'; + +describe('countBy', function() { + var array = [6.1, 4.2, 6.3]; + + it('should transform keys by `iteratee`', function() { + var actual = countBy(array, Math.floor); + assert.deepStrictEqual(actual, { '4': 1, '6': 2 }); + }); + + it('should use `_.identity` when `iteratee` is nullish', function() { + var array = [4, 6, 6], + values = [, null, undefined], + expected = lodashStable.map(values, lodashStable.constant({ '4': 1, '6': 2 })); + + var actual = lodashStable.map(values, function(value, index) { + return index ? countBy(array, value) : countBy(array); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with `_.property` shorthands', function() { + var actual = countBy(['one', 'two', 'three'], 'length'); + assert.deepStrictEqual(actual, { '3': 2, '5': 1 }); + }); + + it('should only add values to own, not inherited, properties', function() { + var actual = countBy(array, function(n) { + return Math.floor(n) > 4 ? 'hasOwnProperty' : 'constructor'; + }); + + assert.deepStrictEqual(actual.constructor, 1); + assert.deepStrictEqual(actual.hasOwnProperty, 2); + }); + + it('should work with a number for `iteratee`', function() { + var array = [ + [1, 'a'], + [2, 'a'], + [2, 'b'] + ]; + + assert.deepStrictEqual(countBy(array, 0), { '1': 1, '2': 2 }); + assert.deepStrictEqual(countBy(array, 1), { 'a': 2, 'b': 1 }); + }); + + it('should work with an object for `collection`', function() { + var actual = countBy({ 'a': 6.1, 'b': 4.2, 'c': 6.3 }, Math.floor); + assert.deepStrictEqual(actual, { '4': 1, '6': 2 }); + }); + + it('should work in a lazy sequence', function() { + var array = lodashStable.range(LARGE_ARRAY_SIZE).concat( + lodashStable.range(Math.floor(LARGE_ARRAY_SIZE / 2), LARGE_ARRAY_SIZE), + lodashStable.range(Math.floor(LARGE_ARRAY_SIZE / 1.5), LARGE_ARRAY_SIZE) + ); + + var actual = _(array).countBy().map(square).filter(isEven).take().value(); + + assert.deepEqual(actual, _.take(_.filter(_.map(countBy(array), square), isEven))); + }); +}); diff --git a/test/create.js b/test/create.js new file mode 100644 index 000000000..4f287393e --- /dev/null +++ b/test/create.js @@ -0,0 +1,88 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubObject, primitives, stubTrue } from './utils.js'; +import create from '../create.js'; +import keys from '../keys.js'; + +describe('create', function() { + function Shape() { + this.x = 0; + this.y = 0; + } + + function Circle() { + Shape.call(this); + } + + it('should create an object that inherits from the given `prototype` object', function() { + Circle.prototype = create(Shape.prototype); + Circle.prototype.constructor = Circle; + + var actual = new Circle; + + assert.ok(actual instanceof Circle); + assert.ok(actual instanceof Shape); + assert.notStrictEqual(Circle.prototype, Shape.prototype); + }); + + it('should assign `properties` to the created object', function() { + var expected = { 'constructor': Circle, 'radius': 0 }; + Circle.prototype = create(Shape.prototype, expected); + + var actual = new Circle; + + assert.ok(actual instanceof Circle); + assert.ok(actual instanceof Shape); + assert.deepStrictEqual(Circle.prototype, expected); + }); + + it('should assign own properties', function() { + function Foo() { + this.a = 1; + this.c = 3; + } + Foo.prototype.b = 2; + + assert.deepStrictEqual(create({}, new Foo), { 'a': 1, 'c': 3 }); + }); + + it('should assign properties that shadow those of `prototype`', function() { + function Foo() { + this.a = 1; + } + var object = create(new Foo, { 'a': 1 }); + assert.deepStrictEqual(lodashStable.keys(object), ['a']); + }); + + it('should accept a falsey `prototype`', function() { + var expected = lodashStable.map(falsey, stubObject); + + var actual = lodashStable.map(falsey, function(prototype, index) { + return index ? create(prototype) : create(); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should ignore a primitive `prototype` and use an empty object instead', function() { + var expected = lodashStable.map(primitives, stubTrue); + + var actual = lodashStable.map(primitives, function(value, index) { + return lodashStable.isPlainObject(index ? create(value) : create()); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var array = [{ 'a': 1 }, { 'a': 1 }, { 'a': 1 }], + expected = lodashStable.map(array, stubTrue), + objects = lodashStable.map(array, create); + + var actual = lodashStable.map(objects, function(object) { + return object.a === 1 && !keys(object).length; + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/curry-methods.js b/test/curry-methods.js new file mode 100644 index 000000000..e742e0315 --- /dev/null +++ b/test/curry-methods.js @@ -0,0 +1,52 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, slice } from './utils.js'; +import curry from '../curry.js'; + +describe('curry methods', function() { + lodashStable.each(['curry', 'curryRight'], function(methodName) { + var func = _[methodName], + fn = function(a, b) { return slice.call(arguments); }, + isCurry = methodName == 'curry'; + + it('`_.' + methodName + '` should not error on functions with the same name as lodash methods', function() { + function run(a, b) { + return a + b; + } + + var curried = func(run); + + try { + var actual = curried(1)(2); + } catch (e) {} + + assert.strictEqual(actual, 3); + }); + + it('`_.' + methodName + '` should work for function names that shadow those on `Object.prototype`', function() { + var curried = curry(function hasOwnProperty(a, b, c) { + return [a, b, c]; + }); + + var expected = [1, 2, 3]; + + assert.deepStrictEqual(curried(1)(2)(3), expected); + }); + + it('`_.' + methodName + '` should work as an iteratee for methods like `_.map`', function() { + var array = [fn, fn, fn], + object = { 'a': fn, 'b': fn, 'c': fn }; + + lodashStable.each([array, object], function(collection) { + var curries = lodashStable.map(collection, func), + expected = lodashStable.map(collection, lodashStable.constant(isCurry ? ['a', 'b'] : ['b', 'a'])); + + var actual = lodashStable.map(curries, function(curried) { + return curried('a')('b'); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + }); +}); diff --git a/test/curry.js b/test/curry.js new file mode 100644 index 000000000..5b8ab7364 --- /dev/null +++ b/test/curry.js @@ -0,0 +1,135 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice, stubArray } from './utils.js'; +import curry from '../curry.js'; +import placeholder from '../placeholder.js'; +import bind from '../bind.js'; +import partial from '../partial.js'; +import partialRight from '../partialRight.js'; + +describe('curry', function() { + function fn(a, b, c, d) { + return slice.call(arguments); + } + + it('should curry based on the number of arguments given', function() { + var curried = curry(fn), + expected = [1, 2, 3, 4]; + + assert.deepStrictEqual(curried(1)(2)(3)(4), expected); + assert.deepStrictEqual(curried(1, 2)(3, 4), expected); + assert.deepStrictEqual(curried(1, 2, 3, 4), expected); + }); + + it('should allow specifying `arity`', function() { + var curried = curry(fn, 3), + expected = [1, 2, 3]; + + assert.deepStrictEqual(curried(1)(2, 3), expected); + assert.deepStrictEqual(curried(1, 2)(3), expected); + assert.deepStrictEqual(curried(1, 2, 3), expected); + }); + + it('should coerce `arity` to an integer', function() { + var values = ['0', 0.6, 'xyz'], + expected = lodashStable.map(values, stubArray); + + var actual = lodashStable.map(values, function(arity) { + return curry(fn, arity)(); + }); + + assert.deepStrictEqual(actual, expected); + assert.deepStrictEqual(curry(fn, '2')(1)(2), [1, 2]); + }); + + it('should support placeholders', function() { + var curried = curry(fn), + ph = curried.placeholder; + + assert.deepStrictEqual(curried(1)(ph, 3)(ph, 4)(2), [1, 2, 3, 4]); + assert.deepStrictEqual(curried(ph, 2)(1)(ph, 4)(3), [1, 2, 3, 4]); + assert.deepStrictEqual(curried(ph, ph, 3)(ph, 2)(ph, 4)(1), [1, 2, 3, 4]); + assert.deepStrictEqual(curried(ph, ph, ph, 4)(ph, ph, 3)(ph, 2)(1), [1, 2, 3, 4]); + }); + + it('should persist placeholders', function() { + var curried = curry(fn), + ph = curried.placeholder, + actual = curried(ph, ph, ph, 'd')('a')(ph)('b')('c'); + + assert.deepStrictEqual(actual, ['a', 'b', 'c', 'd']); + }); + + it('should use `_.placeholder` when set', function() { + var curried = curry(fn), + _ph = placeholder = {}, + ph = curried.placeholder; + + assert.deepEqual(curried(1)(_ph, 3)(ph, 4), [1, ph, 3, 4]); + delete placeholder; + }); + + it('should provide additional arguments after reaching the target arity', function() { + var curried = curry(fn, 3); + assert.deepStrictEqual(curried(1)(2, 3, 4), [1, 2, 3, 4]); + assert.deepStrictEqual(curried(1, 2)(3, 4, 5), [1, 2, 3, 4, 5]); + assert.deepStrictEqual(curried(1, 2, 3, 4, 5, 6), [1, 2, 3, 4, 5, 6]); + }); + + it('should create a function with a `length` of `0`', function() { + lodashStable.times(2, function(index) { + var curried = index ? curry(fn, 4) : curry(fn); + assert.strictEqual(curried.length, 0); + assert.strictEqual(curried(1).length, 0); + assert.strictEqual(curried(1, 2).length, 0); + }); + }); + + it('should ensure `new curried` is an instance of `func`', function() { + function Foo(value) { + return value && object; + } + + var curried = curry(Foo), + object = {}; + + assert.ok(new curried(false) instanceof Foo); + assert.strictEqual(new curried(true), object); + }); + + it('should use `this` binding of function', function() { + var fn = function(a, b, c) { + var value = this || {}; + return [value[a], value[b], value[c]]; + }; + + var object = { 'a': 1, 'b': 2, 'c': 3 }, + expected = [1, 2, 3]; + + assert.deepStrictEqual(curry(bind(fn, object), 3)('a')('b')('c'), expected); + assert.deepStrictEqual(curry(bind(fn, object), 3)('a', 'b')('c'), expected); + assert.deepStrictEqual(curry(bind(fn, object), 3)('a', 'b', 'c'), expected); + + assert.deepStrictEqual(bind(curry(fn), object)('a')('b')('c'), Array(3)); + assert.deepStrictEqual(bind(curry(fn), object)('a', 'b')('c'), Array(3)); + assert.deepStrictEqual(bind(curry(fn), object)('a', 'b', 'c'), expected); + + object.curried = curry(fn); + assert.deepStrictEqual(object.curried('a')('b')('c'), Array(3)); + assert.deepStrictEqual(object.curried('a', 'b')('c'), Array(3)); + assert.deepStrictEqual(object.curried('a', 'b', 'c'), expected); + }); + + it('should work with partialed methods', function() { + var curried = curry(fn), + expected = [1, 2, 3, 4]; + + var a = partial(curried, 1), + b = bind(a, null, 2), + c = partialRight(b, 4), + d = partialRight(b(3), 4); + + assert.deepStrictEqual(c(3), expected); + assert.deepStrictEqual(d(), expected); + }); +}); diff --git a/test/curryRight.js b/test/curryRight.js new file mode 100644 index 000000000..21f6acdb7 --- /dev/null +++ b/test/curryRight.js @@ -0,0 +1,136 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice, stubArray } from './utils.js'; +import curryRight from '../curryRight.js'; +import placeholder from '../placeholder.js'; +import bind from '../bind.js'; +import partialRight from '../partialRight.js'; +import partial from '../partial.js'; + +describe('curryRight', function() { + function fn(a, b, c, d) { + return slice.call(arguments); + } + + it('should curry based on the number of arguments given', function() { + var curried = curryRight(fn), + expected = [1, 2, 3, 4]; + + assert.deepStrictEqual(curried(4)(3)(2)(1), expected); + assert.deepStrictEqual(curried(3, 4)(1, 2), expected); + assert.deepStrictEqual(curried(1, 2, 3, 4), expected); + }); + + it('should allow specifying `arity`', function() { + var curried = curryRight(fn, 3), + expected = [1, 2, 3]; + + assert.deepStrictEqual(curried(3)(1, 2), expected); + assert.deepStrictEqual(curried(2, 3)(1), expected); + assert.deepStrictEqual(curried(1, 2, 3), expected); + }); + + it('should coerce `arity` to an integer', function() { + var values = ['0', 0.6, 'xyz'], + expected = lodashStable.map(values, stubArray); + + var actual = lodashStable.map(values, function(arity) { + return curryRight(fn, arity)(); + }); + + assert.deepStrictEqual(actual, expected); + assert.deepStrictEqual(curryRight(fn, '2')(1)(2), [2, 1]); + }); + + it('should support placeholders', function() { + var curried = curryRight(fn), + expected = [1, 2, 3, 4], + ph = curried.placeholder; + + assert.deepStrictEqual(curried(4)(2, ph)(1, ph)(3), expected); + assert.deepStrictEqual(curried(3, ph)(4)(1, ph)(2), expected); + assert.deepStrictEqual(curried(ph, ph, 4)(ph, 3)(ph, 2)(1), expected); + assert.deepStrictEqual(curried(ph, ph, ph, 4)(ph, ph, 3)(ph, 2)(1), expected); + }); + + it('should persist placeholders', function() { + var curried = curryRight(fn), + ph = curried.placeholder, + actual = curried('a', ph, ph, ph)('b')(ph)('c')('d'); + + assert.deepStrictEqual(actual, ['a', 'b', 'c', 'd']); + }); + + it('should use `_.placeholder` when set', function() { + var curried = curryRight(fn), + _ph = placeholder = {}, + ph = curried.placeholder; + + assert.deepEqual(curried(4)(2, _ph)(1, ph), [1, 2, ph, 4]); + delete placeholder; + }); + + it('should provide additional arguments after reaching the target arity', function() { + var curried = curryRight(fn, 3); + assert.deepStrictEqual(curried(4)(1, 2, 3), [1, 2, 3, 4]); + assert.deepStrictEqual(curried(4, 5)(1, 2, 3), [1, 2, 3, 4, 5]); + assert.deepStrictEqual(curried(1, 2, 3, 4, 5, 6), [1, 2, 3, 4, 5, 6]); + }); + + it('should create a function with a `length` of `0`', function() { + lodashStable.times(2, function(index) { + var curried = index ? curryRight(fn, 4) : curryRight(fn); + assert.strictEqual(curried.length, 0); + assert.strictEqual(curried(4).length, 0); + assert.strictEqual(curried(3, 4).length, 0); + }); + }); + + it('should ensure `new curried` is an instance of `func`', function() { + function Foo(value) { + return value && object; + } + + var curried = curryRight(Foo), + object = {}; + + assert.ok(new curried(false) instanceof Foo); + assert.strictEqual(new curried(true), object); + }); + + it('should use `this` binding of function', function() { + var fn = function(a, b, c) { + var value = this || {}; + return [value[a], value[b], value[c]]; + }; + + var object = { 'a': 1, 'b': 2, 'c': 3 }, + expected = [1, 2, 3]; + + assert.deepStrictEqual(curryRight(bind(fn, object), 3)('c')('b')('a'), expected); + assert.deepStrictEqual(curryRight(bind(fn, object), 3)('b', 'c')('a'), expected); + assert.deepStrictEqual(curryRight(bind(fn, object), 3)('a', 'b', 'c'), expected); + + assert.deepStrictEqual(bind(curryRight(fn), object)('c')('b')('a'), Array(3)); + assert.deepStrictEqual(bind(curryRight(fn), object)('b', 'c')('a'), Array(3)); + assert.deepStrictEqual(bind(curryRight(fn), object)('a', 'b', 'c'), expected); + + object.curried = curryRight(fn); + assert.deepStrictEqual(object.curried('c')('b')('a'), Array(3)); + assert.deepStrictEqual(object.curried('b', 'c')('a'), Array(3)); + assert.deepStrictEqual(object.curried('a', 'b', 'c'), expected); + }); + + it('should work with partialed methods', function() { + var curried = curryRight(fn), + expected = [1, 2, 3, 4]; + + var a = partialRight(curried, 4), + b = partialRight(a, 3), + c = bind(b, null, 1), + d = partial(b(2), 1); + + assert.deepStrictEqual(c(2), expected); + assert.deepStrictEqual(d(), expected); + }); +}); diff --git a/test/custom-_.iteratee-methods.js b/test/custom-_.iteratee-methods.js new file mode 100644 index 000000000..0571d77ab --- /dev/null +++ b/test/custom-_.iteratee-methods.js @@ -0,0 +1,270 @@ +import assert from 'assert'; +import partial from '../partial.js'; +import property from '../property.js'; +import iteratee from '../iteratee.js'; + +describe('custom `_.iteratee` methods', function() { + var array = ['one', 'two', 'three'], + getPropA = partial(property, 'a'), + getPropB = partial(property, 'b'), + getLength = partial(property, 'length'), + iteratee = iteratee; + + var getSum = function() { + return function(result, object) { + return result + object.a; + }; + }; + + var objects = [ + { 'a': 0, 'b': 0 }, + { 'a': 1, 'b': 0 }, + { 'a': 1, 'b': 1 } + ]; + + it('`_.countBy` should use `_.iteratee` internally', function() { + iteratee = getLength; + assert.deepEqual(_.countBy(array), { '3': 2, '5': 1 }); + iteratee = iteratee; + }); + + it('`_.differenceBy` should use `_.iteratee` internally', function() { + iteratee = getPropA; + assert.deepEqual(_.differenceBy(objects, [objects[1]]), [objects[0]]); + iteratee = iteratee; + }); + + it('`_.dropRightWhile` should use `_.iteratee` internally', function() { + iteratee = getPropB; + assert.deepEqual(_.dropRightWhile(objects), objects.slice(0, 2)); + iteratee = iteratee; + }); + + it('`_.dropWhile` should use `_.iteratee` internally', function() { + iteratee = getPropB; + assert.deepEqual(_.dropWhile(objects.reverse()).reverse(), objects.reverse().slice(0, 2)); + iteratee = iteratee; + }); + + it('`_.every` should use `_.iteratee` internally', function() { + iteratee = getPropA; + assert.strictEqual(_.every(objects.slice(1)), true); + iteratee = iteratee; + }); + + it('`_.filter` should use `_.iteratee` internally', function() { + var objects = [{ 'a': 0 }, { 'a': 1 }]; + + iteratee = getPropA; + assert.deepEqual(_.filter(objects), [objects[1]]); + iteratee = iteratee; + }); + + it('`_.find` should use `_.iteratee` internally', function() { + iteratee = getPropA; + assert.strictEqual(_.find(objects), objects[1]); + iteratee = iteratee; + }); + + it('`_.findIndex` should use `_.iteratee` internally', function() { + iteratee = getPropA; + assert.strictEqual(_.findIndex(objects), 1); + iteratee = iteratee; + }); + + it('`_.findLast` should use `_.iteratee` internally', function() { + iteratee = getPropA; + assert.strictEqual(_.findLast(objects), objects[2]); + iteratee = iteratee; + }); + + it('`_.findLastIndex` should use `_.iteratee` internally', function() { + iteratee = getPropA; + assert.strictEqual(_.findLastIndex(objects), 2); + iteratee = iteratee; + }); + + it('`_.findKey` should use `_.iteratee` internally', function() { + iteratee = getPropB; + assert.strictEqual(_.findKey(objects), '2'); + iteratee = iteratee; + }); + + it('`_.findLastKey` should use `_.iteratee` internally', function() { + iteratee = getPropB; + assert.strictEqual(_.findLastKey(objects), '2'); + iteratee = iteratee; + }); + + it('`_.groupBy` should use `_.iteratee` internally', function() { + iteratee = getLength; + assert.deepEqual(_.groupBy(array), { '3': ['one', 'two'], '5': ['three'] }); + iteratee = iteratee; + }); + + it('`_.intersectionBy` should use `_.iteratee` internally', function() { + iteratee = getPropA; + assert.deepEqual(_.intersectionBy(objects, [objects[2]]), [objects[1]]); + iteratee = iteratee; + }); + + it('`_.keyBy` should use `_.iteratee` internally', function() { + iteratee = getLength; + assert.deepEqual(_.keyBy(array), { '3': 'two', '5': 'three' }); + iteratee = iteratee; + }); + + it('`_.map` should use `_.iteratee` internally', function() { + iteratee = getPropA; + assert.deepEqual(_.map(objects), [0, 1, 1]); + iteratee = iteratee; + }); + + it('`_.mapKeys` should use `_.iteratee` internally', function() { + iteratee = getPropB; + assert.deepEqual(_.mapKeys({ 'a': { 'b': 2 } }), { '2': { 'b': 2 } }); + iteratee = iteratee; + }); + + it('`_.mapValues` should use `_.iteratee` internally', function() { + iteratee = getPropB; + assert.deepEqual(_.mapValues({ 'a': { 'b': 2 } }), { 'a': 2 }); + iteratee = iteratee; + }); + + it('`_.maxBy` should use `_.iteratee` internally', function() { + iteratee = getPropB; + assert.deepEqual(_.maxBy(objects), objects[2]); + iteratee = iteratee; + }); + + it('`_.meanBy` should use `_.iteratee` internally', function() { + iteratee = getPropA; + assert.strictEqual(_.meanBy(objects), 2 / 3); + iteratee = iteratee; + }); + + it('`_.minBy` should use `_.iteratee` internally', function() { + iteratee = getPropB; + assert.deepEqual(_.minBy(objects), objects[0]); + iteratee = iteratee; + }); + + it('`_.partition` should use `_.iteratee` internally', function() { + var objects = [{ 'a': 1 }, { 'a': 1 }, { 'b': 2 }]; + + iteratee = getPropA; + assert.deepEqual(_.partition(objects), [objects.slice(0, 2), objects.slice(2)]); + iteratee = iteratee; + }); + + it('`_.pullAllBy` should use `_.iteratee` internally', function() { + iteratee = getPropA; + assert.deepEqual(_.pullAllBy(objects.slice(), [{ 'a': 1, 'b': 0 }]), [objects[0]]); + iteratee = iteratee; + }); + + it('`_.reduce` should use `_.iteratee` internally', function() { + iteratee = getSum; + assert.strictEqual(_.reduce(objects, undefined, 0), 2); + iteratee = iteratee; + }); + + it('`_.reduceRight` should use `_.iteratee` internally', function() { + iteratee = getSum; + assert.strictEqual(_.reduceRight(objects, undefined, 0), 2); + iteratee = iteratee; + }); + + it('`_.reject` should use `_.iteratee` internally', function() { + var objects = [{ 'a': 0 }, { 'a': 1 }]; + + iteratee = getPropA; + assert.deepEqual(_.reject(objects), [objects[0]]); + iteratee = iteratee; + }); + + it('`_.remove` should use `_.iteratee` internally', function() { + var objects = [{ 'a': 0 }, { 'a': 1 }]; + + iteratee = getPropA; + _.remove(objects); + assert.deepEqual(objects, [{ 'a': 0 }]); + iteratee = iteratee; + }); + + it('`_.some` should use `_.iteratee` internally', function() { + iteratee = getPropB; + assert.strictEqual(_.some(objects), true); + iteratee = iteratee; + }); + + it('`_.sortBy` should use `_.iteratee` internally', function() { + iteratee = getPropA; + assert.deepEqual(_.sortBy(objects.slice().reverse()), [objects[0], objects[2], objects[1]]); + iteratee = iteratee; + }); + + it('`_.sortedIndexBy` should use `_.iteratee` internally', function() { + var objects = [{ 'a': 30 }, { 'a': 50 }]; + + iteratee = getPropA; + assert.strictEqual(_.sortedIndexBy(objects, { 'a': 40 }), 1); + iteratee = iteratee; + }); + + it('`_.sortedLastIndexBy` should use `_.iteratee` internally', function() { + var objects = [{ 'a': 30 }, { 'a': 50 }]; + + iteratee = getPropA; + assert.strictEqual(_.sortedLastIndexBy(objects, { 'a': 40 }), 1); + iteratee = iteratee; + }); + + it('`_.sumBy` should use `_.iteratee` internally', function() { + iteratee = getPropB; + assert.strictEqual(_.sumBy(objects), 1); + iteratee = iteratee; + }); + + it('`_.takeRightWhile` should use `_.iteratee` internally', function() { + iteratee = getPropB; + assert.deepEqual(_.takeRightWhile(objects), objects.slice(2)); + iteratee = iteratee; + }); + + it('`_.takeWhile` should use `_.iteratee` internally', function() { + iteratee = getPropB; + assert.deepEqual(_.takeWhile(objects.reverse()), objects.reverse().slice(2)); + iteratee = iteratee; + }); + + it('`_.transform` should use `_.iteratee` internally', function() { + iteratee = function() { + return function(result, object) { + result.sum += object.a; + }; + }; + + assert.deepEqual(_.transform(objects, undefined, { 'sum': 0 }), { 'sum': 2 }); + iteratee = iteratee; + }); + + it('`_.uniqBy` should use `_.iteratee` internally', function() { + iteratee = getPropB; + assert.deepEqual(_.uniqBy(objects), [objects[0], objects[2]]); + iteratee = iteratee; + }); + + it('`_.unionBy` should use `_.iteratee` internally', function() { + iteratee = getPropB; + assert.deepEqual(_.unionBy(objects.slice(0, 1), [objects[2]]), [objects[0], objects[2]]); + iteratee = iteratee; + }); + + it('`_.xorBy` should use `_.iteratee` internally', function() { + iteratee = getPropA; + assert.deepEqual(_.xorBy(objects, objects.slice(1)), [objects[0]]); + iteratee = iteratee; + }); +}); diff --git a/test/debounce-and-throttle.js b/test/debounce-and-throttle.js new file mode 100644 index 000000000..ece01eaa6 --- /dev/null +++ b/test/debounce-and-throttle.js @@ -0,0 +1,167 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, noop, push, isModularize } from './utils.js'; +import runInContext from '../runInContext.js'; + +describe('debounce and throttle', function() { + lodashStable.each(['debounce', 'throttle'], function(methodName) { + var func = _[methodName], + isDebounce = methodName == 'debounce'; + + it('`_.' + methodName + '` should not error for non-object `options` values', function() { + func(noop, 32, 1); + assert.ok(true); + }); + + it('`_.' + methodName + '` should use a default `wait` of `0`', function(done) { + var callCount = 0, + funced = func(function() { callCount++; }); + + funced(); + + setTimeout(function() { + funced(); + assert.strictEqual(callCount, isDebounce ? 1 : 2); + done(); + }, 32); + }); + + it('`_.' + methodName + '` should invoke `func` with the correct `this` binding', function(done) { + var actual = [], + object = { 'funced': func(function() { actual.push(this); }, 32) }, + expected = lodashStable.times(isDebounce ? 1 : 2, lodashStable.constant(object)); + + object.funced(); + if (!isDebounce) { + object.funced(); + } + setTimeout(function() { + assert.deepStrictEqual(actual, expected); + done(); + }, 64); + }); + + it('`_.' + methodName + '` supports recursive calls', function(done) { + var actual = [], + args = lodashStable.map(['a', 'b', 'c'], function(chr) { return [{}, chr]; }), + expected = args.slice(), + queue = args.slice(); + + var funced = func(function() { + var current = [this]; + push.apply(current, arguments); + actual.push(current); + + var next = queue.shift(); + if (next) { + funced.call(next[0], next[1]); + } + }, 32); + + var next = queue.shift(); + funced.call(next[0], next[1]); + assert.deepStrictEqual(actual, expected.slice(0, isDebounce ? 0 : 1)); + + setTimeout(function() { + assert.deepStrictEqual(actual, expected.slice(0, actual.length)); + done(); + }, 256); + }); + + it('`_.' + methodName + '` should work if the system time is set backwards', function(done) { + if (!isModularize) { + var callCount = 0, + dateCount = 0; + + var lodash = runInContext({ + 'Date': { + 'now': function() { + return ++dateCount == 4 + ? +new Date(2012, 3, 23, 23, 27, 18) + : +new Date; + } + } + }); + + var funced = lodash[methodName](function() { + callCount++; + }, 32); + + funced(); + + setTimeout(function() { + funced(); + assert.strictEqual(callCount, isDebounce ? 1 : 2); + done(); + }, 64); + } + else { + done(); + } + }); + + it('`_.' + methodName + '` should support cancelling delayed calls', function(done) { + var callCount = 0; + + var funced = func(function() { + callCount++; + }, 32, { 'leading': false }); + + funced(); + funced.cancel(); + + setTimeout(function() { + assert.strictEqual(callCount, 0); + done(); + }, 64); + }); + + it('`_.' + methodName + '` should reset `lastCalled` after cancelling', function(done) { + var callCount = 0; + + var funced = func(function() { + return ++callCount; + }, 32, { 'leading': true }); + + assert.strictEqual(funced(), 1); + funced.cancel(); + + assert.strictEqual(funced(), 2); + funced(); + + setTimeout(function() { + assert.strictEqual(callCount, 3); + done(); + }, 64); + }); + + it('`_.' + methodName + '` should support flushing delayed calls', function(done) { + var callCount = 0; + + var funced = func(function() { + return ++callCount; + }, 32, { 'leading': false }); + + funced(); + assert.strictEqual(funced.flush(), 1); + + setTimeout(function() { + assert.strictEqual(callCount, 1); + done(); + }, 64); + }); + + it('`_.' + methodName + '` should noop `cancel` and `flush` when nothing is queued', function(done) { + var callCount = 0, + funced = func(function() { callCount++; }, 32); + + funced.cancel(); + assert.strictEqual(funced.flush(), undefined); + + setTimeout(function() { + assert.strictEqual(callCount, 0); + done(); + }, 64); + }); + }); +}); diff --git a/test/debounce.test.js b/test/debounce.test.js new file mode 100644 index 000000000..184d9b91d --- /dev/null +++ b/test/debounce.test.js @@ -0,0 +1,250 @@ +import assert from 'assert'; +import { identity, argv, isPhantom, push } from './utils.js'; +import debounce from '../debounce.js'; + +describe('debounce', function() { + it('should debounce a function', function(done) { + var callCount = 0; + + var debounced = debounce(function(value) { + ++callCount; + return value; + }, 32); + + var results = [debounced('a'), debounced('b'), debounced('c')]; + assert.deepStrictEqual(results, [undefined, undefined, undefined]); + assert.strictEqual(callCount, 0); + + setTimeout(function() { + assert.strictEqual(callCount, 1); + + var results = [debounced('d'), debounced('e'), debounced('f')]; + assert.deepStrictEqual(results, ['c', 'c', 'c']); + assert.strictEqual(callCount, 1); + }, 128); + + setTimeout(function() { + assert.strictEqual(callCount, 2); + done(); + }, 256); + }); + + it('subsequent debounced calls return the last `func` result', function(done) { + var debounced = debounce(identity, 32); + debounced('a'); + + setTimeout(function() { + assert.notStrictEqual(debounced('b'), 'b'); + }, 64); + + setTimeout(function() { + assert.notStrictEqual(debounced('c'), 'c'); + done(); + }, 128); + }); + + it('should not immediately call `func` when `wait` is `0`', function(done) { + var callCount = 0, + debounced = debounce(function() { ++callCount; }, 0); + + debounced(); + debounced(); + assert.strictEqual(callCount, 0); + + setTimeout(function() { + assert.strictEqual(callCount, 1); + done(); + }, 5); + }); + + it('should apply default options', function(done) { + var callCount = 0, + debounced = debounce(function() { callCount++; }, 32, {}); + + debounced(); + assert.strictEqual(callCount, 0); + + setTimeout(function() { + assert.strictEqual(callCount, 1); + done(); + }, 64); + }); + + it('should support a `leading` option', function(done) { + var callCounts = [0, 0]; + + var withLeading = debounce(function() { + callCounts[0]++; + }, 32, { 'leading': true }); + + var withLeadingAndTrailing = debounce(function() { + callCounts[1]++; + }, 32, { 'leading': true }); + + withLeading(); + assert.strictEqual(callCounts[0], 1); + + withLeadingAndTrailing(); + withLeadingAndTrailing(); + assert.strictEqual(callCounts[1], 1); + + setTimeout(function() { + assert.deepStrictEqual(callCounts, [1, 2]); + + withLeading(); + assert.strictEqual(callCounts[0], 2); + + done(); + }, 64); + }); + + it('subsequent leading debounced calls return the last `func` result', function(done) { + var debounced = debounce(identity, 32, { 'leading': true, 'trailing': false }), + results = [debounced('a'), debounced('b')]; + + assert.deepStrictEqual(results, ['a', 'a']); + + setTimeout(function() { + var results = [debounced('c'), debounced('d')]; + assert.deepStrictEqual(results, ['c', 'c']); + done(); + }, 64); + }); + + it('should support a `trailing` option', function(done) { + var withCount = 0, + withoutCount = 0; + + var withTrailing = debounce(function() { + withCount++; + }, 32, { 'trailing': true }); + + var withoutTrailing = debounce(function() { + withoutCount++; + }, 32, { 'trailing': false }); + + withTrailing(); + assert.strictEqual(withCount, 0); + + withoutTrailing(); + assert.strictEqual(withoutCount, 0); + + setTimeout(function() { + assert.strictEqual(withCount, 1); + assert.strictEqual(withoutCount, 0); + done(); + }, 64); + }); + + it('should support a `maxWait` option', function(done) { + var callCount = 0; + + var debounced = debounce(function(value) { + ++callCount; + return value; + }, 32, { 'maxWait': 64 }); + + debounced(); + debounced(); + assert.strictEqual(callCount, 0); + + setTimeout(function() { + assert.strictEqual(callCount, 1); + debounced(); + debounced(); + assert.strictEqual(callCount, 1); + }, 128); + + setTimeout(function() { + assert.strictEqual(callCount, 2); + done(); + }, 256); + }); + + it('should support `maxWait` in a tight loop', function(done) { + var limit = (argv || isPhantom) ? 1000 : 320, + withCount = 0, + withoutCount = 0; + + var withMaxWait = debounce(function() { + withCount++; + }, 64, { 'maxWait': 128 }); + + var withoutMaxWait = debounce(function() { + withoutCount++; + }, 96); + + var start = +new Date; + while ((new Date - start) < limit) { + withMaxWait(); + withoutMaxWait(); + } + var actual = [Boolean(withoutCount), Boolean(withCount)]; + setTimeout(function() { + assert.deepStrictEqual(actual, [false, true]); + done(); + }, 1); + }); + + it('should queue a trailing call for subsequent debounced calls after `maxWait`', function(done) { + var callCount = 0; + + var debounced = debounce(function() { + ++callCount; + }, 200, { 'maxWait': 200 }); + + debounced(); + + setTimeout(debounced, 190); + setTimeout(debounced, 200); + setTimeout(debounced, 210); + + setTimeout(function() { + assert.strictEqual(callCount, 2); + done(); + }, 500); + }); + + it('should cancel `maxDelayed` when `delayed` is invoked', function(done) { + var callCount = 0; + + var debounced = debounce(function() { + callCount++; + }, 32, { 'maxWait': 64 }); + + debounced(); + + setTimeout(function() { + debounced(); + assert.strictEqual(callCount, 1); + }, 128); + + setTimeout(function() { + assert.strictEqual(callCount, 2); + done(); + }, 192); + }); + + it('should invoke the trailing call with the correct arguments and `this` binding', function(done) { + var actual, + callCount = 0, + object = {}; + + var debounced = debounce(function(value) { + actual = [this]; + push.apply(actual, arguments); + return ++callCount != 2; + }, 32, { 'leading': true, 'maxWait': 64 }); + + while (true) { + if (!debounced.call(object, 'a')) { + break; + } + } + setTimeout(function() { + assert.strictEqual(callCount, 2); + assert.deepStrictEqual(actual, [object, 'a']); + done(); + }, 64); + }); +}); diff --git a/test/deburr.test.js b/test/deburr.test.js new file mode 100644 index 000000000..5ab176f4a --- /dev/null +++ b/test/deburr.test.js @@ -0,0 +1,28 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { burredLetters, deburredLetters, comboMarks } from './utils.js'; +import deburr from '../deburr.js'; + +describe('deburr', function() { + it('should convert Latin Unicode letters to basic Latin', function() { + var actual = lodashStable.map(burredLetters, deburr); + assert.deepStrictEqual(actual, deburredLetters); + }); + + it('should not deburr Latin mathematical operators', function() { + var operators = ['\xd7', '\xf7'], + actual = lodashStable.map(operators, deburr); + + assert.deepStrictEqual(actual, operators); + }); + + it('should deburr combining diacritical marks', function() { + var expected = lodashStable.map(comboMarks, lodashStable.constant('ei')); + + var actual = lodashStable.map(comboMarks, function(chr) { + return deburr('e' + chr + 'i'); + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/defaultTo.test.js b/test/defaultTo.test.js new file mode 100644 index 000000000..5d6dc5f3a --- /dev/null +++ b/test/defaultTo.test.js @@ -0,0 +1,18 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey } from './utils.js'; +import defaultTo from '../defaultTo.js'; + +describe('defaultTo', function() { + it('should return a default value if `value` is `NaN` or nullish', function() { + var expected = lodashStable.map(falsey, function(value) { + return (value == null || value !== value) ? 1 : value; + }); + + var actual = lodashStable.map(falsey, function(value) { + return defaultTo(value, 1); + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/defaults.test.js b/test/defaults.test.js new file mode 100644 index 000000000..867f31d80 --- /dev/null +++ b/test/defaults.test.js @@ -0,0 +1,66 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { objectProto } from './utils.js'; +import defaults from '../defaults.js'; + +describe('defaults', function() { + it('should assign source properties if missing on `object`', function() { + var actual = defaults({ 'a': 1 }, { 'a': 2, 'b': 2 }); + assert.deepStrictEqual(actual, { 'a': 1, 'b': 2 }); + }); + + it('should accept multiple sources', function() { + var expected = { 'a': 1, 'b': 2, 'c': 3 }, + actual = defaults({ 'a': 1, 'b': 2 }, { 'b': 3 }, { 'c': 3 }); + + assert.deepStrictEqual(actual, expected); + + actual = defaults({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 3 }, { 'c': 2 }); + assert.deepStrictEqual(actual, expected); + }); + + it('should not overwrite `null` values', function() { + var actual = defaults({ 'a': null }, { 'a': 1 }); + assert.strictEqual(actual.a, null); + }); + + it('should overwrite `undefined` values', function() { + var actual = defaults({ 'a': undefined }, { 'a': 1 }); + assert.strictEqual(actual.a, 1); + }); + + it('should assign `undefined` values', function() { + var source = { 'a': undefined, 'b': 1 }, + actual = defaults({}, source); + + assert.deepStrictEqual(actual, { 'a': undefined, 'b': 1 }); + }); + + it('should assign properties that shadow those on `Object.prototype`', function() { + var object = { + 'constructor': objectProto.constructor, + 'hasOwnProperty': objectProto.hasOwnProperty, + 'isPrototypeOf': objectProto.isPrototypeOf, + 'propertyIsEnumerable': objectProto.propertyIsEnumerable, + 'toLocaleString': objectProto.toLocaleString, + 'toString': objectProto.toString, + 'valueOf': objectProto.valueOf + }; + + var source = { + 'constructor': 1, + 'hasOwnProperty': 2, + 'isPrototypeOf': 3, + 'propertyIsEnumerable': 4, + 'toLocaleString': 5, + 'toString': 6, + 'valueOf': 7 + }; + + var expected = lodashStable.clone(source); + assert.deepStrictEqual(defaults({}, source), expected); + + expected = lodashStable.clone(object); + assert.deepStrictEqual(defaults({}, object, source), expected); + }); +}); diff --git a/test/defaultsDeep.js b/test/defaultsDeep.js new file mode 100644 index 000000000..a4a9ff2fb --- /dev/null +++ b/test/defaultsDeep.js @@ -0,0 +1,101 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { noop } from './utils.js'; +import defaultsDeep from '../defaultsDeep.js'; + +describe('defaultsDeep', function() { + it('should deep assign source properties if missing on `object`', function() { + var object = { 'a': { 'b': 2 }, 'd': 4 }, + source = { 'a': { 'b': 3, 'c': 3 }, 'e': 5 }, + expected = { 'a': { 'b': 2, 'c': 3 }, 'd': 4, 'e': 5 }; + + assert.deepStrictEqual(defaultsDeep(object, source), expected); + }); + + it('should accept multiple sources', function() { + var source1 = { 'a': { 'b': 3 } }, + source2 = { 'a': { 'c': 3 } }, + source3 = { 'a': { 'b': 3, 'c': 3 } }, + source4 = { 'a': { 'c': 4 } }, + expected = { 'a': { 'b': 2, 'c': 3 } }; + + assert.deepStrictEqual(defaultsDeep({ 'a': { 'b': 2 } }, source1, source2), expected); + assert.deepStrictEqual(defaultsDeep({ 'a': { 'b': 2 } }, source3, source4), expected); + }); + + it('should not overwrite `null` values', function() { + var object = { 'a': { 'b': null } }, + source = { 'a': { 'b': 2 } }, + actual = defaultsDeep(object, source); + + assert.strictEqual(actual.a.b, null); + }); + + it('should not overwrite regexp values', function() { + var object = { 'a': { 'b': /x/ } }, + source = { 'a': { 'b': /y/ } }, + actual = defaultsDeep(object, source); + + assert.deepStrictEqual(actual.a.b, /x/); + }); + + it('should not convert function properties to objects', function() { + var actual = defaultsDeep({}, { 'a': noop }); + assert.strictEqual(actual.a, noop); + + actual = defaultsDeep({}, { 'a': { 'b': noop } }); + assert.strictEqual(actual.a.b, noop); + }); + + it('should overwrite `undefined` values', function() { + var object = { 'a': { 'b': undefined } }, + source = { 'a': { 'b': 2 } }, + actual = defaultsDeep(object, source); + + assert.strictEqual(actual.a.b, 2); + }); + + it('should assign `undefined` values', function() { + var source = { 'a': undefined, 'b': { 'c': undefined, 'd': 1 } }, + expected = lodashStable.cloneDeep(source), + actual = defaultsDeep({}, source); + + assert.deepStrictEqual(actual, expected); + }); + + it('should merge sources containing circular references', function() { + var object = { + 'foo': { 'b': { 'c': { 'd': {} } } }, + 'bar': { 'a': 2 } + }; + + var source = { + 'foo': { 'b': { 'c': { 'd': {} } } }, + 'bar': {} + }; + + object.foo.b.c.d = object; + source.foo.b.c.d = source; + source.bar.b = source.foo.b; + + var actual = defaultsDeep(object, source); + + assert.strictEqual(actual.bar.b, actual.foo.b); + assert.strictEqual(actual.foo.b.c.d, actual.foo.b.c.d.foo.b.c.d); + }); + + it('should not modify sources', function() { + var source1 = { 'a': 1, 'b': { 'c': 2 } }, + source2 = { 'b': { 'c': 3, 'd': 3 } }, + actual = defaultsDeep({}, source1, source2); + + assert.deepStrictEqual(actual, { 'a': 1, 'b': { 'c': 2, 'd': 3 } }); + assert.deepStrictEqual(source1, { 'a': 1, 'b': { 'c': 2 } }); + assert.deepStrictEqual(source2, { 'b': { 'c': 3, 'd': 3 } }); + }); + + it('should not attempt a merge of a string into an array', function() { + var actual = defaultsDeep({ 'a': ['abc'] }, { 'a': 'abc' }); + assert.deepStrictEqual(actual.a, ['abc']); + }); +}); diff --git a/test/defer.test.js b/test/defer.test.js new file mode 100644 index 000000000..421fab372 --- /dev/null +++ b/test/defer.test.js @@ -0,0 +1,40 @@ +import assert from 'assert'; +import { slice } from './utils.js'; +import defer from '../defer.js'; + +describe('defer', function() { + it('should defer `func` execution', function(done) { + var pass = false; + defer(function() { pass = true; }); + + setTimeout(function() { + assert.ok(pass); + done(); + }, 32); + }); + + it('should provide additional arguments to `func`', function(done) { + var args; + + defer(function() { + args = slice.call(arguments); + }, 1, 2); + + setTimeout(function() { + assert.deepStrictEqual(args, [1, 2]); + done(); + }, 32); + }); + + it('should be cancelable', function(done) { + var pass = true, + timerId = defer(function() { pass = false; }); + + clearTimeout(timerId); + + setTimeout(function() { + assert.ok(pass); + done(); + }, 32); + }); +}); diff --git a/test/delay.js b/test/delay.js new file mode 100644 index 000000000..ef3ebcae4 --- /dev/null +++ b/test/delay.js @@ -0,0 +1,67 @@ +import assert from 'assert'; +import { slice } from './utils.js'; +import delay from '../delay.js'; + +describe('delay', function() { + it('should delay `func` execution', function(done) { + var pass = false; + delay(function() { pass = true; }, 32); + + setTimeout(function() { + assert.ok(!pass); + }, 1); + + setTimeout(function() { + assert.ok(pass); + done(); + }, 64); + }); + + it('should provide additional arguments to `func`', function(done) { + var args; + + delay(function() { + args = slice.call(arguments); + }, 32, 1, 2); + + setTimeout(function() { + assert.deepStrictEqual(args, [1, 2]); + done(); + }, 64); + }); + + it('should use a default `wait` of `0`', function(done) { + var pass = false; + delay(function() { pass = true; }); + + assert.ok(!pass); + + setTimeout(function() { + assert.ok(pass); + done(); + }, 0); + }); + + it('should be cancelable', function(done) { + var pass = true, + timerId = delay(function() { pass = false; }, 32); + + clearTimeout(timerId); + + setTimeout(function() { + assert.ok(pass); + done(); + }, 64); + }); + + it('should work with mocked `setTimeout`', function() { + var pass = false, + setTimeout = root.setTimeout; + + setProperty(root, 'setTimeout', function(func) { func(); }); + delay(function() { pass = true; }, 32); + setProperty(root, 'setTimeout', setTimeout); + + assert.ok(pass); + }); +}); diff --git a/test/difference-methods.js b/test/difference-methods.js new file mode 100644 index 000000000..6591193cf --- /dev/null +++ b/test/difference-methods.js @@ -0,0 +1,85 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, LARGE_ARRAY_SIZE, stubOne, stubNaN, args } from './utils.js'; + +describe('difference methods', function() { + lodashStable.each(['difference', 'differenceBy', 'differenceWith'], function(methodName) { + var func = _[methodName]; + + it('`_.' + methodName + '` should return the difference of two arrays', function() { + var actual = func([2, 1], [2, 3]); + assert.deepStrictEqual(actual, [1]); + }); + + it('`_.' + methodName + '` should return the difference of multiple arrays', function() { + var actual = func([2, 1, 2, 3], [3, 4], [3, 2]); + assert.deepStrictEqual(actual, [1]); + }); + + it('`_.' + methodName + '` should treat `-0` as `0`', function() { + var array = [-0, 0]; + + var actual = lodashStable.map(array, function(value) { + return func(array, [value]); + }); + + assert.deepStrictEqual(actual, [[], []]); + + actual = lodashStable.map(func([-0, 1], [1]), lodashStable.toString); + assert.deepStrictEqual(actual, ['0']); + }); + + it('`_.' + methodName + '` should match `NaN`', function() { + assert.deepStrictEqual(func([1, NaN, 3], [NaN, 5, NaN]), [1, 3]); + }); + + it('`_.' + methodName + '` should work with large arrays', function() { + var array1 = lodashStable.range(LARGE_ARRAY_SIZE + 1), + array2 = lodashStable.range(LARGE_ARRAY_SIZE), + a = {}, + b = {}, + c = {}; + + array1.push(a, b, c); + array2.push(b, c, a); + + assert.deepStrictEqual(func(array1, array2), [LARGE_ARRAY_SIZE]); + }); + + it('`_.' + methodName + '` should work with large arrays of `-0` as `0`', function() { + var array = [-0, 0]; + + var actual = lodashStable.map(array, function(value) { + var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, lodashStable.constant(value)); + return func(array, largeArray); + }); + + assert.deepStrictEqual(actual, [[], []]); + + var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, stubOne); + actual = lodashStable.map(func([-0, 1], largeArray), lodashStable.toString); + assert.deepStrictEqual(actual, ['0']); + }); + + it('`_.' + methodName + '` should work with large arrays of `NaN`', function() { + var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, stubNaN); + assert.deepStrictEqual(func([1, NaN, 3], largeArray), [1, 3]); + }); + + it('`_.' + methodName + '` should work with large arrays of objects', function() { + var object1 = {}, + object2 = {}, + largeArray = lodashStable.times(LARGE_ARRAY_SIZE, lodashStable.constant(object1)); + + assert.deepStrictEqual(func([object1, object2], largeArray), [object2]); + }); + + it('`_.' + methodName + '` should ignore values that are not array-like', function() { + var array = [1, null, 3]; + + assert.deepStrictEqual(func(args, 3, { '0': 1 }), [1, 2, 3]); + assert.deepStrictEqual(func(null, array, 1), []); + assert.deepStrictEqual(func(array, args, null), [null]); + }); + }); +}); diff --git a/test/differenceBy.js b/test/differenceBy.js new file mode 100644 index 000000000..af5ca665b --- /dev/null +++ b/test/differenceBy.js @@ -0,0 +1,23 @@ +import assert from 'assert'; +import { slice } from './utils.js'; +import differenceBy from '../differenceBy.js'; + +describe('differenceBy', function() { + it('should accept an `iteratee`', function() { + var actual = differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor); + assert.deepStrictEqual(actual, [1.2]); + + actual = differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x'); + assert.deepStrictEqual(actual, [{ 'x': 2 }]); + }); + + it('should provide correct `iteratee` arguments', function() { + var args; + + differenceBy([2.1, 1.2], [2.3, 3.4], function() { + args || (args = slice.call(arguments)); + }); + + assert.deepStrictEqual(args, [2.3]); + }); +}); diff --git a/test/differenceWith.test.js b/test/differenceWith.test.js new file mode 100644 index 000000000..1c7c49d40 --- /dev/null +++ b/test/differenceWith.test.js @@ -0,0 +1,26 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { LARGE_ARRAY_SIZE, stubOne } from './utils.js'; +import differenceWith from '../differenceWith.js'; + +describe('differenceWith', function() { + it('should work with a `comparator`', function() { + var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }], + actual = differenceWith(objects, [{ 'x': 1, 'y': 2 }], lodashStable.isEqual); + + assert.deepStrictEqual(actual, [objects[1]]); + }); + + it('should preserve the sign of `0`', function() { + var array = [-0, 1], + largeArray = lodashStable.times(LARGE_ARRAY_SIZE, stubOne), + others = [[1], largeArray], + expected = lodashStable.map(others, lodashStable.constant(['-0'])); + + var actual = lodashStable.map(others, function(other) { + return lodashStable.map(differenceWith(array, other, lodashStable.eq), lodashStable.toString); + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/divide.test.js b/test/divide.test.js new file mode 100644 index 000000000..495a6bb70 --- /dev/null +++ b/test/divide.test.js @@ -0,0 +1,15 @@ +import assert from 'assert'; +import divide from '../divide.js'; + +describe('divide', function() { + it('should divide two numbers', function() { + assert.strictEqual(divide(6, 4), 1.5); + assert.strictEqual(divide(-6, 4), -1.5); + assert.strictEqual(divide(-6, -4), 1.5); + }); + + it('should coerce arguments to numbers', function() { + assert.strictEqual(divide('6', '4'), 1.5); + assert.deepStrictEqual(divide('x', 'y'), NaN); + }); +}); diff --git a/test/drop.js b/test/drop.js new file mode 100644 index 000000000..13aadd8f9 --- /dev/null +++ b/test/drop.js @@ -0,0 +1,69 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, LARGE_ARRAY_SIZE, isEven } from './utils.js'; +import drop from '../drop.js'; + +describe('drop', function() { + var array = [1, 2, 3]; + + it('should drop the first two elements', function() { + assert.deepStrictEqual(drop(array, 2), [3]); + }); + + it('should treat falsey `n` values, except `undefined`, as `0`', function() { + var expected = lodashStable.map(falsey, function(value) { + return value === undefined ? [2, 3] : array; + }); + + var actual = lodashStable.map(falsey, function(n) { + return drop(array, n); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return all elements when `n` < `1`', function() { + lodashStable.each([0, -1, -Infinity], function(n) { + assert.deepStrictEqual(drop(array, n), array); + }); + }); + + it('should return an empty array when `n` >= `length`', function() { + lodashStable.each([3, 4, Math.pow(2, 32), Infinity], function(n) { + assert.deepStrictEqual(drop(array, n), []); + }); + }); + + it('should coerce `n` to an integer', function() { + assert.deepStrictEqual(drop(array, 1.6), [2, 3]); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + actual = lodashStable.map(array, drop); + + assert.deepStrictEqual(actual, [[2, 3], [5, 6], [8, 9]]); + }); + + it('should work in a lazy sequence', function() { + var array = lodashStable.range(1, LARGE_ARRAY_SIZE + 1), + predicate = function(value) { values.push(value); return isEven(value); }, + values = [], + actual = _(array).drop(2).drop().value(); + + assert.deepEqual(actual, array.slice(3)); + + actual = _(array).filter(predicate).drop(2).drop().value(); + assert.deepEqual(values, array); + assert.deepEqual(actual, drop(drop(_.filter(array, predicate), 2))); + + actual = _(array).drop(2).dropRight().drop().dropRight(2).value(); + assert.deepEqual(actual, _.dropRight(drop(_.dropRight(drop(array, 2))), 2)); + + values = []; + + actual = _(array).drop().filter(predicate).drop(2).dropRight().drop().dropRight(2).value(); + assert.deepEqual(values, array.slice(1)); + assert.deepEqual(actual, _.dropRight(drop(_.dropRight(drop(_.filter(drop(array), predicate), 2))), 2)); + }); +}); diff --git a/test/dropRight.js b/test/dropRight.js new file mode 100644 index 000000000..5966621ab --- /dev/null +++ b/test/dropRight.js @@ -0,0 +1,69 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, LARGE_ARRAY_SIZE, isEven } from './utils.js'; +import dropRight from '../dropRight.js'; + +describe('dropRight', function() { + var array = [1, 2, 3]; + + it('should drop the last two elements', function() { + assert.deepStrictEqual(dropRight(array, 2), [1]); + }); + + it('should treat falsey `n` values, except `undefined`, as `0`', function() { + var expected = lodashStable.map(falsey, function(value) { + return value === undefined ? [1, 2] : array; + }); + + var actual = lodashStable.map(falsey, function(n) { + return dropRight(array, n); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return all elements when `n` < `1`', function() { + lodashStable.each([0, -1, -Infinity], function(n) { + assert.deepStrictEqual(dropRight(array, n), array); + }); + }); + + it('should return an empty array when `n` >= `length`', function() { + lodashStable.each([3, 4, Math.pow(2, 32), Infinity], function(n) { + assert.deepStrictEqual(dropRight(array, n), []); + }); + }); + + it('should coerce `n` to an integer', function() { + assert.deepStrictEqual(dropRight(array, 1.6), [1, 2]); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + actual = lodashStable.map(array, dropRight); + + assert.deepStrictEqual(actual, [[1, 2], [4, 5], [7, 8]]); + }); + + it('should work in a lazy sequence', function() { + var array = lodashStable.range(1, LARGE_ARRAY_SIZE + 1), + predicate = function(value) { values.push(value); return isEven(value); }, + values = [], + actual = _(array).dropRight(2).dropRight().value(); + + assert.deepEqual(actual, array.slice(0, -3)); + + actual = _(array).filter(predicate).dropRight(2).dropRight().value(); + assert.deepEqual(values, array); + assert.deepEqual(actual, dropRight(dropRight(_.filter(array, predicate), 2))); + + actual = _(array).dropRight(2).drop().dropRight().drop(2).value(); + assert.deepEqual(actual, _.drop(dropRight(_.drop(dropRight(array, 2))), 2)); + + values = []; + + actual = _(array).dropRight().filter(predicate).dropRight(2).drop().dropRight().drop(2).value(); + assert.deepEqual(values, array.slice(0, -1)); + assert.deepEqual(actual, _.drop(dropRight(_.drop(dropRight(_.filter(dropRight(array), predicate), 2))), 2)); + }); +}); diff --git a/test/dropRightWhile.js b/test/dropRightWhile.js new file mode 100644 index 000000000..07dceadde --- /dev/null +++ b/test/dropRightWhile.js @@ -0,0 +1,52 @@ +import assert from 'assert'; +import { slice } from './utils.js'; +import dropRightWhile from '../dropRightWhile.js'; + +describe('dropRightWhile', function() { + var array = [1, 2, 3, 4]; + + var objects = [ + { 'a': 0, 'b': 0 }, + { 'a': 1, 'b': 1 }, + { 'a': 2, 'b': 2 } + ]; + + it('should drop elements while `predicate` returns truthy', function() { + var actual = dropRightWhile(array, function(n) { + return n > 2; + }); + + assert.deepStrictEqual(actual, [1, 2]); + }); + + it('should provide correct `predicate` arguments', function() { + var args; + + dropRightWhile(array, function() { + args = slice.call(arguments); + }); + + assert.deepStrictEqual(args, [4, 3, array]); + }); + + it('should work with `_.matches` shorthands', function() { + assert.deepStrictEqual(dropRightWhile(objects, { 'b': 2 }), objects.slice(0, 2)); + }); + + it('should work with `_.matchesProperty` shorthands', function() { + assert.deepStrictEqual(dropRightWhile(objects, ['b', 2]), objects.slice(0, 2)); + }); + + it('should work with `_.property` shorthands', function() { + assert.deepStrictEqual(dropRightWhile(objects, 'b'), objects.slice(0, 1)); + }); + + it('should return a wrapped value when chaining', function() { + var wrapped = _(array).dropRightWhile(function(n) { + return n > 2; + }); + + assert.ok(wrapped instanceof _); + assert.deepEqual(wrapped.value(), [1, 2]); + }); +}); diff --git a/test/dropWhile.js b/test/dropWhile.js new file mode 100644 index 000000000..f02088104 --- /dev/null +++ b/test/dropWhile.js @@ -0,0 +1,67 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice, LARGE_ARRAY_SIZE } from './utils.js'; +import dropWhile from '../dropWhile.js'; + +describe('dropWhile', function() { + var array = [1, 2, 3, 4]; + + var objects = [ + { 'a': 2, 'b': 2 }, + { 'a': 1, 'b': 1 }, + { 'a': 0, 'b': 0 } + ]; + + it('should drop elements while `predicate` returns truthy', function() { + var actual = dropWhile(array, function(n) { + return n < 3; + }); + + assert.deepStrictEqual(actual, [3, 4]); + }); + + it('should provide correct `predicate` arguments', function() { + var args; + + dropWhile(array, function() { + args = slice.call(arguments); + }); + + assert.deepStrictEqual(args, [1, 0, array]); + }); + + it('should work with `_.matches` shorthands', function() { + assert.deepStrictEqual(dropWhile(objects, { 'b': 2 }), objects.slice(1)); + }); + + it('should work with `_.matchesProperty` shorthands', function() { + assert.deepStrictEqual(dropWhile(objects, ['b', 2]), objects.slice(1)); + }); + + it('should work with `_.property` shorthands', function() { + assert.deepStrictEqual(dropWhile(objects, 'b'), objects.slice(2)); + }); + + it('should work in a lazy sequence', function() { + var array = lodashStable.range(1, LARGE_ARRAY_SIZE + 3), + predicate = function(n) { return n < 3; }, + expected = dropWhile(array, predicate), + wrapped = _(array).dropWhile(predicate); + + assert.deepEqual(wrapped.value(), expected); + assert.deepEqual(wrapped.reverse().value(), expected.slice().reverse()); + assert.strictEqual(wrapped.last(), _.last(expected)); + }); + + it('should work in a lazy sequence with `drop`', function() { + var array = lodashStable.range(1, LARGE_ARRAY_SIZE + 3); + + var actual = _(array) + .dropWhile(function(n) { return n == 1; }) + .drop() + .dropWhile(function(n) { return n == 3; }) + .value(); + + assert.deepEqual(actual, array.slice(3)); + }); +}); diff --git a/test/endsWith.test.js b/test/endsWith.test.js new file mode 100644 index 000000000..ceb9dddd4 --- /dev/null +++ b/test/endsWith.test.js @@ -0,0 +1,49 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { MAX_SAFE_INTEGER, falsey, stubTrue } from './utils.js'; +import endsWith from '../endsWith.js'; + +describe('endsWith', function() { + var string = 'abc'; + + it('should return `true` if a string ends with `target`', function() { + assert.strictEqual(endsWith(string, 'c'), true); + }); + + it('should return `false` if a string does not end with `target`', function() { + assert.strictEqual(endsWith(string, 'b'), false); + }); + + it('should work with a `position`', function() { + assert.strictEqual(endsWith(string, 'b', 2), true); + }); + + it('should work with `position` >= `length`', function() { + lodashStable.each([3, 5, MAX_SAFE_INTEGER, Infinity], function(position) { + assert.strictEqual(endsWith(string, 'c', position), true); + }); + }); + + it('should treat falsey `position` values, except `undefined`, as `0`', function() { + var expected = lodashStable.map(falsey, stubTrue); + + var actual = lodashStable.map(falsey, function(position) { + return endsWith(string, position === undefined ? 'c' : '', position); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should treat a negative `position` as `0`', function() { + lodashStable.each([-1, -3, -Infinity], function(position) { + assert.ok(lodashStable.every(string, function(chr) { + return !endsWith(string, chr, position); + })); + assert.strictEqual(endsWith(string, '', position), true); + }); + }); + + it('should coerce `position` to an integer', function() { + assert.strictEqual(endsWith(string, 'ab', 2.2), true); + }); +}); diff --git a/test/eq.test.js b/test/eq.test.js new file mode 100644 index 000000000..ec0c7adad --- /dev/null +++ b/test/eq.test.js @@ -0,0 +1,21 @@ +import assert from 'assert'; +import eq from '../eq.js'; + +describe('eq', function() { + it('should perform a `SameValueZero` comparison of two values', function() { + assert.strictEqual(eq(), true); + assert.strictEqual(eq(undefined), true); + assert.strictEqual(eq(0, -0), true); + assert.strictEqual(eq(NaN, NaN), true); + assert.strictEqual(eq(1, 1), true); + + assert.strictEqual(eq(null, undefined), false); + assert.strictEqual(eq(1, Object(1)), false); + assert.strictEqual(eq(1, '1'), false); + assert.strictEqual(eq(1, '1'), false); + + var object = { 'a': 1 }; + assert.strictEqual(eq(object, object), true); + assert.strictEqual(eq(object, { 'a': 1 }), false); + }); +}); diff --git a/test/escape.js b/test/escape.js new file mode 100644 index 000000000..f5b7dbaba --- /dev/null +++ b/test/escape.js @@ -0,0 +1,30 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import escape from '../escape.js'; +import unescape from '../unescape.js'; + +describe('escape', function() { + var escaped = '&<>"'/', + unescaped = '&<>"\'/'; + + escaped += escaped; + unescaped += unescaped; + + it('should escape values', function() { + assert.strictEqual(escape(unescaped), escaped); + }); + + it('should handle strings with nothing to escape', function() { + assert.strictEqual(escape('abc'), 'abc'); + }); + + it('should escape the same characters unescaped by `_.unescape`', function() { + assert.strictEqual(escape(unescape(escaped)), escaped); + }); + + lodashStable.each(['`', '/'], function(chr) { + it('should not escape the "' + chr + '" character', function() { + assert.strictEqual(escape(chr), chr); + }); + }); +}); diff --git a/test/escapeRegExp.js b/test/escapeRegExp.js new file mode 100644 index 000000000..3fa706274 --- /dev/null +++ b/test/escapeRegExp.js @@ -0,0 +1,28 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { stubString } from './utils.js'; +import escapeRegExp from '../escapeRegExp.js'; + +describe('escapeRegExp', function() { + var escaped = '\\^\\$\\.\\*\\+\\?\\(\\)\\[\\]\\{\\}\\|\\\\', + unescaped = '^$.*+?()[]{}|\\'; + + it('should escape values', function() { + assert.strictEqual(escapeRegExp(unescaped + unescaped), escaped + escaped); + }); + + it('should handle strings with nothing to escape', function() { + assert.strictEqual(escapeRegExp('abc'), 'abc'); + }); + + it('should return an empty string for empty values', function() { + var values = [, null, undefined, ''], + expected = lodashStable.map(values, stubString); + + var actual = lodashStable.map(values, function(value, index) { + return index ? escapeRegExp(value) : escapeRegExp(); + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/every.js b/test/every.js new file mode 100644 index 000000000..76052aa9f --- /dev/null +++ b/test/every.js @@ -0,0 +1,74 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { identity, empties, stubTrue, stubFalse } from './utils.js'; +import every from '../every.js'; + +describe('every', function() { + it('should return `true` if `predicate` returns truthy for all elements', function() { + assert.strictEqual(lodashStable.every([true, 1, 'a'], identity), true); + }); + + it('should return `true` for empty collections', function() { + var expected = lodashStable.map(empties, stubTrue); + + var actual = lodashStable.map(empties, function(value) { + try { + return every(value, identity); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return `false` as soon as `predicate` returns falsey', function() { + var count = 0; + + assert.strictEqual(every([true, null, true], function(value) { + count++; + return value; + }), false); + + assert.strictEqual(count, 2); + }); + + it('should work with collections of `undefined` values (test in IE < 9)', function() { + assert.strictEqual(every([undefined, undefined, undefined], identity), false); + }); + + it('should use `_.identity` when `predicate` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, stubFalse); + + var actual = lodashStable.map(values, function(value, index) { + var array = [0]; + return index ? every(array, value) : every(array); + }); + + assert.deepStrictEqual(actual, expected); + + expected = lodashStable.map(values, stubTrue); + actual = lodashStable.map(values, function(value, index) { + var array = [1]; + return index ? every(array, value) : every(array); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with `_.property` shorthands', function() { + var objects = [{ 'a': 0, 'b': 1 }, { 'a': 1, 'b': 2 }]; + assert.strictEqual(every(objects, 'a'), false); + assert.strictEqual(every(objects, 'b'), true); + }); + + it('should work with `_.matches` shorthands', function() { + var objects = [{ 'a': 0, 'b': 0 }, { 'a': 0, 'b': 1 }]; + assert.strictEqual(every(objects, { 'a': 0 }), true); + assert.strictEqual(every(objects, { 'b': 1 }), false); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var actual = lodashStable.map([[1]], every); + assert.deepStrictEqual(actual, [true]); + }); +}); diff --git a/test/exit-early.js b/test/exit-early.js new file mode 100644 index 000000000..090d6c893 --- /dev/null +++ b/test/exit-early.js @@ -0,0 +1,37 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _ } from './utils.js'; + +describe('exit early', function() { + lodashStable.each(['_baseEach', 'forEach', 'forEachRight', 'forIn', 'forInRight', 'forOwn', 'forOwnRight', 'transform'], function(methodName) { + var func = _[methodName]; + + it('`_.' + methodName + '` can exit early when iterating arrays', function() { + if (func) { + var array = [1, 2, 3], + values = []; + + func(array, function(value, other) { + values.push(lodashStable.isArray(value) ? other : value); + return false; + }); + + assert.deepStrictEqual(values, [lodashStable.endsWith(methodName, 'Right') ? 3 : 1]); + } + }); + + it('`_.' + methodName + '` can exit early when iterating objects', function() { + if (func) { + var object = { 'a': 1, 'b': 2, 'c': 3 }, + values = []; + + func(object, function(value, other) { + values.push(lodashStable.isArray(value) ? other : value); + return false; + }); + + assert.strictEqual(values.length, 1); + } + }); + }); +}); diff --git a/test/extremum-methods.js b/test/extremum-methods.js new file mode 100644 index 000000000..6dad236bd --- /dev/null +++ b/test/extremum-methods.js @@ -0,0 +1,64 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _ } from './utils.js'; + +describe('extremum methods', function() { + lodashStable.each(['max', 'maxBy', 'min', 'minBy'], function(methodName) { + var func = _[methodName], + isMax = /^max/.test(methodName); + + it('`_.' + methodName + '` should work with Date objects', function() { + var curr = new Date, + past = new Date(0); + + assert.strictEqual(func([curr, past]), isMax ? curr : past); + }); + + it('`_.' + methodName + '` should work with extremely large arrays', function() { + var array = lodashStable.range(0, 5e5); + assert.strictEqual(func(array), isMax ? 499999 : 0); + }); + + it('`_.' + methodName + '` should work when chaining on an array with only one value', function() { + var actual = _([40])[methodName](); + assert.strictEqual(actual, 40); + }); + }); + + lodashStable.each(['maxBy', 'minBy'], function(methodName) { + var array = [1, 2, 3], + func = _[methodName], + isMax = methodName == 'maxBy'; + + it('`_.' + methodName + '` should work with an `iteratee`', function() { + var actual = func(array, function(n) { + return -n; + }); + + assert.strictEqual(actual, isMax ? 1 : 3); + }); + + it('should work with `_.property` shorthands', function() { + var objects = [{ 'a': 2 }, { 'a': 3 }, { 'a': 1 }], + actual = func(objects, 'a'); + + assert.deepStrictEqual(actual, objects[isMax ? 1 : 2]); + + var arrays = [[2], [3], [1]]; + actual = func(arrays, 0); + + assert.deepStrictEqual(actual, arrays[isMax ? 1 : 2]); + }); + + it('`_.' + methodName + '` should work when `iteratee` returns +/-Infinity', function() { + var value = isMax ? -Infinity : Infinity, + object = { 'a': value }; + + var actual = func([object, { 'a': value }], function(object) { + return object.a; + }); + + assert.strictEqual(actual, object); + }); + }); +}); diff --git a/test/fill.js b/test/fill.js new file mode 100644 index 000000000..518e368a7 --- /dev/null +++ b/test/fill.js @@ -0,0 +1,128 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey } from './utils.js'; +import fill from '../fill.js'; + +describe('fill', function() { + it('should use a default `start` of `0` and a default `end` of `length`', function() { + var array = [1, 2, 3]; + assert.deepStrictEqual(fill(array, 'a'), ['a', 'a', 'a']); + }); + + it('should use `undefined` for `value` if not given', function() { + var array = [1, 2, 3], + actual = fill(array); + + assert.deepStrictEqual(actual, Array(3)); + assert.ok(lodashStable.every(actual, function(value, index) { + return index in actual; + })); + }); + + it('should work with a positive `start`', function() { + var array = [1, 2, 3]; + assert.deepStrictEqual(fill(array, 'a', 1), [1, 'a', 'a']); + }); + + it('should work with a `start` >= `length`', function() { + lodashStable.each([3, 4, Math.pow(2, 32), Infinity], function(start) { + var array = [1, 2, 3]; + assert.deepStrictEqual(fill(array, 'a', start), [1, 2, 3]); + }); + }); + + it('should treat falsey `start` values as `0`', function() { + var expected = lodashStable.map(falsey, lodashStable.constant(['a', 'a', 'a'])); + + var actual = lodashStable.map(falsey, function(start) { + var array = [1, 2, 3]; + return fill(array, 'a', start); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with a negative `start`', function() { + var array = [1, 2, 3]; + assert.deepStrictEqual(fill(array, 'a', -1), [1, 2, 'a']); + }); + + it('should work with a negative `start` <= negative `length`', function() { + lodashStable.each([-3, -4, -Infinity], function(start) { + var array = [1, 2, 3]; + assert.deepStrictEqual(fill(array, 'a', start), ['a', 'a', 'a']); + }); + }); + + it('should work with `start` >= `end`', function() { + lodashStable.each([2, 3], function(start) { + var array = [1, 2, 3]; + assert.deepStrictEqual(fill(array, 'a', start, 2), [1, 2, 3]); + }); + }); + + it('should work with a positive `end`', function() { + var array = [1, 2, 3]; + assert.deepStrictEqual(fill(array, 'a', 0, 1), ['a', 2, 3]); + }); + + it('should work with a `end` >= `length`', function() { + lodashStable.each([3, 4, Math.pow(2, 32), Infinity], function(end) { + var array = [1, 2, 3]; + assert.deepStrictEqual(fill(array, 'a', 0, end), ['a', 'a', 'a']); + }); + }); + + it('should treat falsey `end` values, except `undefined`, as `0`', function() { + var expected = lodashStable.map(falsey, function(value) { + return value === undefined ? ['a', 'a', 'a'] : [1, 2, 3]; + }); + + var actual = lodashStable.map(falsey, function(end) { + var array = [1, 2, 3]; + return fill(array, 'a', 0, end); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with a negative `end`', function() { + var array = [1, 2, 3]; + assert.deepStrictEqual(fill(array, 'a', 0, -1), ['a', 'a', 3]); + }); + + it('should work with a negative `end` <= negative `length`', function() { + lodashStable.each([-3, -4, -Infinity], function(end) { + var array = [1, 2, 3]; + assert.deepStrictEqual(fill(array, 'a', 0, end), [1, 2, 3]); + }); + }); + + it('should coerce `start` and `end` to integers', function() { + var positions = [[0.1, 1.6], ['0', 1], [0, '1'], ['1'], [NaN, 1], [1, NaN]]; + + var actual = lodashStable.map(positions, function(pos) { + var array = [1, 2, 3]; + return fill.apply(_, [array, 'a'].concat(pos)); + }); + + assert.deepStrictEqual(actual, [['a', 2, 3], ['a', 2, 3], ['a', 2, 3], [1, 'a', 'a'], ['a', 2, 3], [1, 2, 3]]); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var array = [[1, 2], [3, 4]], + actual = lodashStable.map(array, fill); + + assert.deepStrictEqual(actual, [[0, 0], [1, 1]]); + }); + + it('should return a wrapped value when chaining', function() { + var array = [1, 2, 3], + wrapped = _(array).fill('a'), + actual = wrapped.value(); + + assert.ok(wrapped instanceof _); + assert.strictEqual(actual, array); + assert.deepEqual(actual, ['a', 'a', 'a']); + }); +}); diff --git a/test/filter-methods.js b/test/filter-methods.js new file mode 100644 index 000000000..2e98afc09 --- /dev/null +++ b/test/filter-methods.js @@ -0,0 +1,100 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, LARGE_ARRAY_SIZE, isEven, square } from './utils.js'; + +describe('filter methods', function() { + lodashStable.each(['filter', 'reject'], function(methodName) { + var array = [1, 2, 3, 4], + func = _[methodName], + isFilter = methodName == 'filter', + objects = [{ 'a': 0 }, { 'a': 1 }]; + + it('`_.' + methodName + '` should not modify the resulting value from within `predicate`', function() { + var actual = func([0], function(value, index, array) { + array[index] = 1; + return isFilter; + }); + + assert.deepStrictEqual(actual, [0]); + }); + + it('`_.' + methodName + '` should work with `_.property` shorthands', function() { + assert.deepStrictEqual(func(objects, 'a'), [objects[isFilter ? 1 : 0]]); + }); + + it('`_.' + methodName + '` should work with `_.matches` shorthands', function() { + assert.deepStrictEqual(func(objects, objects[1]), [objects[isFilter ? 1 : 0]]); + }); + + it('`_.' + methodName + '` should not modify wrapped values', function() { + var wrapped = _(array); + + var actual = wrapped[methodName](function(n) { + return n < 3; + }); + + assert.deepEqual(actual.value(), isFilter ? [1, 2] : [3, 4]); + + actual = wrapped[methodName](function(n) { + return n > 2; + }); + + assert.deepEqual(actual.value(), isFilter ? [3, 4] : [1, 2]); + }); + + it('`_.' + methodName + '` should work in a lazy sequence', function() { + var array = lodashStable.range(LARGE_ARRAY_SIZE + 1), + predicate = function(value) { return isFilter ? isEven(value) : !isEven(value); }; + + var object = lodashStable.zipObject(lodashStable.times(LARGE_ARRAY_SIZE, function(index) { + return ['key' + index, index]; + })); + + var actual = _(array).slice(1).map(square)[methodName](predicate).value(); + assert.deepEqual(actual, _[methodName](lodashStable.map(array.slice(1), square), predicate)); + + actual = _(object).mapValues(square)[methodName](predicate).value(); + assert.deepEqual(actual, _[methodName](lodashStable.mapValues(object, square), predicate)); + }); + + it('`_.' + methodName + '` should provide correct `predicate` arguments in a lazy sequence', function() { + var args, + array = lodashStable.range(LARGE_ARRAY_SIZE + 1), + expected = [1, 0, lodashStable.map(array.slice(1), square)]; + + _(array).slice(1)[methodName](function(value, index, array) { + args || (args = slice.call(arguments)); + }).value(); + + assert.deepEqual(args, [1, 0, array.slice(1)]); + + args = undefined; + _(array).slice(1).map(square)[methodName](function(value, index, array) { + args || (args = slice.call(arguments)); + }).value(); + + assert.deepEqual(args, expected); + + args = undefined; + _(array).slice(1).map(square)[methodName](function(value, index) { + args || (args = slice.call(arguments)); + }).value(); + + assert.deepEqual(args, expected); + + args = undefined; + _(array).slice(1).map(square)[methodName](function(value) { + args || (args = slice.call(arguments)); + }).value(); + + assert.deepEqual(args, [1]); + + args = undefined; + _(array).slice(1).map(square)[methodName](function() { + args || (args = slice.call(arguments)); + }).value(); + + assert.deepEqual(args, expected); + }); + }); +}); diff --git a/test/filter.test.js b/test/filter.test.js new file mode 100644 index 000000000..cff7d2555 --- /dev/null +++ b/test/filter.test.js @@ -0,0 +1,11 @@ +import assert from 'assert'; +import { isEven } from './utils.js'; +import filter from '../filter.js'; + +describe('filter', function() { + var array = [1, 2, 3]; + + it('should return elements `predicate` returns truthy for', function() { + assert.deepStrictEqual(filter(array, isEven), [2]); + }); +}); diff --git a/test/find-and-findLast.js b/test/find-and-findLast.js new file mode 100644 index 000000000..e55725e97 --- /dev/null +++ b/test/find-and-findLast.js @@ -0,0 +1,22 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { LARGE_ARRAY_SIZE, square, isEven } from './utils.js'; + +describe('find and findLast', function() { + lodashStable.each(['find', 'findLast'], function(methodName) { + var isFind = methodName == 'find'; + + it('`_.' + methodName + '` should support shortcut fusion', function() { + var findCount = 0, + mapCount = 0, + array = lodashStable.range(1, LARGE_ARRAY_SIZE + 1), + iteratee = function(value) { mapCount++; return square(value); }, + predicate = function(value) { findCount++; return isEven(value); }, + actual = _(array).map(iteratee)[methodName](predicate); + + assert.strictEqual(findCount, isFind ? 2 : 1); + assert.strictEqual(mapCount, isFind ? 2 : 1); + assert.strictEqual(actual, isFind ? 4 : square(LARGE_ARRAY_SIZE)); + }); + }); +}); diff --git a/test/find-and-includes.js b/test/find-and-includes.js new file mode 100644 index 000000000..b6978b13b --- /dev/null +++ b/test/find-and-includes.js @@ -0,0 +1,103 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, identity, args, falsey } from './utils.js'; + +describe('find and includes', function() { + lodashStable.each(['includes', 'find'], function(methodName) { + var func = _[methodName], + isIncludes = methodName == 'includes', + resolve = methodName == 'find' ? lodashStable.curry(lodashStable.eq) : identity; + + lodashStable.each({ + 'an `arguments` object': args, + 'an array': [1, 2, 3] + }, + function(collection, key) { + var values = lodashStable.toArray(collection); + + it('`_.' + methodName + '` should work with ' + key + ' and a positive `fromIndex`', function() { + var expected = [ + isIncludes || values[2], + isIncludes ? false : undefined + ]; + + var actual = [ + func(collection, resolve(values[2]), 2), + func(collection, resolve(values[1]), 2) + ]; + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should work with ' + key + ' and a `fromIndex` >= `length`', function() { + var indexes = [4, 6, Math.pow(2, 32), Infinity]; + + var expected = lodashStable.map(indexes, function() { + var result = isIncludes ? false : undefined; + return [result, result, result]; + }); + + var actual = lodashStable.map(indexes, function(fromIndex) { + return [ + func(collection, resolve(1), fromIndex), + func(collection, resolve(undefined), fromIndex), + func(collection, resolve(''), fromIndex) + ]; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should work with ' + key + ' and treat falsey `fromIndex` values as `0`', function() { + var expected = lodashStable.map(falsey, lodashStable.constant(isIncludes || values[0])); + + var actual = lodashStable.map(falsey, function(fromIndex) { + return func(collection, resolve(values[0]), fromIndex); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should work with ' + key + ' and coerce `fromIndex` to an integer', function() { + var expected = [ + isIncludes || values[0], + isIncludes || values[0], + isIncludes ? false : undefined + ]; + + var actual = [ + func(collection, resolve(values[0]), 0.1), + func(collection, resolve(values[0]), NaN), + func(collection, resolve(values[0]), '1') + ]; + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should work with ' + key + ' and a negative `fromIndex`', function() { + var expected = [ + isIncludes || values[2], + isIncludes ? false : undefined + ]; + + var actual = [ + func(collection, resolve(values[2]), -1), + func(collection, resolve(values[1]), -1) + ]; + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should work with ' + key + ' and a negative `fromIndex` <= `-length`', function() { + var indexes = [-4, -6, -Infinity], + expected = lodashStable.map(indexes, lodashStable.constant(isIncludes || values[0])); + + var actual = lodashStable.map(indexes, function(fromIndex) { + return func(collection, resolve(values[0]), fromIndex); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + }); +}); diff --git a/test/find-methods.js b/test/find-methods.js new file mode 100644 index 000000000..8a0c24767 --- /dev/null +++ b/test/find-methods.js @@ -0,0 +1,139 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, empties, LARGE_ARRAY_SIZE, slice } from './utils.js'; +import each from '../each.js'; + +describe('find methods', function() { + lodashStable.each(['find', 'findIndex', 'findKey', 'findLast', 'findLastIndex', 'findLastKey'], function(methodName) { + var array = [1, 2, 3, 4], + func = _[methodName]; + + var objects = [ + { 'a': 0, 'b': 0 }, + { 'a': 1, 'b': 1 }, + { 'a': 2, 'b': 2 } + ]; + + var expected = ({ + 'find': [objects[1], undefined, objects[2]], + 'findIndex': [1, -1, 2], + 'findKey': ['1', undefined, '2'], + 'findLast': [objects[2], undefined, objects[2]], + 'findLastIndex': [2, -1, 2], + 'findLastKey': ['2', undefined, '2'] + })[methodName]; + + it('`_.' + methodName + '` should return the found value', function() { + assert.strictEqual(func(objects, function(object) { return object.a; }), expected[0]); + }); + + it('`_.' + methodName + '` should return `' + expected[1] + '` if value is not found', function() { + assert.strictEqual(func(objects, function(object) { return object.a === 3; }), expected[1]); + }); + + it('`_.' + methodName + '` should work with `_.matches` shorthands', function() { + assert.strictEqual(func(objects, { 'b': 2 }), expected[2]); + }); + + it('`_.' + methodName + '` should work with `_.matchesProperty` shorthands', function() { + assert.strictEqual(func(objects, ['b', 2]), expected[2]); + }); + + it('`_.' + methodName + '` should work with `_.property` shorthands', function() { + assert.strictEqual(func(objects, 'b'), expected[0]); + }); + + it('`_.' + methodName + '` should return `' + expected[1] + '` for empty collections', function() { + var emptyValues = lodashStable.endsWith(methodName, 'Index') ? lodashStable.reject(empties, lodashStable.isPlainObject) : empties, + expecting = lodashStable.map(emptyValues, lodashStable.constant(expected[1])); + + var actual = lodashStable.map(emptyValues, function(value) { + try { + return func(value, { 'a': 3 }); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expecting); + }); + + it('`_.' + methodName + '` should return an unwrapped value when implicitly chaining', function() { + var expected = ({ + 'find': 1, + 'findIndex': 0, + 'findKey': '0', + 'findLast': 4, + 'findLastIndex': 3, + 'findLastKey': '3' + })[methodName]; + + assert.strictEqual(_(array)[methodName](), expected); + }); + + it('`_.' + methodName + '` should return a wrapped value when explicitly chaining', function() { + assert.ok(_(array).chain()[methodName]() instanceof _); + }); + + it('`_.' + methodName + '` should not execute immediately when explicitly chaining', function() { + var wrapped = _(array).chain()[methodName](); + assert.strictEqual(wrapped.__wrapped__, array); + }); + + it('`_.' + methodName + '` should work in a lazy sequence', function() { + var largeArray = lodashStable.range(1, LARGE_ARRAY_SIZE + 1), + smallArray = array; + + lodashStable.times(2, function(index) { + var array = index ? largeArray : smallArray, + wrapped = _(array).filter(isEven); + + assert.strictEqual(wrapped[methodName](), func(lodashStable.filter(array, isEven))); + }); + }); + }), + function() { + each(['find', 'findIndex', 'findLast', 'findLastIndex'], function(methodName) { + var func = _[methodName]; + + it('`_.' + methodName + '` should provide correct `predicate` arguments for arrays', function() { + var args, + array = ['a']; + + func(array, function() { + args || (args = slice.call(arguments)); + }); + + assert.deepStrictEqual(args, ['a', 0, array]); + }); + }); + + each(['find', 'findKey', 'findLast', 'findLastKey'], function(methodName) { + var func = _[methodName]; + + it('`_.' + methodName + '` should work with an object for `collection`', function() { + var actual = func({ 'a': 1, 'b': 2, 'c': 3 }, function(n) { + return n < 3; + }); + + var expected = ({ + 'find': 1, + 'findKey': 'a', + 'findLast': 2, + 'findLastKey': 'b' + })[methodName]; + + assert.strictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should provide correct `predicate` arguments for objects', function() { + var args, + object = { 'a': 1 }; + + func(object, function() { + args || (args = slice.call(arguments)); + }); + + assert.deepStrictEqual(args, [1, 'a', object]); + }); + }); + } +}); diff --git a/test/findIndex-and-indexOf.js b/test/findIndex-and-indexOf.js new file mode 100644 index 000000000..217e5503d --- /dev/null +++ b/test/findIndex-and-indexOf.js @@ -0,0 +1,63 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, identity, stubZero, falsey } from './utils.js'; + +describe('findIndex and indexOf', function() { + lodashStable.each(['findIndex', 'indexOf'], function(methodName) { + var array = [1, 2, 3, 1, 2, 3], + func = _[methodName], + resolve = methodName == 'findIndex' ? lodashStable.curry(lodashStable.eq) : identity; + + it('`_.' + methodName + '` should return the index of the first matched value', function() { + assert.strictEqual(func(array, resolve(3)), 2); + }); + + it('`_.' + methodName + '` should work with a positive `fromIndex`', function() { + assert.strictEqual(func(array, resolve(1), 2), 3); + }); + + it('`_.' + methodName + '` should work with a `fromIndex` >= `length`', function() { + var values = [6, 8, Math.pow(2, 32), Infinity], + expected = lodashStable.map(values, lodashStable.constant([-1, -1, -1])); + + var actual = lodashStable.map(values, function(fromIndex) { + return [ + func(array, resolve(undefined), fromIndex), + func(array, resolve(1), fromIndex), + func(array, resolve(''), fromIndex) + ]; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should work with a negative `fromIndex`', function() { + assert.strictEqual(func(array, resolve(2), -3), 4); + }); + + it('`_.' + methodName + '` should work with a negative `fromIndex` <= `-length`', function() { + var values = [-6, -8, -Infinity], + expected = lodashStable.map(values, stubZero); + + var actual = lodashStable.map(values, function(fromIndex) { + return func(array, resolve(1), fromIndex); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should treat falsey `fromIndex` values as `0`', function() { + var expected = lodashStable.map(falsey, stubZero); + + var actual = lodashStable.map(falsey, function(fromIndex) { + return func(array, resolve(1), fromIndex); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should coerce `fromIndex` to an integer', function() { + assert.strictEqual(func(array, resolve(2), 1.2), 1); + }); + }); +}); diff --git a/test/findLast.js b/test/findLast.js new file mode 100644 index 000000000..b303b55f5 --- /dev/null +++ b/test/findLast.js @@ -0,0 +1,99 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { args, falsey } from './utils.js'; +import findLast from '../findLast.js'; + +describe('findLast', function() { + var resolve = lodashStable.curry(lodashStable.eq); + + lodashStable.each({ + 'an `arguments` object': args, + 'an array': [1, 2, 3] + }, + function(collection, key) { + var values = lodashStable.toArray(collection); + + it('should work with ' + key + ' and a positive `fromIndex`', function() { + var expected = [ + values[1], + undefined + ]; + + var actual = [ + findLast(collection, resolve(values[1]), 1), + findLast(collection, resolve(values[2]), 1) + ]; + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with ' + key + ' and a `fromIndex` >= `length`', function() { + var indexes = [4, 6, Math.pow(2, 32), Infinity]; + + var expected = lodashStable.map(indexes, lodashStable.constant([values[0], undefined, undefined])); + + var actual = lodashStable.map(indexes, function(fromIndex) { + return [ + findLast(collection, resolve(1), fromIndex), + findLast(collection, resolve(undefined), fromIndex), + findLast(collection, resolve(''), fromIndex) + ]; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with ' + key + ' and treat falsey `fromIndex` values correctly', function() { + var expected = lodashStable.map(falsey, function(value) { + return value === undefined ? values[3] : undefined; + }); + + var actual = lodashStable.map(falsey, function(fromIndex) { + return findLast(collection, resolve(values[3]), fromIndex); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with ' + key + ' and coerce `fromIndex` to an integer', function() { + var expected = [ + values[0], + values[0], + undefined + ]; + + var actual = [ + findLast(collection, resolve(values[0]), 0.1), + findLast(collection, resolve(values[0]), NaN), + findLast(collection, resolve(values[2]), '1') + ]; + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with ' + key + ' and a negative `fromIndex`', function() { + var expected = [ + values[1], + undefined + ]; + + var actual = [ + findLast(collection, resolve(values[1]), -2), + findLast(collection, resolve(values[2]), -2) + ]; + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with ' + key + ' and a negative `fromIndex` <= `-length`', function() { + var indexes = [-4, -6, -Infinity], + expected = lodashStable.map(indexes, lodashStable.constant(values[0])); + + var actual = lodashStable.map(indexes, function(fromIndex) { + return findLast(collection, resolve(values[0]), fromIndex); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); +}); diff --git a/test/findLastIndex-and-lastIndexOf.js b/test/findLastIndex-and-lastIndexOf.js new file mode 100644 index 000000000..44744bb99 --- /dev/null +++ b/test/findLastIndex-and-lastIndexOf.js @@ -0,0 +1,65 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, identity, stubZero, falsey } from './utils.js'; + +describe('findLastIndex and lastIndexOf', function() { + lodashStable.each(['findLastIndex', 'lastIndexOf'], function(methodName) { + var array = [1, 2, 3, 1, 2, 3], + func = _[methodName], + resolve = methodName == 'findLastIndex' ? lodashStable.curry(lodashStable.eq) : identity; + + it('`_.' + methodName + '` should return the index of the last matched value', function() { + assert.strictEqual(func(array, resolve(3)), 5); + }); + + it('`_.' + methodName + '` should work with a positive `fromIndex`', function() { + assert.strictEqual(func(array, resolve(1), 2), 0); + }); + + it('`_.' + methodName + '` should work with a `fromIndex` >= `length`', function() { + var values = [6, 8, Math.pow(2, 32), Infinity], + expected = lodashStable.map(values, lodashStable.constant([-1, 3, -1])); + + var actual = lodashStable.map(values, function(fromIndex) { + return [ + func(array, resolve(undefined), fromIndex), + func(array, resolve(1), fromIndex), + func(array, resolve(''), fromIndex) + ]; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should work with a negative `fromIndex`', function() { + assert.strictEqual(func(array, resolve(2), -3), 1); + }); + + it('`_.' + methodName + '` should work with a negative `fromIndex` <= `-length`', function() { + var values = [-6, -8, -Infinity], + expected = lodashStable.map(values, stubZero); + + var actual = lodashStable.map(values, function(fromIndex) { + return func(array, resolve(1), fromIndex); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should treat falsey `fromIndex` values correctly', function() { + var expected = lodashStable.map(falsey, function(value) { + return value === undefined ? 5 : -1; + }); + + var actual = lodashStable.map(falsey, function(fromIndex) { + return func(array, resolve(3), fromIndex); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should coerce `fromIndex` to an integer', function() { + assert.strictEqual(func(array, resolve(2), 4.2), 4); + }); + }); +}); diff --git a/test/flatMap-methods.js b/test/flatMap-methods.js new file mode 100644 index 000000000..4508e0a2b --- /dev/null +++ b/test/flatMap-methods.js @@ -0,0 +1,72 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, identity, falsey, stubArray } from './utils.js'; + +describe('flatMap methods', function() { + lodashStable.each(['flatMap', 'flatMapDeep', 'flatMapDepth'], function(methodName) { + var func = _[methodName], + array = [1, 2, 3, 4]; + + function duplicate(n) { + return [n, n]; + } + + it('`_.' + methodName + '` should map values in `array` to a new flattened array', function() { + var actual = func(array, duplicate), + expected = lodashStable.flatten(lodashStable.map(array, duplicate)); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should work with `_.property` shorthands', function() { + var objects = [{ 'a': [1, 2] }, { 'a': [3, 4] }]; + assert.deepStrictEqual(func(objects, 'a'), array); + }); + + it('`_.' + methodName + '` should iterate over own string keyed properties of objects', function() { + function Foo() { + this.a = [1, 2]; + } + Foo.prototype.b = [3, 4]; + + var actual = func(new Foo, identity); + assert.deepStrictEqual(actual, [1, 2]); + }); + + it('`_.' + methodName + '` should use `_.identity` when `iteratee` is nullish', function() { + var array = [[1, 2], [3, 4]], + object = { 'a': [1, 2], 'b': [3, 4] }, + values = [, null, undefined], + expected = lodashStable.map(values, lodashStable.constant([1, 2, 3, 4])); + + lodashStable.each([array, object], function(collection) { + var actual = lodashStable.map(values, function(value, index) { + return index ? func(collection, value) : func(collection); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('`_.' + methodName + '` should accept a falsey `collection`', function() { + var expected = lodashStable.map(falsey, stubArray); + + var actual = lodashStable.map(falsey, function(collection, index) { + try { + return index ? func(collection) : func(); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should treat number values for `collection` as empty', function() { + assert.deepStrictEqual(func(1), []); + }); + + it('`_.' + methodName + '` should work with objects with non-number length properties', function() { + var object = { 'length': [1, 2] }; + assert.deepStrictEqual(func(object, identity), [1, 2]); + }); + }); +}); diff --git a/test/flatMapDepth.js b/test/flatMapDepth.js new file mode 100644 index 000000000..365ce8fcb --- /dev/null +++ b/test/flatMapDepth.js @@ -0,0 +1,33 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { identity } from './utils.js'; +import flatMapDepth from '../flatMapDepth.js'; + +describe('flatMapDepth', function() { + var array = [1, [2, [3, [4]], 5]]; + + it('should use a default `depth` of `1`', function() { + assert.deepStrictEqual(flatMapDepth(array, identity), [1, 2, [3, [4]], 5]); + }); + + it('should use `_.identity` when `iteratee` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, lodashStable.constant([1, 2, [3, [4]], 5])); + + var actual = lodashStable.map(values, function(value, index) { + return index ? flatMapDepth(array, value) : flatMapDepth(array); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should treat a `depth` of < `1` as a shallow clone', function() { + lodashStable.each([-1, 0], function(depth) { + assert.deepStrictEqual(flatMapDepth(array, identity, depth), [1, [2, [3, [4]], 5]]); + }); + }); + + it('should coerce `depth` to an integer', function() { + assert.deepStrictEqual(flatMapDepth(array, identity, 2.2), [1, 2, 3, [4], 5]); + }); +}); diff --git a/test/flatten-methods.js b/test/flatten-methods.js new file mode 100644 index 000000000..63b01e8c4 --- /dev/null +++ b/test/flatten-methods.js @@ -0,0 +1,106 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { args, _ } from './utils.js'; +import flatten from '../flatten.js'; +import flattenDeep from '../flattenDeep.js'; +import flattenDepth from '../flattenDepth.js'; + +describe('flatten methods', function() { + var array = [1, [2, [3, [4]], 5]], + methodNames = ['flatten', 'flattenDeep', 'flattenDepth']; + + it('should flatten `arguments` objects', function() { + var array = [args, [args]]; + + assert.deepStrictEqual(flatten(array), [1, 2, 3, args]); + assert.deepStrictEqual(flattenDeep(array), [1, 2, 3, 1, 2, 3]); + assert.deepStrictEqual(flattenDepth(array, 2), [1, 2, 3, 1, 2, 3]); + }); + + it('should treat sparse arrays as dense', function() { + var array = [[1, 2, 3], Array(3)], + expected = [1, 2, 3]; + + expected.push(undefined, undefined, undefined); + + lodashStable.each(methodNames, function(methodName) { + var actual = _[methodName](array); + assert.deepStrictEqual(actual, expected); + assert.ok('4' in actual); + }); + }); + + it('should flatten objects with a truthy `Symbol.isConcatSpreadable` value', function() { + if (Symbol && Symbol.isConcatSpreadable) { + var object = { '0': 'a', 'length': 1 }, + array = [object], + expected = lodashStable.map(methodNames, lodashStable.constant(['a'])); + + object[Symbol.isConcatSpreadable] = true; + + var actual = lodashStable.map(methodNames, function(methodName) { + return _[methodName](array); + }); + + assert.deepStrictEqual(actual, expected); + } + }); + + it('should work with extremely large arrays', function() { + lodashStable.times(3, function(index) { + var expected = Array(5e5); + try { + var func = flatten; + if (index == 1) { + func = flattenDeep; + } else if (index == 2) { + func = flattenDepth; + } + assert.deepStrictEqual(func([expected]), expected); + } catch (e) { + assert.ok(false, e.message); + } + }); + }); + + it('should work with empty arrays', function() { + var array = [[], [[]], [[], [[[]]]]]; + + assert.deepStrictEqual(flatten(array), [[], [], [[[]]]]); + assert.deepStrictEqual(flattenDeep(array), []); + assert.deepStrictEqual(flattenDepth(array, 2), [[[]]]); + }); + + it('should support flattening of nested arrays', function() { + assert.deepStrictEqual(flatten(array), [1, 2, [3, [4]], 5]); + assert.deepStrictEqual(flattenDeep(array), [1, 2, 3, 4, 5]); + assert.deepStrictEqual(flattenDepth(array, 2), [1, 2, 3, [4], 5]); + }); + + it('should return an empty array for non array-like objects', function() { + var expected = [], + nonArray = { '0': 'a' }; + + assert.deepStrictEqual(flatten(nonArray), expected); + assert.deepStrictEqual(flattenDeep(nonArray), expected); + assert.deepStrictEqual(flattenDepth(nonArray, 2), expected); + }); + + it('should return a wrapped value when chaining', function() { + var wrapped = _(array), + actual = wrapped.flatten(); + + assert.ok(actual instanceof _); + assert.deepEqual(actual.value(), [1, 2, [3, [4]], 5]); + + actual = wrapped.flattenDeep(); + + assert.ok(actual instanceof _); + assert.deepEqual(actual.value(), [1, 2, 3, 4, 5]); + + actual = wrapped.flattenDepth(2); + + assert.ok(actual instanceof _); + assert.deepEqual(actual.value(), [1, 2, 3, [4], 5]); + }); +}); diff --git a/test/flattenDepth.js b/test/flattenDepth.js new file mode 100644 index 000000000..6b6374e8d --- /dev/null +++ b/test/flattenDepth.js @@ -0,0 +1,21 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import flattenDepth from '../flattenDepth.js'; + +describe('flattenDepth', function() { + var array = [1, [2, [3, [4]], 5]]; + + it('should use a default `depth` of `1`', function() { + assert.deepStrictEqual(flattenDepth(array), [1, 2, [3, [4]], 5]); + }); + + it('should treat a `depth` of < `1` as a shallow clone', function() { + lodashStable.each([-1, 0], function(depth) { + assert.deepStrictEqual(flattenDepth(array, depth), [1, [2, [3, [4]], 5]]); + }); + }); + + it('should coerce `depth` to an integer', function() { + assert.deepStrictEqual(flattenDepth(array, 2.2), [1, 2, 3, [4], 5]); + }); +}); diff --git a/test/flip.test.js b/test/flip.test.js new file mode 100644 index 000000000..c8423ece4 --- /dev/null +++ b/test/flip.test.js @@ -0,0 +1,14 @@ +import assert from 'assert'; +import { slice } from './utils.js'; +import flip from '../flip.js'; + +describe('flip', function() { + function fn() { + return slice.call(arguments); + } + + it('should flip arguments provided to `func`', function() { + var flipped = flip(fn); + assert.deepStrictEqual(flipped('a', 'b', 'c', 'd'), ['d', 'c', 'b', 'a']); + }); +}); diff --git a/test/flow-methods.js b/test/flow-methods.js new file mode 100644 index 000000000..916a4e396 --- /dev/null +++ b/test/flow-methods.js @@ -0,0 +1,108 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, add, square, noop, identity, LARGE_ARRAY_SIZE, isEven, isNpm } from './utils.js'; +import times from '../times.js'; +import curry from '../curry.js'; +import head from '../head.js'; +import filter from '../filter.js'; +import rearg from '../rearg.js'; +import ary from '../ary.js'; +import map from '../map.js'; +import take from '../take.js'; +import compact from '../compact.js'; +import uniq from '../uniq.js'; + +describe('flow methods', function() { + lodashStable.each(['flow', 'flowRight'], function(methodName) { + var func = _[methodName], + isFlow = methodName == 'flow'; + + it('`_.' + methodName + '` should supply each function with the return value of the previous', function() { + var fixed = function(n) { return n.toFixed(1); }, + combined = isFlow ? func(add, square, fixed) : func(fixed, square, add); + + assert.strictEqual(combined(1, 2), '9.0'); + }); + + it('`_.' + methodName + '` should return a new function', function() { + assert.notStrictEqual(func(noop), noop); + }); + + it('`_.' + methodName + '` should return an identity function when no arguments are given', function() { + times(2, function(index) { + try { + var combined = index ? func([]) : func(); + assert.strictEqual(combined('a'), 'a'); + } catch (e) { + assert.ok(false, e.message); + } + assert.strictEqual(combined.length, 0); + assert.notStrictEqual(combined, identity); + }); + }); + + it('`_.' + methodName + '` should work with a curried function and `_.head`', function() { + var curried = curry(identity); + + var combined = isFlow + ? func(head, curried) + : func(curried, head); + + assert.strictEqual(combined([1]), 1); + }); + + it('`_.' + methodName + '` should support shortcut fusion', function() { + var filterCount, + mapCount, + array = lodashStable.range(LARGE_ARRAY_SIZE), + iteratee = function(value) { mapCount++; return square(value); }, + predicate = function(value) { filterCount++; return isEven(value); }; + + lodashStable.times(2, function(index) { + var filter1 = filter, + filter2 = curry(rearg(ary(filter, 2), 1, 0), 2), + filter3 = (filter = index ? filter2 : filter1, filter2(predicate)); + + var map1 = map, + map2 = curry(rearg(ary(map, 2), 1, 0), 2), + map3 = (map = index ? map2 : map1, map2(iteratee)); + + var take1 = take, + take2 = curry(rearg(ary(take, 2), 1, 0), 2), + take3 = (take = index ? take2 : take1, take2(2)); + + var combined = isFlow + ? func(map3, filter3, compact, take3) + : func(take3, compact, filter3, map3); + + filterCount = mapCount = 0; + assert.deepStrictEqual(combined(array), [4, 16]); + + if (!isNpm && WeakMap && WeakMap.name) { + assert.strictEqual(filterCount, 5, 'filterCount'); + assert.strictEqual(mapCount, 5, 'mapCount'); + } + filter = filter1; + map = map1; + take = take1; + }); + }); + + it('`_.' + methodName + '` should work with curried functions with placeholders', function() { + var curried = curry(ary(map, 2), 2), + getProp = curried(curried.placeholder, 'a'), + objects = [{ 'a': 1 }, { 'a': 2 }, { 'a': 1 }]; + + var combined = isFlow + ? func(getProp, uniq) + : func(uniq, getProp); + + assert.deepStrictEqual(combined(objects), [1, 2]); + }); + + it('`_.' + methodName + '` should return a wrapped value when chaining', function() { + var wrapped = _(noop)[methodName](); + assert.ok(wrapped instanceof _); + }); + }); +}); diff --git a/test/forEach.test.js b/test/forEach.test.js new file mode 100644 index 000000000..8770ffdf9 --- /dev/null +++ b/test/forEach.test.js @@ -0,0 +1,9 @@ +import assert from 'assert'; +import each from '../each.js'; +import forEach from '../forEach.js'; + +describe('forEach', function() { + it('should be aliased', function() { + assert.strictEqual(each, forEach); + }); +}); diff --git a/test/forEachRight.test.js b/test/forEachRight.test.js new file mode 100644 index 000000000..29494d9a4 --- /dev/null +++ b/test/forEachRight.test.js @@ -0,0 +1,9 @@ +import assert from 'assert'; +import eachRight from '../eachRight.js'; +import forEachRight from '../forEachRight.js'; + +describe('forEachRight', function() { + it('should be aliased', function() { + assert.strictEqual(eachRight, forEachRight); + }); +}); diff --git a/test/forIn-methods.js b/test/forIn-methods.js new file mode 100644 index 000000000..f806201db --- /dev/null +++ b/test/forIn-methods.js @@ -0,0 +1,20 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _ } from './utils.js'; + +describe('forIn methods', function() { + lodashStable.each(['forIn', 'forInRight'], function(methodName) { + var func = _[methodName]; + + it('`_.' + methodName + '` iterates over inherited string keyed properties', function() { + function Foo() { + this.a = 1; + } + Foo.prototype.b = 2; + + var keys = []; + func(new Foo, function(value, key) { keys.push(key); }); + assert.deepStrictEqual(keys.sort(), ['a', 'b']); + }); + }); +}); diff --git a/test/forOwn-methods.js b/test/forOwn-methods.js new file mode 100644 index 000000000..a8a4fbbef --- /dev/null +++ b/test/forOwn-methods.js @@ -0,0 +1,17 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _ } from './utils.js'; + +describe('forOwn methods', function() { + lodashStable.each(['forOwn', 'forOwnRight'], function(methodName) { + var func = _[methodName]; + + it('`_.' + methodName + '` should iterate over `length` properties', function() { + var object = { '0': 'zero', '1': 'one', 'length': 2 }, + props = []; + + func(object, function(value, prop) { props.push(prop); }); + assert.deepStrictEqual(props.sort(), ['0', '1', 'length']); + }); + }); +}); diff --git a/test/fromPairs.js b/test/fromPairs.js new file mode 100644 index 000000000..5e94ad131 --- /dev/null +++ b/test/fromPairs.js @@ -0,0 +1,47 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubObject, LARGE_ARRAY_SIZE } from './utils.js'; +import fromPairs from '../fromPairs.js'; +import toPairs from '../toPairs.js'; + +describe('fromPairs', function() { + it('should accept a two dimensional array', function() { + var array = [['a', 1], ['b', 2]], + object = { 'a': 1, 'b': 2 }, + actual = fromPairs(array); + + assert.deepStrictEqual(actual, object); + }); + + it('should accept a falsey `array`', function() { + var expected = lodashStable.map(falsey, stubObject); + + var actual = lodashStable.map(falsey, function(array, index) { + try { + return index ? fromPairs(array) : fromPairs(); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should not support deep paths', function() { + var actual = fromPairs([['a.b', 1]]); + assert.deepStrictEqual(actual, { 'a.b': 1 }); + }); + + it('should support consuming the return value of `_.toPairs`', function() { + var object = { 'a.b': 1 }; + assert.deepStrictEqual(fromPairs(toPairs(object)), object); + }); + + it('should work in a lazy sequence', function() { + var array = lodashStable.times(LARGE_ARRAY_SIZE, function(index) { + return ['key' + index, index]; + }); + + var actual = _(array).fromPairs().map(square).filter(isEven).take().value(); + + assert.deepEqual(actual, _.take(_.filter(_.map(fromPairs(array), square), isEven))); + }); +}); diff --git a/test/functions.test.js b/test/functions.test.js new file mode 100644 index 000000000..1d1daeeb1 --- /dev/null +++ b/test/functions.test.js @@ -0,0 +1,22 @@ +import assert from 'assert'; +import { identity, noop } from './utils.js'; +import functions from '../functions.js'; + +describe('functions', function() { + it('should return the function names of an object', function() { + var object = { 'a': 'a', 'b': identity, 'c': /x/, 'd': noop }, + actual = functions(object).sort(); + + assert.deepStrictEqual(actual, ['b', 'd']); + }); + + it('should not include inherited functions', function() { + function Foo() { + this.a = identity; + this.b = 'b'; + } + Foo.prototype.c = noop; + + assert.deepStrictEqual(functions(new Foo), ['a']); + }); +}); diff --git a/test/get-and-result.js b/test/get-and-result.js new file mode 100644 index 000000000..194f40f68 --- /dev/null +++ b/test/get-and-result.js @@ -0,0 +1,148 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, symbol, noop, numberProto, empties } from './utils.js'; + +describe('get and result', function() { + lodashStable.each(['get', 'result'], function(methodName) { + var func = _[methodName]; + + it('`_.' + methodName + '` should get string keyed property values', function() { + var object = { 'a': 1 }; + + lodashStable.each(['a', ['a']], function(path) { + assert.strictEqual(func(object, path), 1); + }); + }); + + it('`_.' + methodName + '` should preserve the sign of `0`', function() { + var object = { '-0': 'a', '0': 'b' }, + props = [-0, Object(-0), 0, Object(0)]; + + var actual = lodashStable.map(props, function(key) { + return func(object, key); + }); + + assert.deepStrictEqual(actual, ['a', 'a', 'b', 'b']); + }); + + it('`_.' + methodName + '` should get symbol keyed property values', function() { + if (Symbol) { + var object = {}; + object[symbol] = 1; + + assert.strictEqual(func(object, symbol), 1); + } + }); + + it('`_.' + methodName + '` should get deep property values', function() { + var object = { 'a': { 'b': 2 } }; + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + assert.strictEqual(func(object, path), 2); + }); + }); + + it('`_.' + methodName + '` should get a key over a path', function() { + var object = { 'a.b': 1, 'a': { 'b': 2 } }; + + lodashStable.each(['a.b', ['a.b']], function(path) { + assert.strictEqual(func(object, path), 1); + }); + }); + + it('`_.' + methodName + '` should not coerce array paths to strings', function() { + var object = { 'a,b,c': 3, 'a': { 'b': { 'c': 4 } } }; + assert.strictEqual(func(object, ['a', 'b', 'c']), 4); + }); + + it('`_.' + methodName + '` should not ignore empty brackets', function() { + var object = { 'a': { '': 1 } }; + assert.strictEqual(func(object, 'a[]'), 1); + }); + + it('`_.' + methodName + '` should handle empty paths', function() { + lodashStable.each([['', ''], [[], ['']]], function(pair) { + assert.strictEqual(func({}, pair[0]), undefined); + assert.strictEqual(func({ '': 3 }, pair[1]), 3); + }); + }); + + it('`_.' + methodName + '` should handle complex paths', function() { + var object = { 'a': { '-1.23': { '["b"]': { 'c': { "['d']": { '\ne\n': { 'f': { 'g': 8 } } } } } } } }; + + var paths = [ + 'a[-1.23]["[\\"b\\"]"].c[\'[\\\'d\\\']\'][\ne\n][f].g', + ['a', '-1.23', '["b"]', 'c', "['d']", '\ne\n', 'f', 'g'] + ]; + + lodashStable.each(paths, function(path) { + assert.strictEqual(func(object, path), 8); + }); + }); + + it('`_.' + methodName + '` should return `undefined` when `object` is nullish', function() { + lodashStable.each(['constructor', ['constructor']], function(path) { + assert.strictEqual(func(null, path), undefined); + assert.strictEqual(func(undefined, path), undefined); + }); + }); + + it('`_.' + methodName + '` should return `undefined` for deep paths when `object` is nullish', function() { + var values = [null, undefined], + expected = lodashStable.map(values, noop), + paths = ['constructor.prototype.valueOf', ['constructor', 'prototype', 'valueOf']]; + + lodashStable.each(paths, function(path) { + var actual = lodashStable.map(values, function(value) { + return func(value, path); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('`_.' + methodName + '` should return `undefined` if parts of `path` are missing', function() { + var object = { 'a': [, null] }; + + lodashStable.each(['a[1].b.c', ['a', '1', 'b', 'c']], function(path) { + assert.strictEqual(func(object, path), undefined); + }); + }); + + it('`_.' + methodName + '` should be able to return `null` values', function() { + var object = { 'a': { 'b': null } }; + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + assert.strictEqual(func(object, path), null); + }); + }); + + it('`_.' + methodName + '` should follow `path` over non-plain objects', function() { + var paths = ['a.b', ['a', 'b']]; + + lodashStable.each(paths, function(path) { + numberProto.a = { 'b': 2 }; + assert.strictEqual(func(0, path), 2); + delete numberProto.a; + }); + }); + + it('`_.' + methodName + '` should return the default value for `undefined` values', function() { + var object = { 'a': {} }, + values = empties.concat(true, new Date, 1, /x/, 'a'), + expected = lodashStable.map(values, function(value) { return [value, value]; }); + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + var actual = lodashStable.map(values, function(value) { + return [func(object, path, value), func(null, path, value)]; + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('`_.' + methodName + '` should return the default value when `path` is empty', function() { + assert.strictEqual(func({}, [], 'a'), 'a'); + }); + }); +}); diff --git a/test/groupBy.js b/test/groupBy.js new file mode 100644 index 000000000..e20b12316 --- /dev/null +++ b/test/groupBy.js @@ -0,0 +1,68 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { LARGE_ARRAY_SIZE } from './utils.js'; +import groupBy from '../groupBy.js'; + +describe('groupBy', function() { + var array = [6.1, 4.2, 6.3]; + + it('should transform keys by `iteratee`', function() { + var actual = groupBy(array, Math.floor); + assert.deepStrictEqual(actual, { '4': [4.2], '6': [6.1, 6.3] }); + }); + + it('should use `_.identity` when `iteratee` is nullish', function() { + var array = [6, 4, 6], + values = [, null, undefined], + expected = lodashStable.map(values, lodashStable.constant({ '4': [4], '6': [6, 6] })); + + var actual = lodashStable.map(values, function(value, index) { + return index ? groupBy(array, value) : groupBy(array); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with `_.property` shorthands', function() { + var actual = groupBy(['one', 'two', 'three'], 'length'); + assert.deepStrictEqual(actual, { '3': ['one', 'two'], '5': ['three'] }); + }); + + it('should only add values to own, not inherited, properties', function() { + var actual = groupBy(array, function(n) { + return Math.floor(n) > 4 ? 'hasOwnProperty' : 'constructor'; + }); + + assert.deepStrictEqual(actual.constructor, [4.2]); + assert.deepStrictEqual(actual.hasOwnProperty, [6.1, 6.3]); + }); + + it('should work with a number for `iteratee`', function() { + var array = [ + [1, 'a'], + [2, 'a'], + [2, 'b'] + ]; + + assert.deepStrictEqual(groupBy(array, 0), { '1': [[1, 'a']], '2': [[2, 'a'], [2, 'b']] }); + assert.deepStrictEqual(groupBy(array, 1), { 'a': [[1, 'a'], [2, 'a']], 'b': [[2, 'b']] }); + }); + + it('should work with an object for `collection`', function() { + var actual = groupBy({ 'a': 6.1, 'b': 4.2, 'c': 6.3 }, Math.floor); + assert.deepStrictEqual(actual, { '4': [4.2], '6': [6.1, 6.3] }); + }); + + it('should work in a lazy sequence', function() { + var array = lodashStable.range(LARGE_ARRAY_SIZE).concat( + lodashStable.range(Math.floor(LARGE_ARRAY_SIZE / 2), LARGE_ARRAY_SIZE), + lodashStable.range(Math.floor(LARGE_ARRAY_SIZE / 1.5), LARGE_ARRAY_SIZE) + ); + + var iteratee = function(value) { value.push(value[0]); return value; }, + predicate = function(value) { return isEven(value[0]); }, + actual = _(array).groupBy().map(iteratee).filter(predicate).take().value(); + + assert.deepEqual(actual, _.take(_.filter(lodashStable.map(groupBy(array), iteratee), predicate))); + }); +}); diff --git a/test/gt.test.js b/test/gt.test.js new file mode 100644 index 000000000..c46995770 --- /dev/null +++ b/test/gt.test.js @@ -0,0 +1,16 @@ +import assert from 'assert'; +import gt from '../gt.js'; + +describe('gt', function() { + it('should return `true` if `value` > `other`', function() { + assert.strictEqual(gt(3, 1), true); + assert.strictEqual(gt('def', 'abc'), true); + }); + + it('should return `false` if `value` is <= `other`', function() { + assert.strictEqual(gt(1, 3), false); + assert.strictEqual(gt(3, 3), false); + assert.strictEqual(gt('abc', 'def'), false); + assert.strictEqual(gt('def', 'def'), false); + }); +}); diff --git a/test/gte.test.js b/test/gte.test.js new file mode 100644 index 000000000..d49ffaa2b --- /dev/null +++ b/test/gte.test.js @@ -0,0 +1,16 @@ +import assert from 'assert'; +import gte from '../gte.js'; + +describe('gte', function() { + it('should return `true` if `value` >= `other`', function() { + assert.strictEqual(gte(3, 1), true); + assert.strictEqual(gte(3, 3), true); + assert.strictEqual(gte('def', 'abc'), true); + assert.strictEqual(gte('def', 'def'), true); + }); + + it('should return `false` if `value` is less than `other`', function() { + assert.strictEqual(gte(1, 3), false); + assert.strictEqual(gte('abc', 'def'), false); + }); +}); diff --git a/test/has-methods.js b/test/has-methods.js new file mode 100644 index 000000000..ebd01b7cb --- /dev/null +++ b/test/has-methods.js @@ -0,0 +1,205 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, toArgs, stubTrue, args, symbol, defineProperty, stubFalse } from './utils.js'; + +describe('has methods', function() { + lodashStable.each(['has', 'hasIn'], function(methodName) { + var func = _[methodName], + isHas = methodName == 'has', + sparseArgs = toArgs([1]), + sparseArray = Array(1), + sparseString = Object('a'); + + delete sparseArgs[0]; + delete sparseString[0]; + + it('`_.' + methodName + '` should check for own properties', function() { + var object = { 'a': 1 }; + + lodashStable.each(['a', ['a']], function(path) { + assert.strictEqual(func(object, path), true); + }); + }); + + it('`_.' + methodName + '` should not use the `hasOwnProperty` method of `object`', function() { + var object = { 'hasOwnProperty': null, 'a': 1 }; + assert.strictEqual(func(object, 'a'), true); + }); + + it('`_.' + methodName + '` should support deep paths', function() { + var object = { 'a': { 'b': 2 } }; + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + assert.strictEqual(func(object, path), true); + }); + + lodashStable.each(['a.a', ['a', 'a']], function(path) { + assert.strictEqual(func(object, path), false); + }); + }); + + it('`_.' + methodName + '` should coerce `path` to a string', function() { + function fn() {} + fn.toString = lodashStable.constant('fn'); + + var object = { 'null': 1 , 'undefined': 2, 'fn': 3, '[object Object]': 4 }, + paths = [null, undefined, fn, {}], + expected = lodashStable.map(paths, stubTrue); + + lodashStable.times(2, function(index) { + var actual = lodashStable.map(paths, function(path) { + return func(object, index ? [path] : path); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('`_.' + methodName + '` should work with `arguments` objects', function() { + assert.strictEqual(func(args, 1), true); + }); + + it('`_.' + methodName + '` should work with a non-string `path`', function() { + var array = [1, 2, 3]; + + lodashStable.each([1, [1]], function(path) { + assert.strictEqual(func(array, path), true); + }); + }); + + it('`_.' + methodName + '` should preserve the sign of `0`', function() { + var object = { '-0': 'a', '0': 'b' }, + props = [-0, Object(-0), 0, Object(0)], + expected = lodashStable.map(props, stubTrue); + + var actual = lodashStable.map(props, function(key) { + return func(object, key); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should work with a symbol `path`', function() { + function Foo() {} + + if (Symbol) { + Foo.prototype[symbol] = 1; + + var symbol2 = Symbol('b'); + defineProperty(Foo.prototype, symbol2, { + 'configurable': true, + 'enumerable': false, + 'writable': true, + 'value': 2 + }); + + var object = isHas ? Foo.prototype : new Foo; + assert.strictEqual(func(object, symbol), true); + assert.strictEqual(func(object, symbol2), true); + } + }); + + it('`_.' + methodName + '` should check for a key over a path', function() { + var object = { 'a.b': 1 }; + + lodashStable.each(['a.b', ['a.b']], function(path) { + assert.strictEqual(func(object, path), true); + }); + }); + + it('`_.' + methodName + '` should return `true` for indexes of sparse values', function() { + var values = [sparseArgs, sparseArray, sparseString], + expected = lodashStable.map(values, stubTrue); + + var actual = lodashStable.map(values, function(value) { + return func(value, 0); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should return `true` for indexes of sparse values with deep paths', function() { + var values = [sparseArgs, sparseArray, sparseString], + expected = lodashStable.map(values, lodashStable.constant([true, true])); + + var actual = lodashStable.map(values, function(value) { + return lodashStable.map(['a[0]', ['a', '0']], function(path) { + return func({ 'a': value }, path); + }); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should return `' + (isHas ? 'false' : 'true') + '` for inherited properties', function() { + function Foo() {} + Foo.prototype.a = 1; + + lodashStable.each(['a', ['a']], function(path) { + assert.strictEqual(func(new Foo, path), !isHas); + }); + }); + + it('`_.' + methodName + '` should return `' + (isHas ? 'false' : 'true') + '` for nested inherited properties', function() { + function Foo() {} + Foo.prototype.a = { 'b': 1 }; + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + assert.strictEqual(func(new Foo, path), !isHas); + }); + }); + + it('`_.' + methodName + '` should return `false` when `object` is nullish', function() { + var values = [null, undefined], + expected = lodashStable.map(values, stubFalse); + + lodashStable.each(['constructor', ['constructor']], function(path) { + var actual = lodashStable.map(values, function(value) { + return func(value, path); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('`_.' + methodName + '` should return `false` for deep paths when `object` is nullish', function() { + var values = [null, undefined], + expected = lodashStable.map(values, stubFalse); + + lodashStable.each(['constructor.prototype.valueOf', ['constructor', 'prototype', 'valueOf']], function(path) { + var actual = lodashStable.map(values, function(value) { + return func(value, path); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('`_.' + methodName + '` should return `false` for nullish values of nested objects', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, stubFalse); + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + var actual = lodashStable.map(values, function(value, index) { + var object = index ? { 'a': value } : {}; + return func(object, path); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('`_.' + methodName + '` should return `false` over sparse values of deep paths', function() { + var values = [sparseArgs, sparseArray, sparseString], + expected = lodashStable.map(values, lodashStable.constant([false, false])); + + var actual = lodashStable.map(values, function(value) { + return lodashStable.map(['a[0].b', ['a', '0', 'b']], function(path) { + return func({ 'a': value }, path); + }); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); +}); diff --git a/test/head.js b/test/head.js new file mode 100644 index 000000000..c04a788d5 --- /dev/null +++ b/test/head.js @@ -0,0 +1,62 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { arrayProto, LARGE_ARRAY_SIZE } from './utils.js'; +import head from '../head.js'; +import first from '../first.js'; + +describe('head', function() { + var array = [1, 2, 3, 4]; + + it('should return the first element', function() { + assert.strictEqual(head(array), 1); + }); + + it('should return `undefined` when querying empty arrays', function() { + arrayProto[0] = 1; + assert.strictEqual(head([]), undefined); + arrayProto.length = 0; + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + actual = lodashStable.map(array, head); + + assert.deepStrictEqual(actual, [1, 4, 7]); + }); + + it('should be aliased', function() { + assert.strictEqual(first, head); + }); + + it('should return an unwrapped value when implicitly chaining', function() { + var wrapped = _(array); + assert.strictEqual(wrapped.head(), 1); + assert.strictEqual(wrapped.first(), 1); + }); + + it('should return a wrapped value when explicitly chaining', function() { + var wrapped = _(array).chain(); + assert.ok(wrapped.head() instanceof _); + assert.ok(wrapped.first() instanceof _); + }); + + it('should not execute immediately when explicitly chaining', function() { + var wrapped = _(array).chain(); + assert.strictEqual(wrapped.head().__wrapped__, array); + assert.strictEqual(wrapped.first().__wrapped__, array); + }); + + it('should work in a lazy sequence', function() { + var largeArray = lodashStable.range(LARGE_ARRAY_SIZE), + smallArray = array; + + lodashStable.each(['head', 'first'], function(methodName) { + lodashStable.times(2, function(index) { + var array = index ? largeArray : smallArray, + actual = _(array).filter(isEven)[methodName](); + + assert.strictEqual(actual, _[methodName](_.filter(array, isEven))); + }); + }); + }); +}); diff --git a/test/identity.js b/test/identity.js new file mode 100644 index 000000000..ced20b853 --- /dev/null +++ b/test/identity.js @@ -0,0 +1,9 @@ +import assert from 'assert'; +import identity from '../identity.js'; + +describe('identity', function() { + it('should return the first argument given', function() { + var object = { 'name': 'fred' }; + assert.strictEqual(identity(object), object); + }); +}); diff --git a/test/inRange.js b/test/inRange.js new file mode 100644 index 000000000..2efe128c4 --- /dev/null +++ b/test/inRange.js @@ -0,0 +1,54 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubTrue } from './utils.js'; +import inRange from '../inRange.js'; + +describe('inRange', function() { + it('should work with an `end`', function() { + assert.strictEqual(inRange(3, 5), true); + assert.strictEqual(inRange(5, 5), false); + assert.strictEqual(inRange(6, 5), false); + }); + + it('should work with a `start` and `end`', function() { + assert.strictEqual(inRange(1, 1, 5), true); + assert.strictEqual(inRange(3, 1, 5), true); + assert.strictEqual(inRange(0, 1, 5), false); + assert.strictEqual(inRange(5, 1, 5), false); + }); + + it('should treat falsey `start` as `0`', function() { + lodashStable.each(falsey, function(value, index) { + if (index) { + assert.strictEqual(inRange(0, value), false); + assert.strictEqual(inRange(0, value, 1), true); + } else { + assert.strictEqual(inRange(0), false); + } + }); + }); + + it('should swap `start` and `end` when `start` > `end`', function() { + assert.strictEqual(inRange(2, 5, 1), true); + assert.strictEqual(inRange(-3, -2, -6), true); + }); + + it('should work with a floating point `n` value', function() { + assert.strictEqual(inRange(0.5, 5), true); + assert.strictEqual(inRange(1.2, 1, 5), true); + assert.strictEqual(inRange(5.2, 5), false); + assert.strictEqual(inRange(0.5, 1, 5), false); + }); + + it('should coerce arguments to finite numbers', function() { + var actual = [ + inRange(0, '1'), + inRange(0, '0', 1), + inRange(0, 0, '1'), + inRange(0, NaN, 1), + inRange(-1, -1, NaN) + ]; + + assert.deepStrictEqual(actual, lodashStable.map(actual, stubTrue)); + }); +}); diff --git a/test/includes.js b/test/includes.js new file mode 100644 index 000000000..b9147a80c --- /dev/null +++ b/test/includes.js @@ -0,0 +1,95 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { empties, stubFalse } from './utils.js'; +import includes from '../includes.js'; + +describe('includes', function() { + (function() { + lodashStable.each({ + 'an `arguments` object': arguments, + 'an array': [1, 2, 3, 4], + 'an object': { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }, + 'a string': '1234' + }, + function(collection, key) { + it('should work with ' + key + ' and return `true` for matched values', function() { + assert.strictEqual(includes(collection, 3), true); + }); + + it('should work with ' + key + ' and return `false` for unmatched values', function() { + assert.strictEqual(includes(collection, 5), false); + }); + + it('should work with ' + key + ' and floor `position` values', function() { + assert.strictEqual(includes(collection, 2, 1.2), true); + }); + + it('should work with ' + key + ' and return an unwrapped value implicitly when chaining', function() { + assert.strictEqual(_(collection).includes(3), true); + }); + + it('should work with ' + key + ' and return a wrapped value when explicitly chaining', function() { + assert.ok(_(collection).chain().includes(3) instanceof _); + }); + }); + + lodashStable.each({ + 'literal': 'abc', + 'object': Object('abc') + }, + function(collection, key) { + it('should work with a string ' + key + ' for `collection`', function() { + assert.strictEqual(includes(collection, 'bc'), true); + assert.strictEqual(includes(collection, 'd'), false); + }); + }); + + it('should return `false` for empty collections', function() { + var expected = lodashStable.map(empties, stubFalse); + + var actual = lodashStable.map(empties, function(value) { + try { + return includes(value); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with a string and a `fromIndex` >= `length`', function() { + var string = '1234', + length = string.length, + indexes = [4, 6, Math.pow(2, 32), Infinity]; + + var expected = lodashStable.map(indexes, function(index) { + return [false, false, index == length]; + }); + + var actual = lodashStable.map(indexes, function(fromIndex) { + return [ + includes(string, 1, fromIndex), + includes(string, undefined, fromIndex), + includes(string, '', fromIndex) + ]; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should match `NaN`', function() { + assert.strictEqual(includes([1, NaN, 3], NaN), true); + }); + + it('should match `-0` as `0`', function() { + assert.strictEqual(includes([-0], 0), true); + assert.strictEqual(includes([0], -0), true); + }); + + it('should work as an iteratee for methods like `_.every`', function() { + var array = [2, 3, 1], + values = [1, 2, 3]; + + assert.ok(lodashStable.every(values, lodashStable.partial(includes, array))); + }); + })(1, 2, 3, 4); +}); diff --git a/test/indexOf-methods.js b/test/indexOf-methods.js new file mode 100644 index 000000000..05b506f33 --- /dev/null +++ b/test/indexOf-methods.js @@ -0,0 +1,63 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, falsey } from './utils.js'; + +describe('indexOf methods', function() { + lodashStable.each(['indexOf', 'lastIndexOf', 'sortedIndexOf', 'sortedLastIndexOf'], function(methodName) { + var func = _[methodName], + isIndexOf = !/last/i.test(methodName), + isSorted = /^sorted/.test(methodName); + + it('`_.' + methodName + '` should accept a falsey `array`', function() { + var expected = lodashStable.map(falsey, lodashStable.constant(-1)); + + var actual = lodashStable.map(falsey, function(array, index) { + try { + return index ? func(array) : func(); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should return `-1` for an unmatched value', function() { + var array = [1, 2, 3], + empty = []; + + assert.strictEqual(func(array, 4), -1); + assert.strictEqual(func(array, 4, true), -1); + assert.strictEqual(func(array, undefined, true), -1); + + assert.strictEqual(func(empty, undefined), -1); + assert.strictEqual(func(empty, undefined, true), -1); + }); + + it('`_.' + methodName + '` should not match values on empty arrays', function() { + var array = []; + array[-1] = 0; + + assert.strictEqual(func(array, undefined), -1); + assert.strictEqual(func(array, 0, true), -1); + }); + + it('`_.' + methodName + '` should match `NaN`', function() { + var array = isSorted + ? [1, 2, NaN, NaN] + : [1, NaN, 3, NaN, 5, NaN]; + + if (isSorted) { + assert.strictEqual(func(array, NaN, true), isIndexOf ? 2 : 3); + } + else { + assert.strictEqual(func(array, NaN), isIndexOf ? 1 : 5); + assert.strictEqual(func(array, NaN, 2), isIndexOf ? 3 : 1); + assert.strictEqual(func(array, NaN, -2), isIndexOf ? 5 : 3); + } + }); + + it('`_.' + methodName + '` should match `-0` as `0`', function() { + assert.strictEqual(func([-0], 0), 0); + assert.strictEqual(func([0], -0), 0); + }); + }); +}); diff --git a/test/initial.js b/test/initial.js new file mode 100644 index 000000000..c2891ca85 --- /dev/null +++ b/test/initial.js @@ -0,0 +1,61 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubArray, LARGE_ARRAY_SIZE } from './utils.js'; +import initial from '../initial.js'; + +describe('initial', function() { + var array = [1, 2, 3]; + + it('should accept a falsey `array`', function() { + var expected = lodashStable.map(falsey, stubArray); + + var actual = lodashStable.map(falsey, function(array, index) { + try { + return index ? initial(array) : initial(); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should exclude last element', function() { + assert.deepStrictEqual(initial(array), [1, 2]); + }); + + it('should return an empty when querying empty arrays', function() { + assert.deepStrictEqual(initial([]), []); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + actual = lodashStable.map(array, initial); + + assert.deepStrictEqual(actual, [[1, 2], [4, 5], [7, 8]]); + }); + + it('should work in a lazy sequence', function() { + var array = lodashStable.range(LARGE_ARRAY_SIZE), + values = []; + + var actual = _(array).initial().filter(function(value) { + values.push(value); + return false; + }) + .value(); + + assert.deepEqual(actual, []); + assert.deepEqual(values, initial(array)); + + values = []; + + actual = _(array).filter(function(value) { + values.push(value); + return isEven(value); + }) + .initial() + .value(); + + assert.deepEqual(actual, initial(lodashStable.filter(array, isEven))); + assert.deepEqual(values, array); + }); +}); diff --git a/test/intersection-methods.js b/test/intersection-methods.js new file mode 100644 index 000000000..d90104da7 --- /dev/null +++ b/test/intersection-methods.js @@ -0,0 +1,91 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, args, LARGE_ARRAY_SIZE, stubNaN } from './utils.js'; + +describe('intersection methods', function() { + lodashStable.each(['intersection', 'intersectionBy', 'intersectionWith'], function(methodName) { + var func = _[methodName]; + + it('`_.' + methodName + '` should return the intersection of two arrays', function() { + var actual = func([2, 1], [2, 3]); + assert.deepStrictEqual(actual, [2]); + }); + + it('`_.' + methodName + '` should return the intersection of multiple arrays', function() { + var actual = func([2, 1, 2, 3], [3, 4], [3, 2]); + assert.deepStrictEqual(actual, [3]); + }); + + it('`_.' + methodName + '` should return an array of unique values', function() { + var actual = func([1, 1, 3, 2, 2], [5, 2, 2, 1, 4], [2, 1, 1]); + assert.deepStrictEqual(actual, [1, 2]); + }); + + it('`_.' + methodName + '` should work with a single array', function() { + var actual = func([1, 1, 3, 2, 2]); + assert.deepStrictEqual(actual, [1, 3, 2]); + }); + + it('`_.' + methodName + '` should work with `arguments` objects', function() { + var array = [0, 1, null, 3], + expected = [1, 3]; + + assert.deepStrictEqual(func(array, args), expected); + assert.deepStrictEqual(func(args, array), expected); + }); + + it('`_.' + methodName + '` should treat `-0` as `0`', function() { + var values = [-0, 0], + expected = lodashStable.map(values, lodashStable.constant(['0'])); + + var actual = lodashStable.map(values, function(value) { + return lodashStable.map(func(values, [value]), lodashStable.toString); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should match `NaN`', function() { + var actual = func([1, NaN, 3], [NaN, 5, NaN]); + assert.deepStrictEqual(actual, [NaN]); + }); + + it('`_.' + methodName + '` should work with large arrays of `-0` as `0`', function() { + var values = [-0, 0], + expected = lodashStable.map(values, lodashStable.constant(['0'])); + + var actual = lodashStable.map(values, function(value) { + var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, lodashStable.constant(value)); + return lodashStable.map(func(values, largeArray), lodashStable.toString); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should work with large arrays of `NaN`', function() { + var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, stubNaN); + assert.deepStrictEqual(func([1, NaN, 3], largeArray), [NaN]); + }); + + it('`_.' + methodName + '` should work with large arrays of objects', function() { + var object = {}, + largeArray = lodashStable.times(LARGE_ARRAY_SIZE, lodashStable.constant(object)); + + assert.deepStrictEqual(func([object], largeArray), [object]); + assert.deepStrictEqual(func(lodashStable.range(LARGE_ARRAY_SIZE), [1]), [1]); + }); + + it('`_.' + methodName + '` should treat values that are not arrays or `arguments` objects as empty', function() { + var array = [0, 1, null, 3]; + assert.deepStrictEqual(func(array, 3, { '0': 1 }, null), []); + assert.deepStrictEqual(func(null, array, null, [2, 3]), []); + assert.deepStrictEqual(func(array, null, args, null), []); + }); + + it('`_.' + methodName + '` should return a wrapped value when chaining', function() { + var wrapped = _([1, 3, 2])[methodName]([5, 2, 1, 4]); + assert.ok(wrapped instanceof _); + assert.deepEqual(wrapped.value(), [1, 2]); + }); + }); +}); diff --git a/test/intersectionBy.js b/test/intersectionBy.js new file mode 100644 index 000000000..c2a988f1c --- /dev/null +++ b/test/intersectionBy.js @@ -0,0 +1,23 @@ +import assert from 'assert'; +import { slice } from './utils.js'; +import intersectionBy from '../intersectionBy.js'; + +describe('intersectionBy', function() { + it('should accept an `iteratee`', function() { + var actual = intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor); + assert.deepStrictEqual(actual, [2.1]); + + actual = intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); + assert.deepStrictEqual(actual, [{ 'x': 1 }]); + }); + + it('should provide correct `iteratee` arguments', function() { + var args; + + intersectionBy([2.1, 1.2], [2.3, 3.4], function() { + args || (args = slice.call(arguments)); + }); + + assert.deepStrictEqual(args, [2.3]); + }); +}); diff --git a/test/intersectionWith.test.js b/test/intersectionWith.test.js new file mode 100644 index 000000000..72b3cbee3 --- /dev/null +++ b/test/intersectionWith.test.js @@ -0,0 +1,27 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { LARGE_ARRAY_SIZE, stubZero } from './utils.js'; +import intersectionWith from '../intersectionWith.js'; + +describe('intersectionWith', function() { + it('should work with a `comparator`', function() { + var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }], + others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }], + actual = intersectionWith(objects, others, lodashStable.isEqual); + + assert.deepStrictEqual(actual, [objects[0]]); + }); + + it('should preserve the sign of `0`', function() { + var array = [-0], + largeArray = lodashStable.times(LARGE_ARRAY_SIZE, stubZero), + others = [[0], largeArray], + expected = lodashStable.map(others, lodashStable.constant(['-0'])); + + var actual = lodashStable.map(others, function(other) { + return lodashStable.map(intersectionWith(array, other, lodashStable.eq), lodashStable.toString); + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/invert.test.js b/test/invert.test.js new file mode 100644 index 000000000..5fbfd22f2 --- /dev/null +++ b/test/invert.test.js @@ -0,0 +1,30 @@ +import assert from 'assert'; +import invert from '../invert.js'; + +describe('invert', function() { + it('should invert an object', function() { + var object = { 'a': 1, 'b': 2 }, + actual = invert(object); + + assert.deepStrictEqual(actual, { '1': 'a', '2': 'b' }); + assert.deepStrictEqual(invert(actual), { 'a': '1', 'b': '2' }); + }); + + it('should work with values that shadow keys on `Object.prototype`', function() { + var object = { 'a': 'hasOwnProperty', 'b': 'constructor' }; + assert.deepStrictEqual(invert(object), { 'hasOwnProperty': 'a', 'constructor': 'b' }); + }); + + it('should work with an object that has a `length` property', function() { + var object = { '0': 'a', '1': 'b', 'length': 2 }; + assert.deepStrictEqual(invert(object), { 'a': '0', 'b': '1', '2': 'length' }); + }); + + it('should return a wrapped value when chaining', function() { + var object = { 'a': 1, 'b': 2 }, + wrapped = _(object).invert(); + + assert.ok(wrapped instanceof _); + assert.deepEqual(wrapped.value(), { '1': 'a', '2': 'b' }); + }); +}); diff --git a/test/invertBy.js b/test/invertBy.js new file mode 100644 index 000000000..42379222e --- /dev/null +++ b/test/invertBy.js @@ -0,0 +1,42 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import invertBy from '../invertBy.js'; + +describe('invertBy', function() { + var object = { 'a': 1, 'b': 2, 'c': 1 }; + + it('should transform keys by `iteratee`', function() { + var expected = { 'group1': ['a', 'c'], 'group2': ['b'] }; + + var actual = invertBy(object, function(value) { + return 'group' + value; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should use `_.identity` when `iteratee` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, lodashStable.constant({ '1': ['a', 'c'], '2': ['b'] })); + + var actual = lodashStable.map(values, function(value, index) { + return index ? invertBy(object, value) : invertBy(object); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should only add multiple values to own, not inherited, properties', function() { + var object = { 'a': 'hasOwnProperty', 'b': 'constructor' }, + expected = { 'hasOwnProperty': ['a'], 'constructor': ['b'] }; + + assert.ok(lodashStable.isEqual(invertBy(object), expected)); + }); + + it('should return a wrapped value when chaining', function() { + var wrapped = _(object).invertBy(); + + assert.ok(wrapped instanceof _); + assert.deepEqual(wrapped.value(), { '1': ['a', 'c'], '2': ['b'] }); + }); +}); diff --git a/test/invoke.js b/test/invoke.js new file mode 100644 index 000000000..4d046a80e --- /dev/null +++ b/test/invoke.js @@ -0,0 +1,71 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { noop, stubA, stubB, stubOne } from './utils.js'; +import invoke from '../invoke.js'; + +describe('invoke', function() { + it('should invoke a method on `object`', function() { + var object = { 'a': lodashStable.constant('A') }, + actual = invoke(object, 'a'); + + assert.strictEqual(actual, 'A'); + }); + + it('should support invoking with arguments', function() { + var object = { 'a': function(a, b) { return [a, b]; } }, + actual = invoke(object, 'a', 1, 2); + + assert.deepStrictEqual(actual, [1, 2]); + }); + + it('should not error on nullish elements', function() { + var values = [null, undefined], + expected = lodashStable.map(values, noop); + + var actual = lodashStable.map(values, function(value) { + try { + return invoke(value, 'a.b', 1, 2); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should preserve the sign of `0`', function() { + var object = { '-0': stubA, '0': stubB }, + props = [-0, Object(-0), 0, Object(0)]; + + var actual = lodashStable.map(props, function(key) { + return invoke(object, key); + }); + + assert.deepStrictEqual(actual, ['a', 'a', 'b', 'b']); + }); + + it('should support deep paths', function() { + var object = { 'a': { 'b': function(a, b) { return [a, b]; } } }; + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + var actual = invoke(object, path, 1, 2); + assert.deepStrictEqual(actual, [1, 2]); + }); + }); + + it('should invoke deep property methods with the correct `this` binding', function() { + var object = { 'a': { 'b': function() { return this.c; }, 'c': 1 } }; + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + assert.deepStrictEqual(invoke(object, path), 1); + }); + }); + + it('should return an unwrapped value when implicitly chaining', function() { + var object = { 'a': stubOne }; + assert.strictEqual(_(object).invoke('a'), 1); + }); + + it('should return a wrapped value when explicitly chaining', function() { + var object = { 'a': stubOne }; + assert.ok(_(object).chain().invoke('a') instanceof _); + }); +}); diff --git a/test/invokeMap.js b/test/invokeMap.js new file mode 100644 index 000000000..bb3ba944f --- /dev/null +++ b/test/invokeMap.js @@ -0,0 +1,105 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice, stubOne } from './utils.js'; +import invokeMap from '../invokeMap.js'; + +describe('invokeMap', function() { + it('should invoke a methods on each element of `collection`', function() { + var array = ['a', 'b', 'c'], + actual = invokeMap(array, 'toUpperCase'); + + assert.deepStrictEqual(actual, ['A', 'B', 'C']); + }); + + it('should support invoking with arguments', function() { + var array = [function() { return slice.call(arguments); }], + actual = invokeMap(array, 'call', null, 'a', 'b', 'c'); + + assert.deepStrictEqual(actual, [['a', 'b', 'c']]); + }); + + it('should work with a function for `methodName`', function() { + var array = ['a', 'b', 'c']; + + var actual = invokeMap(array, function(left, right) { + return left + this.toUpperCase() + right; + }, '(', ')'); + + assert.deepStrictEqual(actual, ['(A)', '(B)', '(C)']); + }); + + it('should work with an object for `collection`', function() { + var object = { 'a': 1, 'b': 2, 'c': 3 }, + actual = invokeMap(object, 'toFixed', 1); + + assert.deepStrictEqual(actual, ['1.0', '2.0', '3.0']); + }); + + it('should treat number values for `collection` as empty', function() { + assert.deepStrictEqual(invokeMap(1), []); + }); + + it('should not error on nullish elements', function() { + var array = ['a', null, undefined, 'd']; + + try { + var actual = invokeMap(array, 'toUpperCase'); + } catch (e) {} + + assert.deepStrictEqual(actual, ['A', undefined, undefined, 'D']); + }); + + it('should not error on elements with missing properties', function() { + var objects = lodashStable.map([null, undefined, stubOne], function(value) { + return { 'a': value }; + }); + + var expected = lodashStable.map(objects, function(object) { + return object.a ? object.a() : undefined; + }); + + try { + var actual = invokeMap(objects, 'a'); + } catch (e) {} + + assert.deepStrictEqual(actual, expected); + }); + + it('should invoke deep property methods with the correct `this` binding', function() { + var object = { 'a': { 'b': function() { return this.c; }, 'c': 1 } }; + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + assert.deepStrictEqual(invokeMap([object], path), [1]); + }); + }); + + it('should return a wrapped value when chaining', function() { + var array = ['a', 'b', 'c'], + wrapped = _(array), + actual = wrapped.invokeMap('toUpperCase'); + + assert.ok(actual instanceof _); + assert.deepEqual(actual.valueOf(), ['A', 'B', 'C']); + + actual = wrapped.invokeMap(function(left, right) { + return left + this.toUpperCase() + right; + }, '(', ')'); + + assert.ok(actual instanceof _); + assert.deepEqual(actual.valueOf(), ['(A)', '(B)', '(C)']); + }); + + it('should support shortcut fusion', function() { + var count = 0, + method = function() { count++; return this.index; }; + + var array = lodashStable.times(LARGE_ARRAY_SIZE, function(index) { + return { 'index': index, 'method': method }; + }); + + var actual = _(array).invokeMap('method').take(1).value(); + + assert.strictEqual(count, 1); + assert.deepEqual(actual, [0]); + }); +}); diff --git a/test/isArguments.test.js b/test/isArguments.test.js new file mode 100644 index 000000000..87c62a980 --- /dev/null +++ b/test/isArguments.test.js @@ -0,0 +1,39 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { args, strictArgs, falsey, stubFalse, slice, noop, symbol, realm } from './utils.js'; +import isArguments from '../isArguments.js'; + +describe('isArguments', function() { + it('should return `true` for `arguments` objects', function() { + assert.strictEqual(isArguments(args), true); + assert.strictEqual(isArguments(strictArgs), true); + }); + + it('should return `false` for non `arguments` objects', function() { + var expected = lodashStable.map(falsey, stubFalse); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isArguments(value) : isArguments(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isArguments([1, 2, 3]), false); + assert.strictEqual(isArguments(true), false); + assert.strictEqual(isArguments(new Date), false); + assert.strictEqual(isArguments(new Error), false); + assert.strictEqual(isArguments(_), false); + assert.strictEqual(isArguments(slice), false); + assert.strictEqual(isArguments({ '0': 1, 'callee': noop, 'length': 1 }), false); + assert.strictEqual(isArguments(1), false); + assert.strictEqual(isArguments(/x/), false); + assert.strictEqual(isArguments('a'), false); + assert.strictEqual(isArguments(symbol), false); + }); + + it('should work with an `arguments` object from another realm', function() { + if (realm.arguments) { + assert.strictEqual(isArguments(realm.arguments), true); + } + }); +}); diff --git a/test/isArray.js b/test/isArray.js new file mode 100644 index 000000000..552796755 --- /dev/null +++ b/test/isArray.js @@ -0,0 +1,38 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubFalse, args, slice, symbol, realm } from './utils.js'; +import isArray from '../isArray.js'; + +describe('isArray', function() { + it('should return `true` for arrays', function() { + assert.strictEqual(isArray([1, 2, 3]), true); + }); + + it('should return `false` for non-arrays', function() { + var expected = lodashStable.map(falsey, stubFalse); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isArray(value) : isArray(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isArray(args), false); + assert.strictEqual(isArray(true), false); + assert.strictEqual(isArray(new Date), false); + assert.strictEqual(isArray(new Error), false); + assert.strictEqual(isArray(_), false); + assert.strictEqual(isArray(slice), false); + assert.strictEqual(isArray({ '0': 1, 'length': 1 }), false); + assert.strictEqual(isArray(1), false); + assert.strictEqual(isArray(/x/), false); + assert.strictEqual(isArray('a'), false); + assert.strictEqual(isArray(symbol), false); + }); + + it('should work with an array from another realm', function() { + if (realm.array) { + assert.strictEqual(isArray(realm.array), true); + } + }); +}); diff --git a/test/isArrayBuffer.test.js b/test/isArrayBuffer.test.js new file mode 100644 index 000000000..7ac1bf098 --- /dev/null +++ b/test/isArrayBuffer.test.js @@ -0,0 +1,41 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { arrayBuffer, falsey, stubFalse, args, slice, symbol, realm } from './utils.js'; +import isArrayBuffer from '../isArrayBuffer.js'; + +describe('isArrayBuffer', function() { + it('should return `true` for array buffers', function() { + if (ArrayBuffer) { + assert.strictEqual(isArrayBuffer(arrayBuffer), true); + } + }); + + it('should return `false` for non array buffers', function() { + var expected = lodashStable.map(falsey, stubFalse); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isArrayBuffer(value) : isArrayBuffer(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isArrayBuffer(args), false); + assert.strictEqual(isArrayBuffer([1]), false); + assert.strictEqual(isArrayBuffer(true), false); + assert.strictEqual(isArrayBuffer(new Date), false); + assert.strictEqual(isArrayBuffer(new Error), false); + assert.strictEqual(isArrayBuffer(_), false); + assert.strictEqual(isArrayBuffer(slice), false); + assert.strictEqual(isArrayBuffer({ 'a': 1 }), false); + assert.strictEqual(isArrayBuffer(1), false); + assert.strictEqual(isArrayBuffer(/x/), false); + assert.strictEqual(isArrayBuffer('a'), false); + assert.strictEqual(isArrayBuffer(symbol), false); + }); + + it('should work with array buffers from another realm', function() { + if (realm.arrayBuffer) { + assert.strictEqual(isArrayBuffer(realm.arrayBuffer), true); + } + }); +}); diff --git a/test/isArrayLike.test.js b/test/isArrayLike.test.js new file mode 100644 index 000000000..6e332dd2d --- /dev/null +++ b/test/isArrayLike.test.js @@ -0,0 +1,48 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { args, stubTrue, falsey, asyncFunc, genFunc, slice, symbol, realm } from './utils.js'; +import isArrayLike from '../isArrayLike.js'; + +describe('isArrayLike', function() { + it('should return `true` for array-like values', function() { + var values = [args, [1, 2, 3], { '0': 'a', 'length': 1 }, 'a'], + expected = lodashStable.map(values, stubTrue), + actual = lodashStable.map(values, isArrayLike); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return `false` for non-arrays', function() { + var expected = lodashStable.map(falsey, function(value) { + return value === ''; + }); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isArrayLike(value) : isArrayLike(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isArrayLike(true), false); + assert.strictEqual(isArrayLike(new Date), false); + assert.strictEqual(isArrayLike(new Error), false); + assert.strictEqual(isArrayLike(_), false); + assert.strictEqual(isArrayLike(asyncFunc), false); + assert.strictEqual(isArrayLike(genFunc), false); + assert.strictEqual(isArrayLike(slice), false); + assert.strictEqual(isArrayLike({ 'a': 1 }), false); + assert.strictEqual(isArrayLike(1), false); + assert.strictEqual(isArrayLike(/x/), false); + assert.strictEqual(isArrayLike(symbol), false); + }); + + it('should work with an array from another realm', function() { + if (realm.object) { + var values = [realm.arguments, realm.array, realm.string], + expected = lodashStable.map(values, stubTrue), + actual = lodashStable.map(values, isArrayLike); + + assert.deepStrictEqual(actual, expected); + } + }); +}); diff --git a/test/isBoolean.test.js b/test/isBoolean.test.js new file mode 100644 index 000000000..6f6c2e3d4 --- /dev/null +++ b/test/isBoolean.test.js @@ -0,0 +1,43 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, args, slice, symbol, realm } from './utils.js'; +import isBoolean from '../isBoolean.js'; + +describe('isBoolean', function() { + it('should return `true` for booleans', function() { + assert.strictEqual(isBoolean(true), true); + assert.strictEqual(isBoolean(false), true); + assert.strictEqual(isBoolean(Object(true)), true); + assert.strictEqual(isBoolean(Object(false)), true); + }); + + it('should return `false` for non-booleans', function() { + var expected = lodashStable.map(falsey, function(value) { + return value === false; + }); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isBoolean(value) : isBoolean(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isBoolean(args), false); + assert.strictEqual(isBoolean([1, 2, 3]), false); + assert.strictEqual(isBoolean(new Date), false); + assert.strictEqual(isBoolean(new Error), false); + assert.strictEqual(isBoolean(_), false); + assert.strictEqual(isBoolean(slice), false); + assert.strictEqual(isBoolean({ 'a': 1 }), false); + assert.strictEqual(isBoolean(1), false); + assert.strictEqual(isBoolean(/x/), false); + assert.strictEqual(isBoolean('a'), false); + assert.strictEqual(isBoolean(symbol), false); + }); + + it('should work with a boolean from another realm', function() { + if (realm.boolean) { + assert.strictEqual(isBoolean(realm.boolean), true); + } + }); +}); diff --git a/test/isBuffer.test.js b/test/isBuffer.test.js new file mode 100644 index 000000000..a81af76f8 --- /dev/null +++ b/test/isBuffer.test.js @@ -0,0 +1,41 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubFalse, args, slice, symbol, isStrict, lodashBizarro } from './utils.js'; +import isBuffer from '../isBuffer.js'; + +describe('isBuffer', function() { + it('should return `true` for buffers', function() { + if (Buffer) { + assert.strictEqual(isBuffer(new Buffer(2)), true); + } + }); + + it('should return `false` for non-buffers', function() { + var expected = lodashStable.map(falsey, stubFalse); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isBuffer(value) : isBuffer(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isBuffer(args), false); + assert.strictEqual(isBuffer([1]), false); + assert.strictEqual(isBuffer(true), false); + assert.strictEqual(isBuffer(new Date), false); + assert.strictEqual(isBuffer(new Error), false); + assert.strictEqual(isBuffer(_), false); + assert.strictEqual(isBuffer(slice), false); + assert.strictEqual(isBuffer({ 'a': 1 }), false); + assert.strictEqual(isBuffer(1), false); + assert.strictEqual(isBuffer(/x/), false); + assert.strictEqual(isBuffer('a'), false); + assert.strictEqual(isBuffer(symbol), false); + }); + + it('should return `false` if `Buffer` is not defined', function() { + if (!isStrict && Buffer && lodashBizarro) { + assert.strictEqual(lodashBizarro.isBuffer(new Buffer(2)), false); + } + }); +}); diff --git a/test/isDate.test.js b/test/isDate.test.js new file mode 100644 index 000000000..8da8064c2 --- /dev/null +++ b/test/isDate.test.js @@ -0,0 +1,38 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubFalse, args, slice, symbol, realm } from './utils.js'; +import isDate from '../isDate.js'; + +describe('isDate', function() { + it('should return `true` for dates', function() { + assert.strictEqual(isDate(new Date), true); + }); + + it('should return `false` for non-dates', function() { + var expected = lodashStable.map(falsey, stubFalse); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isDate(value) : isDate(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isDate(args), false); + assert.strictEqual(isDate([1, 2, 3]), false); + assert.strictEqual(isDate(true), false); + assert.strictEqual(isDate(new Error), false); + assert.strictEqual(isDate(_), false); + assert.strictEqual(isDate(slice), false); + assert.strictEqual(isDate({ 'a': 1 }), false); + assert.strictEqual(isDate(1), false); + assert.strictEqual(isDate(/x/), false); + assert.strictEqual(isDate('a'), false); + assert.strictEqual(isDate(symbol), false); + }); + + it('should work with a date object from another realm', function() { + if (realm.date) { + assert.strictEqual(isDate(realm.date), true); + } + }); +}); diff --git a/test/isElement.test.js b/test/isElement.test.js new file mode 100644 index 000000000..c5e8833d7 --- /dev/null +++ b/test/isElement.test.js @@ -0,0 +1,58 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { document, body, falsey, stubFalse, args, slice, symbol, realm } from './utils.js'; +import isElement from '../isElement.js'; + +describe('isElement', function() { + it('should return `true` for elements', function() { + if (document) { + assert.strictEqual(isElement(body), true); + } + }); + + it('should return `true` for non-plain objects', function() { + function Foo() { + this.nodeType = 1; + } + + assert.strictEqual(isElement(new Foo), true); + }); + + it('should return `false` for non DOM elements', function() { + var expected = lodashStable.map(falsey, stubFalse); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isElement(value) : isElement(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isElement(args), false); + assert.strictEqual(isElement([1, 2, 3]), false); + assert.strictEqual(isElement(true), false); + assert.strictEqual(isElement(new Date), false); + assert.strictEqual(isElement(new Error), false); + assert.strictEqual(isElement(_), false); + assert.strictEqual(isElement(slice), false); + assert.strictEqual(isElement({ 'a': 1 }), false); + assert.strictEqual(isElement(1), false); + assert.strictEqual(isElement(/x/), false); + assert.strictEqual(isElement('a'), false); + assert.strictEqual(isElement(symbol), false); + }); + + it('should return `false` for plain objects', function() { + assert.strictEqual(isElement({ 'nodeType': 1 }), false); + assert.strictEqual(isElement({ 'nodeType': Object(1) }), false); + assert.strictEqual(isElement({ 'nodeType': true }), false); + assert.strictEqual(isElement({ 'nodeType': [1] }), false); + assert.strictEqual(isElement({ 'nodeType': '1' }), false); + assert.strictEqual(isElement({ 'nodeType': '001' }), false); + }); + + it('should work with a DOM element from another realm', function() { + if (realm.element) { + assert.strictEqual(isElement(realm.element), true); + } + }); +}); diff --git a/test/isEmpty.js b/test/isEmpty.js new file mode 100644 index 000000000..05c8770ab --- /dev/null +++ b/test/isEmpty.js @@ -0,0 +1,119 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; + +import { + empties, + stubTrue, + slice, + symbol, + args, + push, + arrayProto, + realm, + MAX_SAFE_INTEGER, +} from './utils.js'; + +import isEmpty from '../isEmpty.js'; + +describe('isEmpty', function() { + it('should return `true` for empty values', function() { + var expected = lodashStable.map(empties, stubTrue), + actual = lodashStable.map(empties, isEmpty); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isEmpty(true), true); + assert.strictEqual(isEmpty(slice), true); + assert.strictEqual(isEmpty(1), true); + assert.strictEqual(isEmpty(NaN), true); + assert.strictEqual(isEmpty(/x/), true); + assert.strictEqual(isEmpty(symbol), true); + assert.strictEqual(isEmpty(), true); + + if (Buffer) { + assert.strictEqual(isEmpty(new Buffer(0)), true); + assert.strictEqual(isEmpty(new Buffer(1)), false); + } + }); + + it('should return `false` for non-empty values', function() { + assert.strictEqual(isEmpty([0]), false); + assert.strictEqual(isEmpty({ 'a': 0 }), false); + assert.strictEqual(isEmpty('a'), false); + }); + + it('should work with an object that has a `length` property', function() { + assert.strictEqual(isEmpty({ 'length': 0 }), false); + }); + + it('should work with `arguments` objects', function() { + assert.strictEqual(isEmpty(args), false); + }); + + it('should work with prototype objects', function() { + function Foo() {} + Foo.prototype = { 'constructor': Foo }; + + assert.strictEqual(isEmpty(Foo.prototype), true); + + Foo.prototype.a = 1; + assert.strictEqual(isEmpty(Foo.prototype), false); + }); + + it('should work with jQuery/MooTools DOM query collections', function() { + function Foo(elements) { + push.apply(this, elements); + } + Foo.prototype = { 'length': 0, 'splice': arrayProto.splice }; + + assert.strictEqual(isEmpty(new Foo([])), true); + }); + + it('should work with maps', function() { + if (Map) { + lodashStable.each([new Map, realm.map], function(map) { + assert.strictEqual(isEmpty(map), true); + map.set('a', 1); + assert.strictEqual(isEmpty(map), false); + map.clear(); + }); + } + }); + + it('should work with sets', function() { + if (Set) { + lodashStable.each([new Set, realm.set], function(set) { + assert.strictEqual(isEmpty(set), true); + set.add(1); + assert.strictEqual(isEmpty(set), false); + set.clear(); + }); + } + }); + + it('should not treat objects with negative lengths as array-like', function() { + function Foo() {} + Foo.prototype.length = -1; + + assert.strictEqual(isEmpty(new Foo), true); + }); + + it('should not treat objects with lengths larger than `MAX_SAFE_INTEGER` as array-like', function() { + function Foo() {} + Foo.prototype.length = MAX_SAFE_INTEGER + 1; + + assert.strictEqual(isEmpty(new Foo), true); + }); + + it('should not treat objects with non-number lengths as array-like', function() { + assert.strictEqual(isEmpty({ 'length': '0' }), false); + }); + + it('should return an unwrapped value when implicitly chaining', function() { + assert.strictEqual(_({}).isEmpty(), true); + }); + + it('should return a wrapped value when explicitly chaining', function() { + assert.ok(_({}).chain().isEmpty() instanceof _); + }); +}); diff --git a/test/isEqual.js b/test/isEqual.js new file mode 100644 index 000000000..66056d7e5 --- /dev/null +++ b/test/isEqual.js @@ -0,0 +1,700 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; + +import { + noop, + create, + args, + realm, + arrayViews, + map, + promise, + set, + defineProperty, + document, + stubFalse, +} from './utils.js'; + +import isEqual from '../isEqual.js'; + +describe('isEqual', function() { + var symbol1 = Symbol ? Symbol('a') : true, + symbol2 = Symbol ? Symbol('b') : false; + + it('should compare primitives', function() { + var pairs = [ + [1, 1, true], [1, Object(1), true], [1, '1', false], [1, 2, false], + [-0, -0, true], [0, 0, true], [0, Object(0), true], [Object(0), Object(0), true], [-0, 0, true], [0, '0', false], [0, null, false], + [NaN, NaN, true], [NaN, Object(NaN), true], [Object(NaN), Object(NaN), true], [NaN, 'a', false], [NaN, Infinity, false], + ['a', 'a', true], ['a', Object('a'), true], [Object('a'), Object('a'), true], ['a', 'b', false], ['a', ['a'], false], + [true, true, true], [true, Object(true), true], [Object(true), Object(true), true], [true, 1, false], [true, 'a', false], + [false, false, true], [false, Object(false), true], [Object(false), Object(false), true], [false, 0, false], [false, '', false], + [symbol1, symbol1, true], [symbol1, Object(symbol1), true], [Object(symbol1), Object(symbol1), true], [symbol1, symbol2, false], + [null, null, true], [null, undefined, false], [null, {}, false], [null, '', false], + [undefined, undefined, true], [undefined, null, false], [undefined, '', false] + ]; + + var expected = lodashStable.map(pairs, function(pair) { + return pair[2]; + }); + + var actual = lodashStable.map(pairs, function(pair) { + return isEqual(pair[0], pair[1]); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should compare arrays', function() { + var array1 = [true, null, 1, 'a', undefined], + array2 = [true, null, 1, 'a', undefined]; + + assert.strictEqual(isEqual(array1, array2), true); + + array1 = [[1, 2, 3], new Date(2012, 4, 23), /x/, { 'e': 1 }]; + array2 = [[1, 2, 3], new Date(2012, 4, 23), /x/, { 'e': 1 }]; + + assert.strictEqual(isEqual(array1, array2), true); + + array1 = [1]; + array1[2] = 3; + + array2 = [1]; + array2[1] = undefined; + array2[2] = 3; + + assert.strictEqual(isEqual(array1, array2), true); + + array1 = [Object(1), false, Object('a'), /x/, new Date(2012, 4, 23), ['a', 'b', [Object('c')]], { 'a': 1 }]; + array2 = [1, Object(false), 'a', /x/, new Date(2012, 4, 23), ['a', Object('b'), ['c']], { 'a': 1 }]; + + assert.strictEqual(isEqual(array1, array2), true); + + array1 = [1, 2, 3]; + array2 = [3, 2, 1]; + + assert.strictEqual(isEqual(array1, array2), false); + + array1 = [1, 2]; + array2 = [1, 2, 3]; + + assert.strictEqual(isEqual(array1, array2), false); + }); + + it('should treat arrays with identical values but different non-index properties as equal', function() { + var array1 = [1, 2, 3], + array2 = [1, 2, 3]; + + array1.every = array1.filter = array1.forEach = + array1.indexOf = array1.lastIndexOf = array1.map = + array1.some = array1.reduce = array1.reduceRight = null; + + array2.concat = array2.join = array2.pop = + array2.reverse = array2.shift = array2.slice = + array2.sort = array2.splice = array2.unshift = null; + + assert.strictEqual(isEqual(array1, array2), true); + + array1 = [1, 2, 3]; + array1.a = 1; + + array2 = [1, 2, 3]; + array2.b = 1; + + assert.strictEqual(isEqual(array1, array2), true); + + array1 = /c/.exec('abcde'); + array2 = ['c']; + + assert.strictEqual(isEqual(array1, array2), true); + }); + + it('should compare sparse arrays', function() { + var array = Array(1); + + assert.strictEqual(isEqual(array, Array(1)), true); + assert.strictEqual(isEqual(array, [undefined]), true); + assert.strictEqual(isEqual(array, Array(2)), false); + }); + + it('should compare plain objects', function() { + var object1 = { 'a': true, 'b': null, 'c': 1, 'd': 'a', 'e': undefined }, + object2 = { 'a': true, 'b': null, 'c': 1, 'd': 'a', 'e': undefined }; + + assert.strictEqual(isEqual(object1, object2), true); + + object1 = { 'a': [1, 2, 3], 'b': new Date(2012, 4, 23), 'c': /x/, 'd': { 'e': 1 } }; + object2 = { 'a': [1, 2, 3], 'b': new Date(2012, 4, 23), 'c': /x/, 'd': { 'e': 1 } }; + + assert.strictEqual(isEqual(object1, object2), true); + + object1 = { 'a': 1, 'b': 2, 'c': 3 }; + object2 = { 'a': 3, 'b': 2, 'c': 1 }; + + assert.strictEqual(isEqual(object1, object2), false); + + object1 = { 'a': 1, 'b': 2, 'c': 3 }; + object2 = { 'd': 1, 'e': 2, 'f': 3 }; + + assert.strictEqual(isEqual(object1, object2), false); + + object1 = { 'a': 1, 'b': 2 }; + object2 = { 'a': 1, 'b': 2, 'c': 3 }; + + assert.strictEqual(isEqual(object1, object2), false); + }); + + it('should compare objects regardless of key order', function() { + var object1 = { 'a': 1, 'b': 2, 'c': 3 }, + object2 = { 'c': 3, 'a': 1, 'b': 2 }; + + assert.strictEqual(isEqual(object1, object2), true); + }); + + it('should compare nested objects', function() { + var object1 = { + 'a': [1, 2, 3], + 'b': true, + 'c': Object(1), + 'd': 'a', + 'e': { + 'f': ['a', Object('b'), 'c'], + 'g': Object(false), + 'h': new Date(2012, 4, 23), + 'i': noop, + 'j': 'a' + } + }; + + var object2 = { + 'a': [1, Object(2), 3], + 'b': Object(true), + 'c': 1, + 'd': Object('a'), + 'e': { + 'f': ['a', 'b', 'c'], + 'g': false, + 'h': new Date(2012, 4, 23), + 'i': noop, + 'j': 'a' + } + }; + + assert.strictEqual(isEqual(object1, object2), true); + }); + + it('should compare object instances', function() { + function Foo() { + this.a = 1; + } + Foo.prototype.a = 1; + + function Bar() { + this.a = 1; + } + Bar.prototype.a = 2; + + assert.strictEqual(isEqual(new Foo, new Foo), true); + assert.strictEqual(isEqual(new Foo, new Bar), false); + assert.strictEqual(isEqual({ 'a': 1 }, new Foo), false); + assert.strictEqual(isEqual({ 'a': 2 }, new Bar), false); + }); + + it('should compare objects with constructor properties', function() { + assert.strictEqual(isEqual({ 'constructor': 1 }, { 'constructor': 1 }), true); + assert.strictEqual(isEqual({ 'constructor': 1 }, { 'constructor': '1' }), false); + assert.strictEqual(isEqual({ 'constructor': [1] }, { 'constructor': [1] }), true); + assert.strictEqual(isEqual({ 'constructor': [1] }, { 'constructor': ['1'] }), false); + assert.strictEqual(isEqual({ 'constructor': Object }, {}), false); + }); + + it('should compare arrays with circular references', function() { + var array1 = [], + array2 = []; + + array1.push(array1); + array2.push(array2); + + assert.strictEqual(isEqual(array1, array2), true); + + array1.push('b'); + array2.push('b'); + + assert.strictEqual(isEqual(array1, array2), true); + + array1.push('c'); + array2.push('d'); + + assert.strictEqual(isEqual(array1, array2), false); + + array1 = ['a', 'b', 'c']; + array1[1] = array1; + array2 = ['a', ['a', 'b', 'c'], 'c']; + + assert.strictEqual(isEqual(array1, array2), false); + }); + + it('should have transitive equivalence for circular references of arrays', function() { + var array1 = [], + array2 = [array1], + array3 = [array2]; + + array1[0] = array1; + + assert.strictEqual(isEqual(array1, array2), true); + assert.strictEqual(isEqual(array2, array3), true); + assert.strictEqual(isEqual(array1, array3), true); + }); + + it('should compare objects with circular references', function() { + var object1 = {}, + object2 = {}; + + object1.a = object1; + object2.a = object2; + + assert.strictEqual(isEqual(object1, object2), true); + + object1.b = 0; + object2.b = Object(0); + + assert.strictEqual(isEqual(object1, object2), true); + + object1.c = Object(1); + object2.c = Object(2); + + assert.strictEqual(isEqual(object1, object2), false); + + object1 = { 'a': 1, 'b': 2, 'c': 3 }; + object1.b = object1; + object2 = { 'a': 1, 'b': { 'a': 1, 'b': 2, 'c': 3 }, 'c': 3 }; + + assert.strictEqual(isEqual(object1, object2), false); + }); + + it('should have transitive equivalence for circular references of objects', function() { + var object1 = {}, + object2 = { 'a': object1 }, + object3 = { 'a': object2 }; + + object1.a = object1; + + assert.strictEqual(isEqual(object1, object2), true); + assert.strictEqual(isEqual(object2, object3), true); + assert.strictEqual(isEqual(object1, object3), true); + }); + + it('should compare objects with multiple circular references', function() { + var array1 = [{}], + array2 = [{}]; + + (array1[0].a = array1).push(array1); + (array2[0].a = array2).push(array2); + + assert.strictEqual(isEqual(array1, array2), true); + + array1[0].b = 0; + array2[0].b = Object(0); + + assert.strictEqual(isEqual(array1, array2), true); + + array1[0].c = Object(1); + array2[0].c = Object(2); + + assert.strictEqual(isEqual(array1, array2), false); + }); + + it('should compare objects with complex circular references', function() { + var object1 = { + 'foo': { 'b': { 'c': { 'd': {} } } }, + 'bar': { 'a': 2 } + }; + + var object2 = { + 'foo': { 'b': { 'c': { 'd': {} } } }, + 'bar': { 'a': 2 } + }; + + object1.foo.b.c.d = object1; + object1.bar.b = object1.foo.b; + + object2.foo.b.c.d = object2; + object2.bar.b = object2.foo.b; + + assert.strictEqual(isEqual(object1, object2), true); + }); + + it('should compare objects with shared property values', function() { + var object1 = { + 'a': [1, 2] + }; + + var object2 = { + 'a': [1, 2], + 'b': [1, 2] + }; + + object1.b = object1.a; + + assert.strictEqual(isEqual(object1, object2), true); + }); + + it('should treat objects created by `Object.create(null)` like plain objects', function() { + function Foo() { + this.a = 1; + } + Foo.prototype.constructor = null; + + var object1 = create(null); + object1.a = 1; + + var object2 = { 'a': 1 }; + + assert.strictEqual(isEqual(object1, object2), true); + assert.strictEqual(isEqual(new Foo, object2), false); + }); + + it('should avoid common type coercions', function() { + assert.strictEqual(isEqual(true, Object(false)), false); + assert.strictEqual(isEqual(Object(false), Object(0)), false); + assert.strictEqual(isEqual(false, Object('')), false); + assert.strictEqual(isEqual(Object(36), Object('36')), false); + assert.strictEqual(isEqual(0, ''), false); + assert.strictEqual(isEqual(1, true), false); + assert.strictEqual(isEqual(1337756400000, new Date(2012, 4, 23)), false); + assert.strictEqual(isEqual('36', 36), false); + assert.strictEqual(isEqual(36, '36'), false); + }); + + it('should compare `arguments` objects', function() { + var args1 = (function() { return arguments; }()), + args2 = (function() { return arguments; }()), + args3 = (function() { return arguments; }(1, 2)); + + assert.strictEqual(isEqual(args1, args2), true); + assert.strictEqual(isEqual(args1, args3), false); + }); + + it('should treat `arguments` objects like `Object` objects', function() { + var object = { '0': 1, '1': 2, '2': 3 }; + + function Foo() {} + Foo.prototype = object; + + assert.strictEqual(isEqual(args, object), true); + assert.strictEqual(isEqual(object, args), true); + assert.strictEqual(isEqual(args, new Foo), false); + assert.strictEqual(isEqual(new Foo, args), false); + }); + + it('should compare array buffers', function() { + if (ArrayBuffer) { + var buffer = new Int8Array([-1]).buffer; + + assert.strictEqual(isEqual(buffer, new Uint8Array([255]).buffer), true); + assert.strictEqual(isEqual(buffer, new ArrayBuffer(1)), false); + } + }); + + it('should compare array views', function() { + lodashStable.times(2, function(index) { + var ns = index ? realm : root; + + var pairs = lodashStable.map(arrayViews, function(type, viewIndex) { + var otherType = arrayViews[(viewIndex + 1) % arrayViews.length], + CtorA = ns[type] || function(n) { this.n = n; }, + CtorB = ns[otherType] || function(n) { this.n = n; }, + bufferA = ns[type] ? new ns.ArrayBuffer(8) : 8, + bufferB = ns[otherType] ? new ns.ArrayBuffer(8) : 8, + bufferC = ns[otherType] ? new ns.ArrayBuffer(16) : 16; + + return [new CtorA(bufferA), new CtorA(bufferA), new CtorB(bufferB), new CtorB(bufferC)]; + }); + + var expected = lodashStable.map(pairs, lodashStable.constant([true, false, false])); + + var actual = lodashStable.map(pairs, function(pair) { + return [isEqual(pair[0], pair[1]), isEqual(pair[0], pair[2]), isEqual(pair[2], pair[3])]; + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('should compare buffers', function() { + if (Buffer) { + var buffer = new Buffer([1]); + + assert.strictEqual(isEqual(buffer, new Buffer([1])), true); + assert.strictEqual(isEqual(buffer, new Buffer([2])), false); + assert.strictEqual(isEqual(buffer, new Uint8Array([1])), false); + } + }); + + it('should compare date objects', function() { + var date = new Date(2012, 4, 23); + + assert.strictEqual(isEqual(date, new Date(2012, 4, 23)), true); + assert.strictEqual(isEqual(new Date('a'), new Date('b')), true); + assert.strictEqual(isEqual(date, new Date(2013, 3, 25)), false); + assert.strictEqual(isEqual(date, { 'getTime': lodashStable.constant(+date) }), false); + }); + + it('should compare error objects', function() { + var pairs = lodashStable.map([ + 'Error', + 'EvalError', + 'RangeError', + 'ReferenceError', + 'SyntaxError', + 'TypeError', + 'URIError' + ], function(type, index, errorTypes) { + var otherType = errorTypes[++index % errorTypes.length], + CtorA = root[type], + CtorB = root[otherType]; + + return [new CtorA('a'), new CtorA('a'), new CtorB('a'), new CtorB('b')]; + }); + + var expected = lodashStable.map(pairs, lodashStable.constant([true, false, false])); + + var actual = lodashStable.map(pairs, function(pair) { + return [isEqual(pair[0], pair[1]), isEqual(pair[0], pair[2]), isEqual(pair[2], pair[3])]; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should compare functions', function() { + function a() { return 1 + 2; } + function b() { return 1 + 2; } + + assert.strictEqual(isEqual(a, a), true); + assert.strictEqual(isEqual(a, b), false); + }); + + it('should compare maps', function() { + if (Map) { + lodashStable.each([[map, new Map], [map, realm.map]], function(maps) { + var map1 = maps[0], + map2 = maps[1]; + + map1.set('a', 1); + map2.set('b', 2); + assert.strictEqual(isEqual(map1, map2), false); + + map1.set('b', 2); + map2.set('a', 1); + assert.strictEqual(isEqual(map1, map2), true); + + map1.delete('a'); + map1.set('a', 1); + assert.strictEqual(isEqual(map1, map2), true); + + map2.delete('a'); + assert.strictEqual(isEqual(map1, map2), false); + + map1.clear(); + map2.clear(); + }); + } + }); + + it('should compare maps with circular references', function() { + if (Map) { + var map1 = new Map, + map2 = new Map; + + map1.set('a', map1); + map2.set('a', map2); + assert.strictEqual(isEqual(map1, map2), true); + + map1.set('b', 1); + map2.set('b', 2); + assert.strictEqual(isEqual(map1, map2), false); + } + }); + + it('should compare promises by reference', function() { + if (promise) { + lodashStable.each([[promise, Promise.resolve(1)], [promise, realm.promise]], function(promises) { + var promise1 = promises[0], + promise2 = promises[1]; + + assert.strictEqual(isEqual(promise1, promise2), false); + assert.strictEqual(isEqual(promise1, promise1), true); + }); + } + }); + + it('should compare regexes', function() { + assert.strictEqual(isEqual(/x/gim, /x/gim), true); + assert.strictEqual(isEqual(/x/gim, /x/mgi), true); + assert.strictEqual(isEqual(/x/gi, /x/g), false); + assert.strictEqual(isEqual(/x/, /y/), false); + assert.strictEqual(isEqual(/x/g, { 'global': true, 'ignoreCase': false, 'multiline': false, 'source': 'x' }), false); + }); + + it('should compare sets', function() { + if (Set) { + lodashStable.each([[set, new Set], [set, realm.set]], function(sets) { + var set1 = sets[0], + set2 = sets[1]; + + set1.add(1); + set2.add(2); + assert.strictEqual(isEqual(set1, set2), false); + + set1.add(2); + set2.add(1); + assert.strictEqual(isEqual(set1, set2), true); + + set1.delete(1); + set1.add(1); + assert.strictEqual(isEqual(set1, set2), true); + + set2.delete(1); + assert.strictEqual(isEqual(set1, set2), false); + + set1.clear(); + set2.clear(); + }); + } + }); + + it('should compare sets with circular references', function() { + if (Set) { + var set1 = new Set, + set2 = new Set; + + set1.add(set1); + set2.add(set2); + assert.strictEqual(isEqual(set1, set2), true); + + set1.add(1); + set2.add(2); + assert.strictEqual(isEqual(set1, set2), false); + } + }); + + it('should compare symbol properties', function() { + if (Symbol) { + var object1 = { 'a': 1 }, + object2 = { 'a': 1 }; + + object1[symbol1] = { 'a': { 'b': 2 } }; + object2[symbol1] = { 'a': { 'b': 2 } }; + + defineProperty(object2, symbol2, { + 'configurable': true, + 'enumerable': false, + 'writable': true, + 'value': 2 + }); + + assert.strictEqual(isEqual(object1, object2), true); + + object2[symbol1] = { 'a': 1 }; + assert.strictEqual(isEqual(object1, object2), false); + + delete object2[symbol1]; + object2[Symbol('a')] = { 'a': { 'b': 2 } }; + assert.strictEqual(isEqual(object1, object2), false); + } + }); + + it('should compare wrapped values', function() { + var stamp = +new Date; + + var values = [ + [[1, 2], [1, 2], [1, 2, 3]], + [true, true, false], + [new Date(stamp), new Date(stamp), new Date(stamp - 100)], + [{ 'a': 1, 'b': 2 }, { 'a': 1, 'b': 2 }, { 'a': 1, 'b': 1 }], + [1, 1, 2], + [NaN, NaN, Infinity], + [/x/, /x/, /x/i], + ['a', 'a', 'A'] + ]; + + lodashStable.each(values, function(vals) { + var wrapped1 = _(vals[0]), + wrapped2 = _(vals[1]), + actual = wrapped1.isEqual(wrapped2); + + assert.strictEqual(actual, true); + assert.strictEqual(isEqual(_(actual), _(true)), true); + + wrapped1 = _(vals[0]); + wrapped2 = _(vals[2]); + + actual = wrapped1.isEqual(wrapped2); + assert.strictEqual(actual, false); + assert.strictEqual(isEqual(_(actual), _(false)), true); + }); + }); + + it('should compare wrapped and non-wrapped values', function() { + var object1 = _({ 'a': 1, 'b': 2 }), + object2 = { 'a': 1, 'b': 2 }; + + assert.strictEqual(object1.isEqual(object2), true); + assert.strictEqual(isEqual(object1, object2), true); + + object1 = _({ 'a': 1, 'b': 2 }); + object2 = { 'a': 1, 'b': 1 }; + + assert.strictEqual(object1.isEqual(object2), false); + assert.strictEqual(isEqual(object1, object2), false); + }); + + it('should work as an iteratee for `_.every`', function() { + var actual = lodashStable.every([1, 1, 1], lodashStable.partial(isEqual, 1)); + assert.ok(actual); + }); + + it('should not error on DOM elements', function() { + if (document) { + var element1 = document.createElement('div'), + element2 = element1.cloneNode(true); + + try { + assert.strictEqual(isEqual(element1, element2), false); + } catch (e) { + assert.ok(false, e.message); + } + } + }); + + it('should return `true` for like-objects from different documents', function() { + if (realm.object) { + assert.strictEqual(isEqual([1], realm.array), true); + assert.strictEqual(isEqual([2], realm.array), false); + assert.strictEqual(isEqual({ 'a': 1 }, realm.object), true); + assert.strictEqual(isEqual({ 'a': 2 }, realm.object), false); + } + }); + + it('should return `false` for objects with custom `toString` methods', function() { + var primitive, + object = { 'toString': function() { return primitive; } }, + values = [true, null, 1, 'a', undefined], + expected = lodashStable.map(values, stubFalse); + + var actual = lodashStable.map(values, function(value) { + primitive = value; + return isEqual(object, value); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return an unwrapped value when implicitly chaining', function() { + assert.strictEqual(_('a').isEqual('a'), true); + }); + + it('should return a wrapped value when explicitly chaining', function() { + assert.ok(_('a').chain().isEqual('a') instanceof _); + }); +}); diff --git a/test/isEqualWith.js b/test/isEqualWith.js new file mode 100644 index 000000000..312e3e485 --- /dev/null +++ b/test/isEqualWith.js @@ -0,0 +1,128 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice, noop, stubC, falsey, stubFalse } from './utils.js'; +import isEqualWith from '../isEqualWith.js'; +import isString from '../isString.js'; +import without from '../without.js'; +import partial from '../partial.js'; + +describe('isEqualWith', function() { + it('should provide correct `customizer` arguments', function() { + var argsList = [], + object1 = { 'a': [1, 2], 'b': null }, + object2 = { 'a': [1, 2], 'b': null }; + + object1.b = object2; + object2.b = object1; + + var expected = [ + [object1, object2], + [object1.a, object2.a, 'a', object1, object2], + [object1.a[0], object2.a[0], 0, object1.a, object2.a], + [object1.a[1], object2.a[1], 1, object1.a, object2.a], + [object1.b, object2.b, 'b', object1.b, object2.b] + ]; + + isEqualWith(object1, object2, function() { + var length = arguments.length, + args = slice.call(arguments, 0, length - (length > 2 ? 1 : 0)); + + argsList.push(args); + }); + + assert.deepStrictEqual(argsList, expected); + }); + + it('should handle comparisons when `customizer` returns `undefined`', function() { + assert.strictEqual(isEqualWith('a', 'a', noop), true); + assert.strictEqual(isEqualWith(['a'], ['a'], noop), true); + assert.strictEqual(isEqualWith({ '0': 'a' }, { '0': 'a' }, noop), true); + }); + + it('should not handle comparisons when `customizer` returns `true`', function() { + var customizer = function(value) { + return isString(value) || undefined; + }; + + assert.strictEqual(isEqualWith('a', 'b', customizer), true); + assert.strictEqual(isEqualWith(['a'], ['b'], customizer), true); + assert.strictEqual(isEqualWith({ '0': 'a' }, { '0': 'b' }, customizer), true); + }); + + it('should not handle comparisons when `customizer` returns `false`', function() { + var customizer = function(value) { + return isString(value) ? false : undefined; + }; + + assert.strictEqual(isEqualWith('a', 'a', customizer), false); + assert.strictEqual(isEqualWith(['a'], ['a'], customizer), false); + assert.strictEqual(isEqualWith({ '0': 'a' }, { '0': 'a' }, customizer), false); + }); + + it('should return a boolean value even when `customizer` does not', function() { + var actual = isEqualWith('a', 'b', stubC); + assert.strictEqual(actual, true); + + var values = without(falsey, undefined), + expected = lodashStable.map(values, stubFalse); + + actual = []; + lodashStable.each(values, function(value) { + actual.push(isEqualWith('a', 'a', lodashStable.constant(value))); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should ensure `customizer` is a function', function() { + var array = [1, 2, 3], + eq = partial(isEqualWith, array), + actual = lodashStable.map([array, [1, 0, 3]], eq); + + assert.deepStrictEqual(actual, [true, false]); + }); + + it('should call `customizer` for values maps and sets', function() { + var value = { 'a': { 'b': 2 } }; + + if (Map) { + var map1 = new Map; + map1.set('a', value); + + var map2 = new Map; + map2.set('a', value); + } + if (Set) { + var set1 = new Set; + set1.add(value); + + var set2 = new Set; + set2.add(value); + } + lodashStable.each([[map1, map2], [set1, set2]], function(pair, index) { + if (pair[0]) { + var argsList = [], + array = lodashStable.toArray(pair[0]); + + var expected = [ + [pair[0], pair[1]], + [array[0], array[0], 0, array, array], + [array[0][0], array[0][0], 0, array[0], array[0]], + [array[0][1], array[0][1], 1, array[0], array[0]] + ]; + + if (index) { + expected.length = 2; + } + isEqualWith(pair[0], pair[1], function() { + var length = arguments.length, + args = slice.call(arguments, 0, length - (length > 2 ? 1 : 0)); + + argsList.push(args); + }); + + assert.deepStrictEqual(argsList, expected, index ? 'Set' : 'Map'); + } + }); + }); +}); diff --git a/test/isError.test.js b/test/isError.test.js new file mode 100644 index 000000000..0805b588e --- /dev/null +++ b/test/isError.test.js @@ -0,0 +1,70 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; + +import { + errors, + stubTrue, + CustomError, + falsey, + stubFalse, + args, + slice, + symbol, + realm, +} from './utils.js'; + +import isError from '../isError.js'; + +describe('isError', function() { + it('should return `true` for error objects', function() { + var expected = lodashStable.map(errors, stubTrue); + + var actual = lodashStable.map(errors, function(error) { + return isError(error) === true; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return `true` for subclassed values', function() { + assert.strictEqual(isError(new CustomError('x')), true); + }); + + it('should return `false` for non error objects', function() { + var expected = lodashStable.map(falsey, stubFalse); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isError(value) : isError(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isError(args), false); + assert.strictEqual(isError([1, 2, 3]), false); + assert.strictEqual(isError(true), false); + assert.strictEqual(isError(new Date), false); + assert.strictEqual(isError(_), false); + assert.strictEqual(isError(slice), false); + assert.strictEqual(isError({ 'a': 1 }), false); + assert.strictEqual(isError(1), false); + assert.strictEqual(isError(/x/), false); + assert.strictEqual(isError('a'), false); + assert.strictEqual(isError(symbol), false); + }); + + it('should return `false` for plain objects', function() { + assert.strictEqual(isError({ 'name': 'Error', 'message': '' }), false); + }); + + it('should work with an error object from another realm', function() { + if (realm.errors) { + var expected = lodashStable.map(realm.errors, stubTrue); + + var actual = lodashStable.map(realm.errors, function(error) { + return isError(error) === true; + }); + + assert.deepStrictEqual(actual, expected); + } + }); +}); diff --git a/test/isFinite.js b/test/isFinite.js new file mode 100644 index 000000000..f5c8ec688 --- /dev/null +++ b/test/isFinite.js @@ -0,0 +1,48 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { stubTrue, stubFalse, args, symbol } from './utils.js'; +import isFinite from '../isFinite.js'; + +describe('isFinite', function() { + it('should return `true` for finite values', function() { + var values = [0, 1, 3.14, -1], + expected = lodashStable.map(values, stubTrue), + actual = lodashStable.map(values, isFinite); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return `false` for non-finite values', function() { + var values = [NaN, Infinity, -Infinity, Object(1)], + expected = lodashStable.map(values, stubFalse), + actual = lodashStable.map(values, isFinite); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return `false` for non-numeric values', function() { + var values = [undefined, [], true, '', ' ', '2px'], + expected = lodashStable.map(values, stubFalse), + actual = lodashStable.map(values, isFinite); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isFinite(args), false); + assert.strictEqual(isFinite([1, 2, 3]), false); + assert.strictEqual(isFinite(true), false); + assert.strictEqual(isFinite(new Date), false); + assert.strictEqual(isFinite(new Error), false); + assert.strictEqual(isFinite({ 'a': 1 }), false); + assert.strictEqual(isFinite(/x/), false); + assert.strictEqual(isFinite('a'), false); + assert.strictEqual(isFinite(symbol), false); + }); + + it('should return `false` for numeric string values', function() { + var values = ['2', '0', '08'], + expected = lodashStable.map(values, stubFalse), + actual = lodashStable.map(values, isFinite); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/isFunction.js b/test/isFunction.js new file mode 100644 index 000000000..6c4b5c01f --- /dev/null +++ b/test/isFunction.js @@ -0,0 +1,83 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; + +import { + slice, + asyncFunc, + genFunc, + arrayViews, + objToString, + funcTag, + falsey, + stubFalse, + args, + symbol, + document, + realm, +} from './utils.js'; + +import isFunction from '../isFunction.js'; + +describe('isFunction', function() { + it('should return `true` for functions', function() { + assert.strictEqual(isFunction(_), true); + assert.strictEqual(isFunction(slice), true); + }); + + it('should return `true` for async functions', function() { + assert.strictEqual(isFunction(asyncFunc), typeof asyncFunc == 'function'); + }); + + it('should return `true` for generator functions', function() { + assert.strictEqual(isFunction(genFunc), typeof genFunc == 'function'); + }); + + it('should return `true` for the `Proxy` constructor', function() { + if (Proxy) { + assert.strictEqual(isFunction(Proxy), true); + } + }); + + it('should return `true` for array view constructors', function() { + var expected = lodashStable.map(arrayViews, function(type) { + return objToString.call(root[type]) == funcTag; + }); + + var actual = lodashStable.map(arrayViews, function(type) { + return isFunction(root[type]); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return `false` for non-functions', function() { + var expected = lodashStable.map(falsey, stubFalse); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isFunction(value) : isFunction(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isFunction(args), false); + assert.strictEqual(isFunction([1, 2, 3]), false); + assert.strictEqual(isFunction(true), false); + assert.strictEqual(isFunction(new Date), false); + assert.strictEqual(isFunction(new Error), false); + assert.strictEqual(isFunction({ 'a': 1 }), false); + assert.strictEqual(isFunction(1), false); + assert.strictEqual(isFunction(/x/), false); + assert.strictEqual(isFunction('a'), false); + assert.strictEqual(isFunction(symbol), false); + + if (document) { + assert.strictEqual(isFunction(document.getElementsByTagName('body')), false); + } + }); + + it('should work with a function from another realm', function() { + if (realm.function) { + assert.strictEqual(isFunction(realm.function), true); + } + }); +}); diff --git a/test/isIndex.test.js b/test/isIndex.test.js new file mode 100644 index 000000000..486eef29e --- /dev/null +++ b/test/isIndex.test.js @@ -0,0 +1,34 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { MAX_SAFE_INTEGER, stubTrue, stubFalse } from './utils.js'; +import _isIndex from '../.internal/isIndex.js'; + +describe('isIndex', function() { + var func = _isIndex; + + it('should return `true` for indexes', function() { + if (func) { + var values = [[0], ['0'], ['1'], [3, 4], [MAX_SAFE_INTEGER - 1]], + expected = lodashStable.map(values, stubTrue); + + var actual = lodashStable.map(values, function(args) { + return func.apply(undefined, args); + }); + + assert.deepStrictEqual(actual, expected); + } + }); + + it('should return `false` for non-indexes', function() { + if (func) { + var values = [['1abc'], ['07'], ['0001'], [-1], [3, 3], [1.1], [MAX_SAFE_INTEGER]], + expected = lodashStable.map(values, stubFalse); + + var actual = lodashStable.map(values, function(args) { + return func.apply(undefined, args); + }); + + assert.deepStrictEqual(actual, expected); + } + }); +}); diff --git a/test/isInteger-methods.js b/test/isInteger-methods.js new file mode 100644 index 000000000..9c12c6840 --- /dev/null +++ b/test/isInteger-methods.js @@ -0,0 +1,55 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, stubTrue, MAX_INTEGER, stubFalse, falsey, args, symbol } from './utils.js'; + +describe('isInteger methods', function() { + lodashStable.each(['isInteger', 'isSafeInteger'], function(methodName) { + var func = _[methodName], + isSafe = methodName == 'isSafeInteger'; + + it('`_.' + methodName + '` should return `true` for integer values', function() { + var values = [-1, 0, 1], + expected = lodashStable.map(values, stubTrue); + + var actual = lodashStable.map(values, function(value) { + return func(value); + }); + + assert.deepStrictEqual(actual, expected); + assert.strictEqual(func(MAX_INTEGER), !isSafe); + }); + + it('should return `false` for non-integer number values', function() { + var values = [NaN, Infinity, -Infinity, Object(1), 3.14], + expected = lodashStable.map(values, stubFalse); + + var actual = lodashStable.map(values, function(value) { + return func(value); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return `false` for non-numeric values', function() { + var expected = lodashStable.map(falsey, function(value) { + return value === 0; + }); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? func(value) : func(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(func(args), false); + assert.strictEqual(func([1, 2, 3]), false); + assert.strictEqual(func(true), false); + assert.strictEqual(func(new Date), false); + assert.strictEqual(func(new Error), false); + assert.strictEqual(func({ 'a': 1 }), false); + assert.strictEqual(func(/x/), false); + assert.strictEqual(func('a'), false); + assert.strictEqual(func(symbol), false); + }); + }); +}); diff --git a/test/isIterateeCall.js b/test/isIterateeCall.js new file mode 100644 index 000000000..86b4de524 --- /dev/null +++ b/test/isIterateeCall.js @@ -0,0 +1,47 @@ +import assert from 'assert'; +import { MAX_SAFE_INTEGER } from './utils.js'; +import _isIterateeCall from '../.internal/isIterateeCall.js'; + +describe('isIterateeCall', function() { + var array = [1], + func = _isIterateeCall, + object = { 'a': 1 }; + + it('should return `true` for iteratee calls', function() { + function Foo() {} + Foo.prototype.a = 1; + + if (func) { + assert.strictEqual(func(1, 0, array), true); + assert.strictEqual(func(1, 'a', object), true); + assert.strictEqual(func(1, 'a', new Foo), true); + } + }); + + it('should return `false` for non-iteratee calls', function() { + if (func) { + assert.strictEqual(func(2, 0, array), false); + assert.strictEqual(func(1, 1.1, array), false); + assert.strictEqual(func(1, 0, { 'length': MAX_SAFE_INTEGER + 1 }), false); + assert.strictEqual(func(1, 'b', object), false); + } + }); + + it('should work with `NaN` values', function() { + if (func) { + assert.strictEqual(func(NaN, 0, [NaN]), true); + assert.strictEqual(func(NaN, 'a', { 'a': NaN }), true); + } + }); + + it('should not error when `index` is an object without a `toString` method', function() { + if (func) { + try { + var actual = func(1, { 'toString': null }, [1]); + } catch (e) { + var message = e.message; + } + assert.strictEqual(actual, false, message || ''); + } + }); +}); diff --git a/test/isLength.test.js b/test/isLength.test.js new file mode 100644 index 000000000..3978c0cb5 --- /dev/null +++ b/test/isLength.test.js @@ -0,0 +1,22 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { MAX_SAFE_INTEGER, stubTrue, stubFalse } from './utils.js'; +import isLength from '../isLength.js'; + +describe('isLength', function() { + it('should return `true` for lengths', function() { + var values = [0, 3, MAX_SAFE_INTEGER], + expected = lodashStable.map(values, stubTrue), + actual = lodashStable.map(values, isLength); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return `false` for non-lengths', function() { + var values = [-1, '1', 1.1, MAX_SAFE_INTEGER + 1], + expected = lodashStable.map(values, stubFalse), + actual = lodashStable.map(values, isLength); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/isMap.test.js b/test/isMap.test.js new file mode 100644 index 000000000..32fb80fff --- /dev/null +++ b/test/isMap.test.js @@ -0,0 +1,53 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { map, falsey, stubFalse, args, slice, symbol, weakMap, realm } from './utils.js'; +import isMap from '../isMap.js'; + +describe('isMap', function() { + it('should return `true` for maps', function() { + if (Map) { + assert.strictEqual(isMap(map), true); + } + }); + + it('should return `false` for non-maps', function() { + var expected = lodashStable.map(falsey, stubFalse); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isMap(value) : isMap(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isMap(args), false); + assert.strictEqual(isMap([1, 2, 3]), false); + assert.strictEqual(isMap(true), false); + assert.strictEqual(isMap(new Date), false); + assert.strictEqual(isMap(new Error), false); + assert.strictEqual(isMap(_), false); + assert.strictEqual(isMap(slice), false); + assert.strictEqual(isMap({ 'a': 1 }), false); + assert.strictEqual(isMap(1), false); + assert.strictEqual(isMap(/x/), false); + assert.strictEqual(isMap('a'), false); + assert.strictEqual(isMap(symbol), false); + assert.strictEqual(isMap(weakMap), false); + }); + + it('should work for objects with a non-function `constructor` (test in IE 11)', function() { + var values = [false, true], + expected = lodashStable.map(values, stubFalse); + + var actual = lodashStable.map(values, function(value) { + return isMap({ 'constructor': value }); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with maps from another realm', function() { + if (realm.map) { + assert.strictEqual(isMap(realm.map), true); + } + }); +}); diff --git a/test/isMatchWith.js b/test/isMatchWith.js new file mode 100644 index 000000000..a7941d658 --- /dev/null +++ b/test/isMatchWith.js @@ -0,0 +1,137 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice, noop, stubA, falsey, stubFalse, isNpm, mapCaches } from './utils.js'; +import isMatchWith from '../isMatchWith.js'; +import isString from '../isString.js'; +import last from '../last.js'; +import partial from '../partial.js'; + +describe('isMatchWith', function() { + it('should provide correct `customizer` arguments', function() { + var argsList = [], + object1 = { 'a': [1, 2], 'b': null }, + object2 = { 'a': [1, 2], 'b': null }; + + object1.b = object2; + object2.b = object1; + + var expected = [ + [object1.a, object2.a, 'a', object1, object2], + [object1.a[0], object2.a[0], 0, object1.a, object2.a], + [object1.a[1], object2.a[1], 1, object1.a, object2.a], + [object1.b, object2.b, 'b', object1, object2], + [object1.b.a, object2.b.a, 'a', object1.b, object2.b], + [object1.b.a[0], object2.b.a[0], 0, object1.b.a, object2.b.a], + [object1.b.a[1], object2.b.a[1], 1, object1.b.a, object2.b.a], + [object1.b.b, object2.b.b, 'b', object1.b, object2.b] + ]; + + isMatchWith(object1, object2, function() { + argsList.push(slice.call(arguments, 0, -1)); + }); + + assert.deepStrictEqual(argsList, expected); + }); + + it('should handle comparisons when `customizer` returns `undefined`', function() { + assert.strictEqual(isMatchWith({ 'a': 1 }, { 'a': 1 }, noop), true); + }); + + it('should not handle comparisons when `customizer` returns `true`', function() { + var customizer = function(value) { + return isString(value) || undefined; + }; + + assert.strictEqual(isMatchWith(['a'], ['b'], customizer), true); + assert.strictEqual(isMatchWith({ '0': 'a' }, { '0': 'b' }, customizer), true); + }); + + it('should not handle comparisons when `customizer` returns `false`', function() { + var customizer = function(value) { + return isString(value) ? false : undefined; + }; + + assert.strictEqual(isMatchWith(['a'], ['a'], customizer), false); + assert.strictEqual(isMatchWith({ '0': 'a' }, { '0': 'a' }, customizer), false); + }); + + it('should return a boolean value even when `customizer` does not', function() { + var object = { 'a': 1 }, + actual = isMatchWith(object, { 'a': 1 }, stubA); + + assert.strictEqual(actual, true); + + var expected = lodashStable.map(falsey, stubFalse); + + actual = []; + lodashStable.each(falsey, function(value) { + actual.push(isMatchWith(object, { 'a': 2 }, lodashStable.constant(value))); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should provide `stack` to `customizer`', function() { + var actual; + + isMatchWith({ 'a': 1 }, { 'a': 1 }, function() { + actual = last(arguments); + }); + + assert.ok(isNpm + ? actual.constructor.name == 'Stack' + : actual instanceof mapCaches.Stack + ); + }); + + it('should ensure `customizer` is a function', function() { + var object = { 'a': 1 }, + matches = partial(isMatchWith, object), + actual = lodashStable.map([object, { 'a': 2 }], matches); + + assert.deepStrictEqual(actual, [true, false]); + }); + + it('should call `customizer` for values maps and sets', function() { + var value = { 'a': { 'b': 2 } }; + + if (Map) { + var map1 = new Map; + map1.set('a', value); + + var map2 = new Map; + map2.set('a', value); + } + if (Set) { + var set1 = new Set; + set1.add(value); + + var set2 = new Set; + set2.add(value); + } + lodashStable.each([[map1, map2], [set1, set2]], function(pair, index) { + if (pair[0]) { + var argsList = [], + array = lodashStable.toArray(pair[0]), + object1 = { 'a': pair[0] }, + object2 = { 'a': pair[1] }; + + var expected = [ + [pair[0], pair[1], 'a', object1, object2], + [array[0], array[0], 0, array, array], + [array[0][0], array[0][0], 0, array[0], array[0]], + [array[0][1], array[0][1], 1, array[0], array[0]] + ]; + + if (index) { + expected.length = 2; + } + isMatchWith({ 'a': pair[0] }, { 'a': pair[1] }, function() { + argsList.push(slice.call(arguments, 0, -1)); + }); + + assert.deepStrictEqual(argsList, expected, index ? 'Set' : 'Map'); + } + }); + }); +}); diff --git a/test/isNaN.js b/test/isNaN.js new file mode 100644 index 000000000..27529eac3 --- /dev/null +++ b/test/isNaN.js @@ -0,0 +1,43 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, args, slice, symbol, realm } from './utils.js'; +import isNaN from '../isNaN.js'; + +describe('isNaN', function() { + it('should return `true` for NaNs', function() { + assert.strictEqual(isNaN(NaN), true); + assert.strictEqual(isNaN(Object(NaN)), true); + }); + + it('should return `false` for non-NaNs', function() { + var expected = lodashStable.map(falsey, function(value) { + return value !== value; + }); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isNaN(value) : isNaN(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isNaN(args), false); + assert.strictEqual(isNaN([1, 2, 3]), false); + assert.strictEqual(isNaN(true), false); + assert.strictEqual(isNaN(new Date), false); + assert.strictEqual(isNaN(new Error), false); + assert.strictEqual(isNaN(_), false); + assert.strictEqual(isNaN(slice), false); + assert.strictEqual(isNaN({ 'a': 1 }), false); + assert.strictEqual(isNaN(1), false); + assert.strictEqual(isNaN(Object(1)), false); + assert.strictEqual(isNaN(/x/), false); + assert.strictEqual(isNaN('a'), false); + assert.strictEqual(isNaN(symbol), false); + }); + + it('should work with `NaN` from another realm', function() { + if (realm.object) { + assert.strictEqual(isNaN(realm.nan), true); + } + }); +}); diff --git a/test/isNative.js b/test/isNative.js new file mode 100644 index 000000000..c5f999485 --- /dev/null +++ b/test/isNative.js @@ -0,0 +1,92 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; + +import { + body, + create, + slice, + falsey, + stubFalse, + args, + symbol, + realm, + amd, + filePath, + emptyObject, + interopRequire, +} from './utils.js'; + +import isNative from '../isNative.js'; +import runInContext from '../runInContext.js'; +import _baseEach from '../.internal/baseEach.js'; + +describe('isNative', function() { + it('should return `true` for native methods', function() { + var values = [Array, body && body.cloneNode, create, root.encodeURI, Promise, slice, Uint8Array], + expected = lodashStable.map(values, Boolean), + actual = lodashStable.map(values, isNative); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return `false` for non-native methods', function() { + var expected = lodashStable.map(falsey, stubFalse); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isNative(value) : isNative(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isNative(args), false); + assert.strictEqual(isNative([1, 2, 3]), false); + assert.strictEqual(isNative(true), false); + assert.strictEqual(isNative(new Date), false); + assert.strictEqual(isNative(new Error), false); + assert.strictEqual(isNative(_), false); + assert.strictEqual(isNative({ 'a': 1 }), false); + assert.strictEqual(isNative(1), false); + assert.strictEqual(isNative(/x/), false); + assert.strictEqual(isNative('a'), false); + assert.strictEqual(isNative(symbol), false); + }); + + it('should work with native functions from another realm', function() { + if (realm.element) { + assert.strictEqual(isNative(realm.element.cloneNode), true); + } + if (realm.object) { + assert.strictEqual(isNative(realm.object.valueOf), true); + } + }); + + it('should throw an error if core-js is detected', function() { + var lodash = runInContext({ + '__core-js_shared__': {} + }); + + assert.raises(function() { lodash.isNative(noop); }); + }); + + it('should detect methods masquerading as native (test in Node.js)', function() { + if (!amd && _baseEach) { + var path = require('path'), + basePath = path.dirname(filePath), + uid = 'e0gvgyrad1jor', + coreKey = '__core-js_shared__', + fakeSrcKey = 'Symbol(src)_1.' + uid; + + root[coreKey] = { 'keys': { 'IE_PROTO': 'Symbol(IE_PROTO)_3.' + uid } }; + emptyObject(require.cache); + + var baseIsNative = interopRequire(path.join(basePath, '_baseIsNative')); + assert.strictEqual(baseIsNative(slice), true); + + slice[fakeSrcKey] = slice + ''; + assert.strictEqual(baseIsNative(slice), false); + + delete slice[fakeSrcKey]; + delete root[coreKey]; + } + }); +}); diff --git a/test/isNil.test.js b/test/isNil.test.js new file mode 100644 index 000000000..3bd067be2 --- /dev/null +++ b/test/isNil.test.js @@ -0,0 +1,47 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, args, slice, symbol, realm } from './utils.js'; +import isNil from '../isNil.js'; + +describe('isNil', function() { + it('should return `true` for nullish values', function() { + assert.strictEqual(isNil(null), true); + assert.strictEqual(isNil(), true); + assert.strictEqual(isNil(undefined), true); + }); + + it('should return `false` for non-nullish values', function() { + var expected = lodashStable.map(falsey, function(value) { + return value == null; + }); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isNil(value) : isNil(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isNil(args), false); + assert.strictEqual(isNil([1, 2, 3]), false); + assert.strictEqual(isNil(true), false); + assert.strictEqual(isNil(new Date), false); + assert.strictEqual(isNil(new Error), false); + assert.strictEqual(isNil(_), false); + assert.strictEqual(isNil(slice), false); + assert.strictEqual(isNil({ 'a': 1 }), false); + assert.strictEqual(isNil(1), false); + assert.strictEqual(isNil(/x/), false); + assert.strictEqual(isNil('a'), false); + + if (Symbol) { + assert.strictEqual(isNil(symbol), false); + } + }); + + it('should work with nils from another realm', function() { + if (realm.object) { + assert.strictEqual(isNil(realm.null), true); + assert.strictEqual(isNil(realm.undefined), true); + } + }); +}); diff --git a/test/isNull.test.js b/test/isNull.test.js new file mode 100644 index 000000000..239e4c826 --- /dev/null +++ b/test/isNull.test.js @@ -0,0 +1,41 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, args, slice, symbol, realm } from './utils.js'; +import isNull from '../isNull.js'; + +describe('isNull', function() { + it('should return `true` for `null` values', function() { + assert.strictEqual(isNull(null), true); + }); + + it('should return `false` for non `null` values', function() { + var expected = lodashStable.map(falsey, function(value) { + return value === null; + }); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isNull(value) : isNull(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isNull(args), false); + assert.strictEqual(isNull([1, 2, 3]), false); + assert.strictEqual(isNull(true), false); + assert.strictEqual(isNull(new Date), false); + assert.strictEqual(isNull(new Error), false); + assert.strictEqual(isNull(_), false); + assert.strictEqual(isNull(slice), false); + assert.strictEqual(isNull({ 'a': 1 }), false); + assert.strictEqual(isNull(1), false); + assert.strictEqual(isNull(/x/), false); + assert.strictEqual(isNull('a'), false); + assert.strictEqual(isNull(symbol), false); + }); + + it('should work with nulls from another realm', function() { + if (realm.object) { + assert.strictEqual(isNull(realm.null), true); + } + }); +}); diff --git a/test/isNumber.test.js b/test/isNumber.test.js new file mode 100644 index 000000000..9764538bf --- /dev/null +++ b/test/isNumber.test.js @@ -0,0 +1,42 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, args, slice, symbol, realm } from './utils.js'; +import isNumber from '../isNumber.js'; + +describe('isNumber', function() { + it('should return `true` for numbers', function() { + assert.strictEqual(isNumber(0), true); + assert.strictEqual(isNumber(Object(0)), true); + assert.strictEqual(isNumber(NaN), true); + }); + + it('should return `false` for non-numbers', function() { + var expected = lodashStable.map(falsey, function(value) { + return typeof value == 'number'; + }); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isNumber(value) : isNumber(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isNumber(args), false); + assert.strictEqual(isNumber([1, 2, 3]), false); + assert.strictEqual(isNumber(true), false); + assert.strictEqual(isNumber(new Date), false); + assert.strictEqual(isNumber(new Error), false); + assert.strictEqual(isNumber(_), false); + assert.strictEqual(isNumber(slice), false); + assert.strictEqual(isNumber({ 'a': 1 }), false); + assert.strictEqual(isNumber(/x/), false); + assert.strictEqual(isNumber('a'), false); + assert.strictEqual(isNumber(symbol), false); + }); + + it('should work with numbers from another realm', function() { + if (realm.number) { + assert.strictEqual(isNumber(realm.number), true); + } + }); +}); diff --git a/test/isObject.test.js b/test/isObject.test.js new file mode 100644 index 000000000..8e24f532a --- /dev/null +++ b/test/isObject.test.js @@ -0,0 +1,53 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { args, slice, document, body, symbol, falsey, stubFalse, realm } from './utils.js'; +import isObject from '../isObject.js'; + +describe('isObject', function() { + it('should return `true` for objects', function() { + assert.strictEqual(isObject(args), true); + assert.strictEqual(isObject([1, 2, 3]), true); + assert.strictEqual(isObject(Object(false)), true); + assert.strictEqual(isObject(new Date), true); + assert.strictEqual(isObject(new Error), true); + assert.strictEqual(isObject(_), true); + assert.strictEqual(isObject(slice), true); + assert.strictEqual(isObject({ 'a': 1 }), true); + assert.strictEqual(isObject(Object(0)), true); + assert.strictEqual(isObject(/x/), true); + assert.strictEqual(isObject(Object('a')), true); + + if (document) { + assert.strictEqual(isObject(body), true); + } + if (Symbol) { + assert.strictEqual(isObject(Object(symbol)), true); + } + }); + + it('should return `false` for non-objects', function() { + var values = falsey.concat(true, 1, 'a', symbol), + expected = lodashStable.map(values, stubFalse); + + var actual = lodashStable.map(values, function(value, index) { + return index ? isObject(value) : isObject(); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with objects from another realm', function() { + if (realm.element) { + assert.strictEqual(isObject(realm.element), true); + } + if (realm.object) { + assert.strictEqual(isObject(realm.boolean), true); + assert.strictEqual(isObject(realm.date), true); + assert.strictEqual(isObject(realm.function), true); + assert.strictEqual(isObject(realm.number), true); + assert.strictEqual(isObject(realm.object), true); + assert.strictEqual(isObject(realm.regexp), true); + assert.strictEqual(isObject(realm.string), true); + } + }); +}); diff --git a/test/isObjectLike.test.js b/test/isObjectLike.test.js new file mode 100644 index 000000000..516d87b23 --- /dev/null +++ b/test/isObjectLike.test.js @@ -0,0 +1,40 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { args, falsey, slice, symbol, stubFalse, realm } from './utils.js'; +import isObjectLike from '../isObjectLike.js'; + +describe('isObjectLike', function() { + it('should return `true` for objects', function() { + assert.strictEqual(isObjectLike(args), true); + assert.strictEqual(isObjectLike([1, 2, 3]), true); + assert.strictEqual(isObjectLike(Object(false)), true); + assert.strictEqual(isObjectLike(new Date), true); + assert.strictEqual(isObjectLike(new Error), true); + assert.strictEqual(isObjectLike({ 'a': 1 }), true); + assert.strictEqual(isObjectLike(Object(0)), true); + assert.strictEqual(isObjectLike(/x/), true); + assert.strictEqual(isObjectLike(Object('a')), true); + }); + + it('should return `false` for non-objects', function() { + var values = falsey.concat(true, _, slice, 1, 'a', symbol), + expected = lodashStable.map(values, stubFalse); + + var actual = lodashStable.map(values, function(value, index) { + return index ? isObjectLike(value) : isObjectLike(); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with objects from another realm', function() { + if (realm.object) { + assert.strictEqual(isObjectLike(realm.boolean), true); + assert.strictEqual(isObjectLike(realm.date), true); + assert.strictEqual(isObjectLike(realm.number), true); + assert.strictEqual(isObjectLike(realm.object), true); + assert.strictEqual(isObjectLike(realm.regexp), true); + assert.strictEqual(isObjectLike(realm.string), true); + } + }); +}); diff --git a/test/isPlainObject.js b/test/isPlainObject.js new file mode 100644 index 000000000..a50ba7b98 --- /dev/null +++ b/test/isPlainObject.js @@ -0,0 +1,114 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; + +import { + document, + create, + objectProto, + falsey, + stubFalse, + symbol, + defineProperty, + realm, +} from './utils.js'; + +import isPlainObject from '../isPlainObject.js'; + +describe('isPlainObject', function() { + var element = document && document.createElement('div'); + + it('should detect plain objects', function() { + function Foo(a) { + this.a = 1; + } + + assert.strictEqual(isPlainObject({}), true); + assert.strictEqual(isPlainObject({ 'a': 1 }), true); + assert.strictEqual(isPlainObject({ 'constructor': Foo }), true); + assert.strictEqual(isPlainObject([1, 2, 3]), false); + assert.strictEqual(isPlainObject(new Foo(1)), false); + }); + + it('should return `true` for objects with a `[[Prototype]]` of `null`', function() { + var object = create(null); + assert.strictEqual(isPlainObject(object), true); + + object.constructor = objectProto.constructor; + assert.strictEqual(isPlainObject(object), true); + }); + + it('should return `true` for objects with a `valueOf` property', function() { + assert.strictEqual(isPlainObject({ 'valueOf': 0 }), true); + }); + + it('should return `true` for objects with a writable `Symbol.toStringTag` property', function() { + if (Symbol && Symbol.toStringTag) { + var object = {}; + object[Symbol.toStringTag] = 'X'; + + assert.deepStrictEqual(isPlainObject(object), true); + } + }); + + it('should return `false` for objects with a custom `[[Prototype]]`', function() { + var object = create({ 'a': 1 }); + assert.strictEqual(isPlainObject(object), false); + }); + + it('should return `false` for DOM elements', function() { + if (element) { + assert.strictEqual(isPlainObject(element), false); + } + }); + + it('should return `false` for non-Object objects', function() { + assert.strictEqual(isPlainObject(arguments), false); + assert.strictEqual(isPlainObject(Error), false); + assert.strictEqual(isPlainObject(Math), false); + }); + + it('should return `false` for non-objects', function() { + var expected = lodashStable.map(falsey, stubFalse); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isPlainObject(value) : isPlainObject(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isPlainObject(true), false); + assert.strictEqual(isPlainObject('a'), false); + assert.strictEqual(isPlainObject(symbol), false); + }); + + it('should return `false` for objects with a read-only `Symbol.toStringTag` property', function() { + if (Symbol && Symbol.toStringTag) { + var object = {}; + defineProperty(object, Symbol.toStringTag, { + 'configurable': true, + 'enumerable': false, + 'writable': false, + 'value': 'X' + }); + + assert.deepStrictEqual(isPlainObject(object), false); + } + }); + + it('should not mutate `value`', function() { + if (Symbol && Symbol.toStringTag) { + var proto = {}; + proto[Symbol.toStringTag] = undefined; + var object = create(proto); + + assert.strictEqual(isPlainObject(object), false); + assert.ok(!lodashStable.has(object, Symbol.toStringTag)); + } + }); + + it('should work with objects from another realm', function() { + if (realm.object) { + assert.strictEqual(isPlainObject(realm.object), true); + } + }); +}); diff --git a/test/isRegExp.test.js b/test/isRegExp.test.js new file mode 100644 index 000000000..507cdd474 --- /dev/null +++ b/test/isRegExp.test.js @@ -0,0 +1,39 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubFalse, args, slice, symbol, realm } from './utils.js'; +import isRegExp from '../isRegExp.js'; + +describe('isRegExp', function() { + it('should return `true` for regexes', function() { + assert.strictEqual(isRegExp(/x/), true); + assert.strictEqual(isRegExp(RegExp('x')), true); + }); + + it('should return `false` for non-regexes', function() { + var expected = lodashStable.map(falsey, stubFalse); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isRegExp(value) : isRegExp(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isRegExp(args), false); + assert.strictEqual(isRegExp([1, 2, 3]), false); + assert.strictEqual(isRegExp(true), false); + assert.strictEqual(isRegExp(new Date), false); + assert.strictEqual(isRegExp(new Error), false); + assert.strictEqual(isRegExp(_), false); + assert.strictEqual(isRegExp(slice), false); + assert.strictEqual(isRegExp({ 'a': 1 }), false); + assert.strictEqual(isRegExp(1), false); + assert.strictEqual(isRegExp('a'), false); + assert.strictEqual(isRegExp(symbol), false); + }); + + it('should work with regexes from another realm', function() { + if (realm.regexp) { + assert.strictEqual(isRegExp(realm.regexp), true); + } + }); +}); diff --git a/test/isSet.test.js b/test/isSet.test.js new file mode 100644 index 000000000..49d9d9065 --- /dev/null +++ b/test/isSet.test.js @@ -0,0 +1,53 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { set, falsey, stubFalse, args, slice, symbol, weakSet, realm } from './utils.js'; +import isSet from '../isSet.js'; + +describe('isSet', function() { + it('should return `true` for sets', function() { + if (Set) { + assert.strictEqual(isSet(set), true); + } + }); + + it('should return `false` for non-sets', function() { + var expected = lodashStable.map(falsey, stubFalse); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isSet(value) : isSet(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isSet(args), false); + assert.strictEqual(isSet([1, 2, 3]), false); + assert.strictEqual(isSet(true), false); + assert.strictEqual(isSet(new Date), false); + assert.strictEqual(isSet(new Error), false); + assert.strictEqual(isSet(_), false); + assert.strictEqual(isSet(slice), false); + assert.strictEqual(isSet({ 'a': 1 }), false); + assert.strictEqual(isSet(1), false); + assert.strictEqual(isSet(/x/), false); + assert.strictEqual(isSet('a'), false); + assert.strictEqual(isSet(symbol), false); + assert.strictEqual(isSet(weakSet), false); + }); + + it('should work for objects with a non-function `constructor` (test in IE 11)', function() { + var values = [false, true], + expected = lodashStable.map(values, stubFalse); + + var actual = lodashStable.map(values, function(value) { + return isSet({ 'constructor': value }); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with weak sets from another realm', function() { + if (realm.set) { + assert.strictEqual(isSet(realm.set), true); + } + }); +}); diff --git a/test/isString.test.js b/test/isString.test.js new file mode 100644 index 000000000..e046d507f --- /dev/null +++ b/test/isString.test.js @@ -0,0 +1,41 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, args, slice, symbol, realm } from './utils.js'; +import isString from '../isString.js'; + +describe('isString', function() { + it('should return `true` for strings', function() { + assert.strictEqual(isString('a'), true); + assert.strictEqual(isString(Object('a')), true); + }); + + it('should return `false` for non-strings', function() { + var expected = lodashStable.map(falsey, function(value) { + return value === ''; + }); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isString(value) : isString(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isString(args), false); + assert.strictEqual(isString([1, 2, 3]), false); + assert.strictEqual(isString(true), false); + assert.strictEqual(isString(new Date), false); + assert.strictEqual(isString(new Error), false); + assert.strictEqual(isString(_), false); + assert.strictEqual(isString(slice), false); + assert.strictEqual(isString({ '0': 1, 'length': 1 }), false); + assert.strictEqual(isString(1), false); + assert.strictEqual(isString(/x/), false); + assert.strictEqual(isString(symbol), false); + }); + + it('should work with strings from another realm', function() { + if (realm.string) { + assert.strictEqual(isString(realm.string), true); + } + }); +}); diff --git a/test/isSymbol.test.js b/test/isSymbol.test.js new file mode 100644 index 000000000..bf546c649 --- /dev/null +++ b/test/isSymbol.test.js @@ -0,0 +1,41 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { symbol, falsey, stubFalse, args, slice, realm } from './utils.js'; +import isSymbol from '../isSymbol.js'; + +describe('isSymbol', function() { + it('should return `true` for symbols', function() { + if (Symbol) { + assert.strictEqual(isSymbol(symbol), true); + assert.strictEqual(isSymbol(Object(symbol)), true); + } + }); + + it('should return `false` for non-symbols', function() { + var expected = lodashStable.map(falsey, stubFalse); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isSymbol(value) : isSymbol(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isSymbol(args), false); + assert.strictEqual(isSymbol([1, 2, 3]), false); + assert.strictEqual(isSymbol(true), false); + assert.strictEqual(isSymbol(new Date), false); + assert.strictEqual(isSymbol(new Error), false); + assert.strictEqual(isSymbol(_), false); + assert.strictEqual(isSymbol(slice), false); + assert.strictEqual(isSymbol({ '0': 1, 'length': 1 }), false); + assert.strictEqual(isSymbol(1), false); + assert.strictEqual(isSymbol(/x/), false); + assert.strictEqual(isSymbol('a'), false); + }); + + it('should work with symbols from another realm', function() { + if (Symbol && realm.symbol) { + assert.strictEqual(isSymbol(realm.symbol), true); + } + }); +}); diff --git a/test/isType-checks.js b/test/isType-checks.js new file mode 100644 index 000000000..909669721 --- /dev/null +++ b/test/isType-checks.js @@ -0,0 +1,39 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { objToString, objectTag, _, xml } from './utils.js'; + +describe('isType checks', function() { + it('should return `false` for subclassed values', function() { + var funcs = [ + 'isArray', 'isBoolean', 'isDate', 'isFunction', + 'isNumber', 'isRegExp', 'isString' + ]; + + lodashStable.each(funcs, function(methodName) { + function Foo() {} + Foo.prototype = root[methodName.slice(2)].prototype; + + var object = new Foo; + if (objToString.call(object) == objectTag) { + assert.strictEqual(_[methodName](object), false, '`_.' + methodName + '` returns `false`'); + } + }); + }); + + it('should not error on host objects (test in IE)', function() { + var funcs = [ + 'isArguments', 'isArray', 'isArrayBuffer', 'isArrayLike', 'isBoolean', + 'isBuffer', 'isDate', 'isElement', 'isError', 'isFinite', 'isFunction', + 'isInteger', 'isMap', 'isNaN', 'isNil', 'isNull', 'isNumber', 'isObject', + 'isObjectLike', 'isRegExp', 'isSet', 'isSafeInteger', 'isString', + 'isUndefined', 'isWeakMap', 'isWeakSet' + ]; + + lodashStable.each(funcs, function(methodName) { + if (xml) { + _[methodName](xml); + assert.ok(true, '`_.' + methodName + '` should not error'); + } + }); + }); +}); diff --git a/test/isTypedArray.js b/test/isTypedArray.js new file mode 100644 index 000000000..3c0d8be2e --- /dev/null +++ b/test/isTypedArray.js @@ -0,0 +1,59 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { typedArrays, falsey, stubFalse, args, slice, symbol, realm } from './utils.js'; +import isTypedArray from '../isTypedArray.js'; + +describe('isTypedArray', function() { + it('should return `true` for typed arrays', function() { + var expected = lodashStable.map(typedArrays, function(type) { + return type in root; + }); + + var actual = lodashStable.map(typedArrays, function(type) { + var Ctor = root[type]; + return Ctor ? isTypedArray(new Ctor(new ArrayBuffer(8))) : false; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return `false` for non typed arrays', function() { + var expected = lodashStable.map(falsey, stubFalse); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isTypedArray(value) : isTypedArray(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isTypedArray(args), false); + assert.strictEqual(isTypedArray([1, 2, 3]), false); + assert.strictEqual(isTypedArray(true), false); + assert.strictEqual(isTypedArray(new Date), false); + assert.strictEqual(isTypedArray(new Error), false); + assert.strictEqual(isTypedArray(_), false); + assert.strictEqual(isTypedArray(slice), false); + assert.strictEqual(isTypedArray({ 'a': 1 }), false); + assert.strictEqual(isTypedArray(1), false); + assert.strictEqual(isTypedArray(/x/), false); + assert.strictEqual(isTypedArray('a'), false); + assert.strictEqual(isTypedArray(symbol), false); + }); + + it('should work with typed arrays from another realm', function() { + if (realm.object) { + var props = lodashStable.invokeMap(typedArrays, 'toLowerCase'); + + var expected = lodashStable.map(props, function(key) { + return realm[key] !== undefined; + }); + + var actual = lodashStable.map(props, function(key) { + var value = realm[key]; + return value ? isTypedArray(value) : false; + }); + + assert.deepStrictEqual(actual, expected); + } + }); +}); diff --git a/test/isUndefined.test.js b/test/isUndefined.test.js new file mode 100644 index 000000000..4f8c1fceb --- /dev/null +++ b/test/isUndefined.test.js @@ -0,0 +1,45 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, args, slice, symbol, realm } from './utils.js'; +import isUndefined from '../isUndefined.js'; + +describe('isUndefined', function() { + it('should return `true` for `undefined` values', function() { + assert.strictEqual(isUndefined(), true); + assert.strictEqual(isUndefined(undefined), true); + }); + + it('should return `false` for non `undefined` values', function() { + var expected = lodashStable.map(falsey, function(value) { + return value === undefined; + }); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isUndefined(value) : isUndefined(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isUndefined(args), false); + assert.strictEqual(isUndefined([1, 2, 3]), false); + assert.strictEqual(isUndefined(true), false); + assert.strictEqual(isUndefined(new Date), false); + assert.strictEqual(isUndefined(new Error), false); + assert.strictEqual(isUndefined(_), false); + assert.strictEqual(isUndefined(slice), false); + assert.strictEqual(isUndefined({ 'a': 1 }), false); + assert.strictEqual(isUndefined(1), false); + assert.strictEqual(isUndefined(/x/), false); + assert.strictEqual(isUndefined('a'), false); + + if (Symbol) { + assert.strictEqual(isUndefined(symbol), false); + } + }); + + it('should work with `undefined` from another realm', function() { + if (realm.object) { + assert.strictEqual(isUndefined(realm.undefined), true); + } + }); +}); diff --git a/test/isWeakMap.test.js b/test/isWeakMap.test.js new file mode 100644 index 000000000..f0c153207 --- /dev/null +++ b/test/isWeakMap.test.js @@ -0,0 +1,53 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { weakMap, falsey, stubFalse, args, slice, map, symbol, realm } from './utils.js'; +import isWeakMap from '../isWeakMap.js'; + +describe('isWeakMap', function() { + it('should return `true` for weak maps', function() { + if (WeakMap) { + assert.strictEqual(isWeakMap(weakMap), true); + } + }); + + it('should return `false` for non weak maps', function() { + var expected = lodashStable.map(falsey, stubFalse); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isWeakMap(value) : isWeakMap(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isWeakMap(args), false); + assert.strictEqual(isWeakMap([1, 2, 3]), false); + assert.strictEqual(isWeakMap(true), false); + assert.strictEqual(isWeakMap(new Date), false); + assert.strictEqual(isWeakMap(new Error), false); + assert.strictEqual(isWeakMap(_), false); + assert.strictEqual(isWeakMap(slice), false); + assert.strictEqual(isWeakMap({ 'a': 1 }), false); + assert.strictEqual(isWeakMap(map), false); + assert.strictEqual(isWeakMap(1), false); + assert.strictEqual(isWeakMap(/x/), false); + assert.strictEqual(isWeakMap('a'), false); + assert.strictEqual(isWeakMap(symbol), false); + }); + + it('should work for objects with a non-function `constructor` (test in IE 11)', function() { + var values = [false, true], + expected = lodashStable.map(values, stubFalse); + + var actual = lodashStable.map(values, function(value) { + return isWeakMap({ 'constructor': value }); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with weak maps from another realm', function() { + if (realm.weakMap) { + assert.strictEqual(isWeakMap(realm.weakMap), true); + } + }); +}); diff --git a/test/isWeakSet.test.js b/test/isWeakSet.test.js new file mode 100644 index 000000000..a974bf1f5 --- /dev/null +++ b/test/isWeakSet.test.js @@ -0,0 +1,42 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { weakSet, falsey, stubFalse, args, slice, set, symbol, realm } from './utils.js'; +import isWeakSet from '../isWeakSet.js'; + +describe('isWeakSet', function() { + it('should return `true` for weak sets', function() { + if (WeakSet) { + assert.strictEqual(isWeakSet(weakSet), true); + } + }); + + it('should return `false` for non weak sets', function() { + var expected = lodashStable.map(falsey, stubFalse); + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? isWeakSet(value) : isWeakSet(); + }); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(isWeakSet(args), false); + assert.strictEqual(isWeakSet([1, 2, 3]), false); + assert.strictEqual(isWeakSet(true), false); + assert.strictEqual(isWeakSet(new Date), false); + assert.strictEqual(isWeakSet(new Error), false); + assert.strictEqual(isWeakSet(_), false); + assert.strictEqual(isWeakSet(slice), false); + assert.strictEqual(isWeakSet({ 'a': 1 }), false); + assert.strictEqual(isWeakSet(1), false); + assert.strictEqual(isWeakSet(/x/), false); + assert.strictEqual(isWeakSet('a'), false); + assert.strictEqual(isWeakSet(set), false); + assert.strictEqual(isWeakSet(symbol), false); + }); + + it('should work with weak sets from another realm', function() { + if (realm.weakSet) { + assert.strictEqual(isWeakSet(realm.weakSet), true); + } + }); +}); diff --git a/test/iteratee.js b/test/iteratee.js new file mode 100644 index 000000000..fd653dd2e --- /dev/null +++ b/test/iteratee.js @@ -0,0 +1,164 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice, _, isNpm, push, stubFalse } from './utils.js'; +import partial from '../partial.js'; +import partialRight from '../partialRight.js'; +import map from '../map.js'; + +describe('iteratee', function() { + it('should provide arguments to `func`', function() { + var fn = function() { return slice.call(arguments); }, + iteratee = _.iteratee(fn), + actual = iteratee('a', 'b', 'c', 'd', 'e', 'f'); + + assert.deepStrictEqual(actual, ['a', 'b', 'c', 'd', 'e', 'f']); + }); + + it('should return `_.identity` when `func` is nullish', function() { + var object = {}, + values = [, null, undefined], + expected = lodashStable.map(values, lodashStable.constant([!isNpm && _.identity, object])); + + var actual = lodashStable.map(values, function(value, index) { + var identity = index ? _.iteratee(value) : _.iteratee(); + return [!isNpm && identity, identity(object)]; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return an iteratee created by `_.matches` when `func` is an object', function() { + var matches = _.iteratee({ 'a': 1, 'b': 2 }); + assert.strictEqual(matches({ 'a': 1, 'b': 2, 'c': 3 }), true); + assert.strictEqual(matches({ 'b': 2 }), false); + }); + + it('should not change `_.matches` behavior if `source` is modified', function() { + var sources = [ + { 'a': { 'b': 2, 'c': 3 } }, + { 'a': 1, 'b': 2 }, + { 'a': 1 } + ]; + + lodashStable.each(sources, function(source, index) { + var object = lodashStable.cloneDeep(source), + matches = _.iteratee(source); + + assert.strictEqual(matches(object), true); + + if (index) { + source.a = 2; + source.b = 1; + source.c = 3; + } else { + source.a.b = 1; + source.a.c = 2; + source.a.d = 3; + } + assert.strictEqual(matches(object), true); + assert.strictEqual(matches(source), false); + }); + }); + + it('should return an iteratee created by `_.matchesProperty` when `func` is an array', function() { + var array = ['a', undefined], + matches = _.iteratee([0, 'a']); + + assert.strictEqual(matches(array), true); + + matches = _.iteratee(['0', 'a']); + assert.strictEqual(matches(array), true); + + matches = _.iteratee([1, undefined]); + assert.strictEqual(matches(array), true); + }); + + it('should support deep paths for `_.matchesProperty` shorthands', function() { + var object = { 'a': { 'b': { 'c': 1, 'd': 2 } } }, + matches = _.iteratee(['a.b', { 'c': 1 }]); + + assert.strictEqual(matches(object), true); + }); + + it('should not change `_.matchesProperty` behavior if `source` is modified', function() { + var sources = [ + { 'a': { 'b': 2, 'c': 3 } }, + { 'a': 1, 'b': 2 }, + { 'a': 1 } + ]; + + lodashStable.each(sources, function(source, index) { + var object = { 'a': lodashStable.cloneDeep(source) }, + matches = _.iteratee(['a', source]); + + assert.strictEqual(matches(object), true); + + if (index) { + source.a = 2; + source.b = 1; + source.c = 3; + } else { + source.a.b = 1; + source.a.c = 2; + source.a.d = 3; + } + assert.strictEqual(matches(object), true); + assert.strictEqual(matches({ 'a': source }), false); + }); + }); + + it('should return an iteratee created by `_.property` when `func` is a number or string', function() { + var array = ['a'], + prop = _.iteratee(0); + + assert.strictEqual(prop(array), 'a'); + + prop = _.iteratee('0'); + assert.strictEqual(prop(array), 'a'); + }); + + it('should support deep paths for `_.property` shorthands', function() { + var object = { 'a': { 'b': 2 } }, + prop = _.iteratee('a.b'); + + assert.strictEqual(prop(object), 2); + }); + + it('should work with functions created by `_.partial` and `_.partialRight`', function() { + var fn = function() { + var result = [this.a]; + push.apply(result, arguments); + return result; + }; + + var expected = [1, 2, 3], + object = { 'a': 1 , 'iteratee': _.iteratee(partial(fn, 2)) }; + + assert.deepStrictEqual(object.iteratee(3), expected); + + object.iteratee = _.iteratee(partialRight(fn, 3)); + assert.deepStrictEqual(object.iteratee(2), expected); + }); + + it('should use internal `iteratee` if external is unavailable', function() { + var iteratee = _.iteratee; + delete _.iteratee; + + assert.deepStrictEqual(map([{ 'a': 1 }], 'a'), [1]); + + _.iteratee = iteratee; + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var fn = function() { return this instanceof Number; }, + array = [fn, fn, fn], + iteratees = lodashStable.map(array, _.iteratee), + expected = lodashStable.map(array, stubFalse); + + var actual = lodashStable.map(iteratees, function(iteratee) { + return iteratee(); + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/iteration-methods.js b/test/iteration-methods.js new file mode 100644 index 000000000..67c73e821 --- /dev/null +++ b/test/iteration-methods.js @@ -0,0 +1,340 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, slice, isNpm, noop, MAX_SAFE_INTEGER, stubTrue } from './utils.js'; + +describe('iteration methods', function() { + var methods = [ + '_baseEach', + 'countBy', + 'every', + 'filter', + 'find', + 'findIndex', + 'findKey', + 'findLast', + 'findLastIndex', + 'findLastKey', + 'forEach', + 'forEachRight', + 'forIn', + 'forInRight', + 'forOwn', + 'forOwnRight', + 'groupBy', + 'keyBy', + 'map', + 'mapKeys', + 'mapValues', + 'maxBy', + 'minBy', + 'omitBy', + 'partition', + 'pickBy', + 'reject', + 'some' + ]; + + var arrayMethods = [ + 'findIndex', + 'findLastIndex', + 'maxBy', + 'minBy' + ]; + + var collectionMethods = [ + '_baseEach', + 'countBy', + 'every', + 'filter', + 'find', + 'findLast', + 'forEach', + 'forEachRight', + 'groupBy', + 'keyBy', + 'map', + 'partition', + 'reduce', + 'reduceRight', + 'reject', + 'some' + ]; + + var forInMethods = [ + 'forIn', + 'forInRight', + 'omitBy', + 'pickBy' + ]; + + var iterationMethods = [ + '_baseEach', + 'forEach', + 'forEachRight', + 'forIn', + 'forInRight', + 'forOwn', + 'forOwnRight' + ]; + + var objectMethods = [ + 'findKey', + 'findLastKey', + 'forIn', + 'forInRight', + 'forOwn', + 'forOwnRight', + 'mapKeys', + 'mapValues', + 'omitBy', + 'pickBy' + ]; + + var rightMethods = [ + 'findLast', + 'findLastIndex', + 'findLastKey', + 'forEachRight', + 'forInRight', + 'forOwnRight' + ]; + + var unwrappedMethods = [ + 'each', + 'eachRight', + 'every', + 'find', + 'findIndex', + 'findKey', + 'findLast', + 'findLastIndex', + 'findLastKey', + 'forEach', + 'forEachRight', + 'forIn', + 'forInRight', + 'forOwn', + 'forOwnRight', + 'max', + 'maxBy', + 'min', + 'minBy', + 'some' + ]; + + lodashStable.each(methods, function(methodName) { + var array = [1, 2, 3], + func = _[methodName], + isBy = /(^partition|By)$/.test(methodName), + isFind = /^find/.test(methodName), + isOmitPick = /^(?:omit|pick)By$/.test(methodName), + isSome = methodName == 'some'; + + it('`_.' + methodName + '` should provide correct iteratee arguments', function() { + if (func) { + var args, + expected = [1, 0, array]; + + func(array, function() { + args || (args = slice.call(arguments)); + }); + + if (lodashStable.includes(rightMethods, methodName)) { + expected[0] = 3; + expected[1] = 2; + } + if (lodashStable.includes(objectMethods, methodName)) { + expected[1] += ''; + } + if (isBy) { + expected.length = isOmitPick ? 2 : 1; + } + assert.deepStrictEqual(args, expected); + } + }); + + it('`_.' + methodName + '` should treat sparse arrays as dense', function() { + if (func) { + var array = [1]; + array[2] = 3; + + var expected = lodashStable.includes(objectMethods, methodName) + ? [[1, '0', array], [undefined, '1', array], [3, '2', array]] + : [[1, 0, array], [undefined, 1, array], [3, 2, array]]; + + if (isBy) { + expected = lodashStable.map(expected, function(args) { + return args.slice(0, isOmitPick ? 2 : 1); + }); + } + else if (lodashStable.includes(objectMethods, methodName)) { + expected = lodashStable.map(expected, function(args) { + args[1] += ''; + return args; + }); + } + if (lodashStable.includes(rightMethods, methodName)) { + expected.reverse(); + } + var argsList = []; + func(array, function() { + argsList.push(slice.call(arguments)); + return !(isFind || isSome); + }); + + assert.deepStrictEqual(argsList, expected); + } + }); + }); + + lodashStable.each(lodashStable.difference(methods, objectMethods), function(methodName) { + var array = [1, 2, 3], + func = _[methodName], + isEvery = methodName == 'every'; + + array.a = 1; + + it('`_.' + methodName + '` should not iterate custom properties on arrays', function() { + if (func) { + var keys = []; + func(array, function(value, key) { + keys.push(key); + return isEvery; + }); + + assert.ok(!lodashStable.includes(keys, 'a')); + } + }); + }); + + lodashStable.each(lodashStable.difference(methods, unwrappedMethods), function(methodName) { + var array = [1, 2, 3], + isBaseEach = methodName == '_baseEach'; + + it('`_.' + methodName + '` should return a wrapped value when implicitly chaining', function() { + if (!(isBaseEach || isNpm)) { + var wrapped = _(array)[methodName](noop); + assert.ok(wrapped instanceof _); + } + }); + }); + + lodashStable.each(unwrappedMethods, function(methodName) { + var array = [1, 2, 3]; + + it('`_.' + methodName + '` should return an unwrapped value when implicitly chaining', function() { + var actual = _(array)[methodName](noop); + assert.notOk(actual instanceof _); + }); + + it('`_.' + methodName + '` should return a wrapped value when explicitly chaining', function() { + var wrapped = _(array).chain(), + actual = wrapped[methodName](noop); + + assert.ok(actual instanceof _); + assert.notStrictEqual(actual, wrapped); + }); + }); + + lodashStable.each(lodashStable.difference(methods, arrayMethods, forInMethods), function(methodName) { + var func = _[methodName]; + + it('`_.' + methodName + '` iterates over own string keyed properties of objects', function() { + function Foo() { + this.a = 1; + } + Foo.prototype.b = 2; + + if (func) { + var values = []; + func(new Foo, function(value) { values.push(value); }); + assert.deepStrictEqual(values, [1]); + } + }); + }); + + lodashStable.each(iterationMethods, function(methodName) { + var array = [1, 2, 3], + func = _[methodName]; + + it('`_.' + methodName + '` should return the collection', function() { + if (func) { + assert.strictEqual(func(array, Boolean), array); + } + }); + }); + + lodashStable.each(collectionMethods, function(methodName) { + var func = _[methodName]; + + it('`_.' + methodName + '` should use `isArrayLike` to determine whether a value is array-like', function() { + if (func) { + var isIteratedAsObject = function(object) { + var result = false; + func(object, function() { result = true; }, 0); + return result; + }; + + var values = [-1, '1', 1.1, Object(1), MAX_SAFE_INTEGER + 1], + expected = lodashStable.map(values, stubTrue); + + var actual = lodashStable.map(values, function(length) { + return isIteratedAsObject({ 'length': length }); + }); + + var Foo = function(a) {}; + Foo.a = 1; + + assert.deepStrictEqual(actual, expected); + assert.ok(isIteratedAsObject(Foo)); + assert.ok(!isIteratedAsObject({ 'length': 0 })); + } + }); + }); + + lodashStable.each(methods, function(methodName) { + var func = _[methodName], + isFind = /^find/.test(methodName), + isSome = methodName == 'some', + isReduce = /^reduce/.test(methodName); + + it('`_.' + methodName + '` should ignore changes to `length`', function() { + if (func) { + var count = 0, + array = [1]; + + func(array, function() { + if (++count == 1) { + array.push(2); + } + return !(isFind || isSome); + }, isReduce ? array : null); + + assert.strictEqual(count, 1); + } + }); + }); + + lodashStable.each(lodashStable.difference(lodashStable.union(methods, collectionMethods), arrayMethods), function(methodName) { + var func = _[methodName], + isFind = /^find/.test(methodName), + isSome = methodName == 'some', + isReduce = /^reduce/.test(methodName); + + it('`_.' + methodName + '` should ignore added `object` properties', function() { + if (func) { + var count = 0, + object = { 'a': 1 }; + + func(object, function() { + if (++count == 1) { + object.b = 2; + } + return !(isFind || isSome); + }, isReduce ? object : null); + + assert.strictEqual(count, 1); + } + }); + }); +}); diff --git a/test/join.js b/test/join.js new file mode 100644 index 000000000..a8f672f8b --- /dev/null +++ b/test/join.js @@ -0,0 +1,20 @@ +import assert from 'assert'; +import join from '../join.js'; + +describe('join', function() { + var array = ['a', 'b', 'c']; + + it('should return join all array elements into a string', function() { + assert.strictEqual(join(array, '~'), 'a~b~c'); + }); + + it('should return an unwrapped value when implicitly chaining', function() { + var wrapped = _(array); + assert.strictEqual(wrapped.join('~'), 'a~b~c'); + assert.strictEqual(wrapped.value(), array); + }); + + it('should return a wrapped value when explicitly chaining', function() { + assert.ok(_(array).chain().join('~') instanceof _); + }); +}); diff --git a/test/keyBy.js b/test/keyBy.js new file mode 100644 index 000000000..997c1015b --- /dev/null +++ b/test/keyBy.js @@ -0,0 +1,76 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { LARGE_ARRAY_SIZE } from './utils.js'; +import keyBy from '../keyBy.js'; + +describe('keyBy', function() { + var array = [ + { 'dir': 'left', 'code': 97 }, + { 'dir': 'right', 'code': 100 } + ]; + + it('should transform keys by `iteratee`', function() { + var expected = { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }; + + var actual = keyBy(array, function(object) { + return String.fromCharCode(object.code); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should use `_.identity` when `iteratee` is nullish', function() { + var array = [4, 6, 6], + values = [, null, undefined], + expected = lodashStable.map(values, lodashStable.constant({ '4': 4, '6': 6 })); + + var actual = lodashStable.map(values, function(value, index) { + return index ? keyBy(array, value) : keyBy(array); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with `_.property` shorthands', function() { + var expected = { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }, + actual = keyBy(array, 'dir'); + + assert.deepStrictEqual(actual, expected); + }); + + it('should only add values to own, not inherited, properties', function() { + var actual = keyBy([6.1, 4.2, 6.3], function(n) { + return Math.floor(n) > 4 ? 'hasOwnProperty' : 'constructor'; + }); + + assert.deepStrictEqual(actual.constructor, 4.2); + assert.deepStrictEqual(actual.hasOwnProperty, 6.3); + }); + + it('should work with a number for `iteratee`', function() { + var array = [ + [1, 'a'], + [2, 'a'], + [2, 'b'] + ]; + + assert.deepStrictEqual(keyBy(array, 0), { '1': [1, 'a'], '2': [2, 'b'] }); + assert.deepStrictEqual(keyBy(array, 1), { 'a': [2, 'a'], 'b': [2, 'b'] }); + }); + + it('should work with an object for `collection`', function() { + var actual = keyBy({ 'a': 6.1, 'b': 4.2, 'c': 6.3 }, Math.floor); + assert.deepStrictEqual(actual, { '4': 4.2, '6': 6.3 }); + }); + + it('should work in a lazy sequence', function() { + var array = lodashStable.range(LARGE_ARRAY_SIZE).concat( + lodashStable.range(Math.floor(LARGE_ARRAY_SIZE / 2), LARGE_ARRAY_SIZE), + lodashStable.range(Math.floor(LARGE_ARRAY_SIZE / 1.5), LARGE_ARRAY_SIZE) + ); + + var actual = _(array).keyBy().map(square).filter(isEven).take().value(); + + assert.deepEqual(actual, _.take(_.filter(_.map(keyBy(array), square), isEven))); + }); +}); diff --git a/test/keys-methods.js b/test/keys-methods.js new file mode 100644 index 000000000..8e422bd93 --- /dev/null +++ b/test/keys-methods.js @@ -0,0 +1,183 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; + +import { + _, + arrayProto, + args, + strictArgs, + objectProto, + stringProto, + primitives, + numberProto, + stubArray, +} from './utils.js'; + +describe('keys methods', function() { + lodashStable.each(['keys', 'keysIn'], function(methodName) { + var func = _[methodName], + isKeys = methodName == 'keys'; + + it('`_.' + methodName + '` should return the string keyed property names of `object`', function() { + var actual = func({ 'a': 1, 'b': 1 }).sort(); + + assert.deepStrictEqual(actual, ['a', 'b']); + }); + + it('`_.' + methodName + '` should ' + (isKeys ? 'not ' : '') + 'include inherited string keyed properties', function() { + function Foo() { + this.a = 1; + } + Foo.prototype.b = 2; + + var expected = isKeys ? ['a'] : ['a', 'b'], + actual = func(new Foo).sort(); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should treat sparse arrays as dense', function() { + var array = [1]; + array[2] = 3; + + var actual = func(array).sort(); + + assert.deepStrictEqual(actual, ['0', '1', '2']); + }); + + it('`_.' + methodName + '` should return keys for custom properties on arrays', function() { + var array = [1]; + array.a = 1; + + var actual = func(array).sort(); + + assert.deepStrictEqual(actual, ['0', 'a']); + }); + + it('`_.' + methodName + '` should ' + (isKeys ? 'not ' : '') + 'include inherited string keyed properties of arrays', function() { + arrayProto.a = 1; + + var expected = isKeys ? ['0'] : ['0', 'a'], + actual = func([1]).sort(); + + assert.deepStrictEqual(actual, expected); + + delete arrayProto.a; + }); + + it('`_.' + methodName + '` should work with `arguments` objects', function() { + var values = [args, strictArgs], + expected = lodashStable.map(values, lodashStable.constant(['0', '1', '2'])); + + var actual = lodashStable.map(values, function(value) { + return func(value).sort(); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should return keys for custom properties on `arguments` objects', function() { + var values = [args, strictArgs], + expected = lodashStable.map(values, lodashStable.constant(['0', '1', '2', 'a'])); + + var actual = lodashStable.map(values, function(value) { + value.a = 1; + var result = func(value).sort(); + delete value.a; + return result; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should ' + (isKeys ? 'not ' : '') + 'include inherited string keyed properties of `arguments` objects', function() { + var values = [args, strictArgs], + expected = lodashStable.map(values, lodashStable.constant(isKeys ? ['0', '1', '2'] : ['0', '1', '2', 'a'])); + + var actual = lodashStable.map(values, function(value) { + objectProto.a = 1; + var result = func(value).sort(); + delete objectProto.a; + return result; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should work with string objects', function() { + var actual = func(Object('abc')).sort(); + + assert.deepStrictEqual(actual, ['0', '1', '2']); + }); + + it('`_.' + methodName + '` should return keys for custom properties on string objects', function() { + var object = Object('a'); + object.a = 1; + + var actual = func(object).sort(); + + assert.deepStrictEqual(actual, ['0', 'a']); + }); + + it('`_.' + methodName + '` should ' + (isKeys ? 'not ' : '') + 'include inherited string keyed properties of string objects', function() { + stringProto.a = 1; + + var expected = isKeys ? ['0'] : ['0', 'a'], + actual = func(Object('a')).sort(); + + assert.deepStrictEqual(actual, expected); + + delete stringProto.a; + }); + + it('`_.' + methodName + '` should work with array-like objects', function() { + var object = { '0': 'a', 'length': 1 }, + actual = func(object).sort(); + + assert.deepStrictEqual(actual, ['0', 'length']); + }); + + it('`_.' + methodName + '` should coerce primitives to objects (test in IE 9)', function() { + var expected = lodashStable.map(primitives, function(value) { + return typeof value == 'string' ? ['0'] : []; + }); + + var actual = lodashStable.map(primitives, func); + assert.deepStrictEqual(actual, expected); + + // IE 9 doesn't box numbers in for-in loops. + numberProto.a = 1; + assert.deepStrictEqual(func(0), isKeys ? [] : ['a']); + delete numberProto.a; + }); + + it('`_.' + methodName + '` skips the `constructor` property on prototype objects', function() { + function Foo() {} + Foo.prototype.a = 1; + + var expected = ['a']; + assert.deepStrictEqual(func(Foo.prototype), expected); + + Foo.prototype = { 'constructor': Foo, 'a': 1 }; + assert.deepStrictEqual(func(Foo.prototype), expected); + + var Fake = { 'prototype': {} }; + Fake.prototype.constructor = Fake; + assert.deepStrictEqual(func(Fake.prototype), ['constructor']); + }); + + it('`_.' + methodName + '` should return an empty array when `object` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, stubArray); + + var actual = lodashStable.map(values, function(value, index) { + objectProto.a = 1; + var result = index ? func(value) : func(); + delete objectProto.a; + return result; + }); + + assert.deepStrictEqual(actual, expected); + }); + }); +}); diff --git a/test/last.js b/test/last.js new file mode 100644 index 000000000..fef570ec9 --- /dev/null +++ b/test/last.js @@ -0,0 +1,51 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { LARGE_ARRAY_SIZE } from './utils.js'; +import last from '../last.js'; + +describe('last', function() { + var array = [1, 2, 3, 4]; + + it('should return the last element', function() { + assert.strictEqual(last(array), 4); + }); + + it('should return `undefined` when querying empty arrays', function() { + var array = []; + array['-1'] = 1; + + assert.strictEqual(last([]), undefined); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + actual = lodashStable.map(array, last); + + assert.deepStrictEqual(actual, [3, 6, 9]); + }); + + it('should return an unwrapped value when implicitly chaining', function() { + assert.strictEqual(_(array).last(), 4); + }); + + it('should return a wrapped value when explicitly chaining', function() { + assert.ok(_(array).chain().last() instanceof _); + }); + + it('should not execute immediately when explicitly chaining', function() { + var wrapped = _(array).chain().last(); + assert.strictEqual(wrapped.__wrapped__, array); + }); + + it('should work in a lazy sequence', function() { + var largeArray = lodashStable.range(LARGE_ARRAY_SIZE), + smallArray = array; + + lodashStable.times(2, function(index) { + var array = index ? largeArray : smallArray, + wrapped = _(array).filter(isEven); + + assert.strictEqual(wrapped.last(), last(_.filter(array, isEven))); + }); + }); +}); diff --git a/test/lodash(...)-methods-that-return-new-wrapped-values.js b/test/lodash(...)-methods-that-return-new-wrapped-values.js new file mode 100644 index 000000000..b66c54c60 --- /dev/null +++ b/test/lodash(...)-methods-that-return-new-wrapped-values.js @@ -0,0 +1,45 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; + +describe('lodash(...) methods that return new wrapped values', function() { + var funcs = [ + 'castArray', + 'concat', + 'difference', + 'differenceBy', + 'differenceWith', + 'intersection', + 'intersectionBy', + 'intersectionWith', + 'pull', + 'pullAll', + 'pullAt', + 'sampleSize', + 'shuffle', + 'slice', + 'splice', + 'split', + 'toArray', + 'union', + 'unionBy', + 'unionWith', + 'uniq', + 'uniqBy', + 'uniqWith', + 'words', + 'xor', + 'xorBy', + 'xorWith' + ]; + + lodashStable.each(funcs, function(methodName) { + it('`_(...).' + methodName + '` should return a new wrapped value', function() { + var value = methodName == 'split' ? 'abc' : [1, 2, 3], + wrapped = _(value), + actual = wrapped[methodName](); + + assert.ok(actual instanceof _); + assert.notStrictEqual(actual, wrapped); + }); + }); +}); diff --git a/test/lodash(...)-methods-that-return-the-wrapped-modified-array.js b/test/lodash(...)-methods-that-return-the-wrapped-modified-array.js new file mode 100644 index 000000000..d58eeff84 --- /dev/null +++ b/test/lodash(...)-methods-that-return-the-wrapped-modified-array.js @@ -0,0 +1,22 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; + +describe('lodash(...) methods that return the wrapped modified array', function() { + var funcs = [ + 'push', + 'reverse', + 'sort', + 'unshift' + ]; + + lodashStable.each(funcs, function(methodName) { + it('`_(...).' + methodName + '` should return a new wrapper', function() { + var array = [1, 2, 3], + wrapped = _(array), + actual = wrapped[methodName](); + + assert.ok(actual instanceof _); + assert.notStrictEqual(actual, wrapped); + }); + }); +}); diff --git a/test/lodash(...)-methods-that-return-unwrapped-values.js b/test/lodash(...)-methods-that-return-unwrapped-values.js new file mode 100644 index 000000000..7b906e1ed --- /dev/null +++ b/test/lodash(...)-methods-that-return-unwrapped-values.js @@ -0,0 +1,112 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; + +describe('lodash(...) methods that return unwrapped values', function() { + var funcs = [ + 'add', + 'camelCase', + 'capitalize', + 'ceil', + 'clone', + 'deburr', + 'defaultTo', + 'divide', + 'endsWith', + 'escape', + 'escapeRegExp', + 'every', + 'find', + 'floor', + 'has', + 'hasIn', + 'head', + 'includes', + 'isArguments', + 'isArray', + 'isArrayBuffer', + 'isArrayLike', + 'isBoolean', + 'isBuffer', + 'isDate', + 'isElement', + 'isEmpty', + 'isEqual', + 'isError', + 'isFinite', + 'isFunction', + 'isInteger', + 'isMap', + 'isNaN', + 'isNative', + 'isNil', + 'isNull', + 'isNumber', + 'isObject', + 'isObjectLike', + 'isPlainObject', + 'isRegExp', + 'isSafeInteger', + 'isSet', + 'isString', + 'isUndefined', + 'isWeakMap', + 'isWeakSet', + 'join', + 'kebabCase', + 'last', + 'lowerCase', + 'lowerFirst', + 'max', + 'maxBy', + 'min', + 'minBy', + 'multiply', + 'nth', + 'pad', + 'padEnd', + 'padStart', + 'parseInt', + 'pop', + 'random', + 'reduce', + 'reduceRight', + 'repeat', + 'replace', + 'round', + 'sample', + 'shift', + 'size', + 'snakeCase', + 'some', + 'startCase', + 'startsWith', + 'subtract', + 'sum', + 'toFinite', + 'toInteger', + 'toLower', + 'toNumber', + 'toSafeInteger', + 'toString', + 'toUpper', + 'trim', + 'trimEnd', + 'trimStart', + 'truncate', + 'unescape', + 'upperCase', + 'upperFirst' + ]; + + lodashStable.each(funcs, function(methodName) { + it('`_(...).' + methodName + '` should return an unwrapped value when implicitly chaining', function() { + var actual = _()[methodName](); + assert.notOk(actual instanceof _); + }); + + it('`_(...).' + methodName + '` should return a wrapped value when explicitly chaining', function() { + var actual = _().chain()[methodName](); + assert.ok(actual instanceof _); + }); + }); +}); diff --git a/test/lodash(...).commit.js b/test/lodash(...).commit.js new file mode 100644 index 000000000..94a7acb08 --- /dev/null +++ b/test/lodash(...).commit.js @@ -0,0 +1,21 @@ +import assert from 'assert'; + +describe('lodash(...).commit', function() { + it('should execute the chained sequence and returns the wrapped result', function() { + var array = [1], + wrapped = _(array).push(2).push(3); + + assert.deepEqual(array, [1]); + + var otherWrapper = wrapped.commit(); + assert.ok(otherWrapper instanceof _); + assert.deepEqual(otherWrapper.value(), [1, 2, 3]); + assert.deepEqual(wrapped.value(), [1, 2, 3, 2, 3]); + }); + + it('should track the `__chain__` value of a wrapper', function() { + var wrapped = _([1]).chain().commit().head(); + assert.ok(wrapped instanceof _); + assert.strictEqual(wrapped.value(), 1); + }); +}); diff --git a/test/lodash(...).next.js b/test/lodash(...).next.js new file mode 100644 index 000000000..e3019ba35 --- /dev/null +++ b/test/lodash(...).next.js @@ -0,0 +1,74 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, isNpm, LARGE_ARRAY_SIZE, isEven } from './utils.js'; +import toArray from '../toArray.js'; +import filter from '../filter.js'; + +describe('lodash(...).next', function() { + lodashStable.each([false, true], function(implicit) { + function chain(value) { + return implicit ? _(value) : _.chain(value); + } + + var chainType = 'in an ' + (implicit ? 'implicit' : 'explict') + ' chain'; + + it('should follow the iterator protocol ' + chainType, function() { + var wrapped = chain([1, 2]); + + assert.deepEqual(wrapped.next(), { 'done': false, 'value': 1 }); + assert.deepEqual(wrapped.next(), { 'done': false, 'value': 2 }); + assert.deepEqual(wrapped.next(), { 'done': true, 'value': undefined }); + }); + + it('should act as an iterable ' + chainType, function() { + if (!isNpm && Symbol && Symbol.iterator) { + var array = [1, 2], + wrapped = chain(array); + + assert.strictEqual(wrapped[Symbol.iterator](), wrapped); + assert.deepStrictEqual(lodashStable.toArray(wrapped), array); + } + }); + + it('should use `_.toArray` to generate the iterable result ' + chainType, function() { + if (!isNpm && Array.from) { + var hearts = '\ud83d\udc95', + values = [[1], { 'a': 1 }, hearts]; + + lodashStable.each(values, function(value) { + var wrapped = chain(value); + assert.deepStrictEqual(Array.from(wrapped), toArray(value)); + }); + } + }); + + it('should reset the iterator correctly ' + chainType, function() { + if (!isNpm && Symbol && Symbol.iterator) { + var array = [1, 2], + wrapped = chain(array); + + assert.deepStrictEqual(lodashStable.toArray(wrapped), array); + assert.deepStrictEqual(lodashStable.toArray(wrapped), [], 'produces an empty array for exhausted iterator'); + + var other = wrapped.filter(); + assert.deepStrictEqual(lodashStable.toArray(other), array, 'reset for new chain segments'); + assert.deepStrictEqual(lodashStable.toArray(wrapped), [], 'iterator is still exhausted'); + } + }); + + it('should work in a lazy sequence ' + chainType, function() { + if (!isNpm && Symbol && Symbol.iterator) { + var array = lodashStable.range(LARGE_ARRAY_SIZE), + predicate = function(value) { values.push(value); return isEven(value); }, + values = [], + wrapped = chain(array); + + assert.deepStrictEqual(lodashStable.toArray(wrapped), array); + + wrapped = wrapped.filter(predicate); + assert.deepStrictEqual(lodashStable.toArray(wrapped), filter(array, isEven), 'reset for new lazy chain segments'); + assert.deepStrictEqual(values, array, 'memoizes iterator values'); + } + }); + }); +}); diff --git a/test/lodash(...).plant.js b/test/lodash(...).plant.js new file mode 100644 index 000000000..191eecaa3 --- /dev/null +++ b/test/lodash(...).plant.js @@ -0,0 +1,39 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { square, isNpm } from './utils.js'; +import compact from '../compact.js'; + +describe('lodash(...).plant', function() { + it('should clone the chained sequence planting `value` as the wrapped value', function() { + var array1 = [5, null, 3, null, 1], + array2 = [10, null, 8, null, 6], + wrapped1 = _(array1).thru(compact).map(square).takeRight(2).sort(), + wrapped2 = wrapped1.plant(array2); + + assert.deepEqual(wrapped2.value(), [36, 64]); + assert.deepEqual(wrapped1.value(), [1, 9]); + }); + + it('should clone `chainAll` settings', function() { + var array1 = [2, 4], + array2 = [6, 8], + wrapped1 = _(array1).chain().map(square), + wrapped2 = wrapped1.plant(array2); + + assert.deepEqual(wrapped2.head().value(), 36); + }); + + it('should reset iterator data on cloned sequences', function() { + if (!isNpm && Symbol && Symbol.iterator) { + var array1 = [2, 4], + array2 = [6, 8], + wrapped1 = _(array1).map(square); + + assert.deepStrictEqual(lodashStable.toArray(wrapped1), [4, 16]); + assert.deepStrictEqual(lodashStable.toArray(wrapped1), []); + + var wrapped2 = wrapped1.plant(array2); + assert.deepStrictEqual(lodashStable.toArray(wrapped2), [36, 64]); + } + }); +}); diff --git a/test/lodash(...).pop.js b/test/lodash(...).pop.js new file mode 100644 index 000000000..a06c0cb27 --- /dev/null +++ b/test/lodash(...).pop.js @@ -0,0 +1,31 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubTrue } from './utils.js'; + +describe('lodash(...).pop', function() { + it('should remove elements from the end of `array`', function() { + var array = [1, 2], + wrapped = _(array); + + assert.strictEqual(wrapped.pop(), 2); + assert.deepEqual(wrapped.value(), [1]); + assert.strictEqual(wrapped.pop(), 1); + + var actual = wrapped.value(); + assert.strictEqual(actual, array); + assert.deepEqual(actual, []); + }); + + it('should accept falsey arguments', function() { + var expected = lodashStable.map(falsey, stubTrue); + + var actual = lodashStable.map(falsey, function(value, index) { + try { + var result = index ? _(value).pop() : _().pop(); + return result === undefined; + } catch (e) {} + }); + + assert.deepEqual(actual, expected); + }); +}); diff --git a/test/lodash(...).push.js b/test/lodash(...).push.js new file mode 100644 index 000000000..f29b124cb --- /dev/null +++ b/test/lodash(...).push.js @@ -0,0 +1,27 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubTrue } from './utils.js'; + +describe('lodash(...).push', function() { + it('should append elements to `array`', function() { + var array = [1], + wrapped = _(array).push(2, 3), + actual = wrapped.value(); + + assert.strictEqual(actual, array); + assert.deepEqual(actual, [1, 2, 3]); + }); + + it('should accept falsey arguments', function() { + var expected = lodashStable.map(falsey, stubTrue); + + var actual = lodashStable.map(falsey, function(value, index) { + try { + var result = index ? _(value).push(1).value() : _().push(1).value(); + return lodashStable.eq(result, value); + } catch (e) {} + }); + + assert.deepEqual(actual, expected); + }); +}); diff --git a/test/lodash(...).shift.js b/test/lodash(...).shift.js new file mode 100644 index 000000000..b6920283a --- /dev/null +++ b/test/lodash(...).shift.js @@ -0,0 +1,31 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubTrue } from './utils.js'; + +describe('lodash(...).shift', function() { + it('should remove elements from the front of `array`', function() { + var array = [1, 2], + wrapped = _(array); + + assert.strictEqual(wrapped.shift(), 1); + assert.deepEqual(wrapped.value(), [2]); + assert.strictEqual(wrapped.shift(), 2); + + var actual = wrapped.value(); + assert.strictEqual(actual, array); + assert.deepEqual(actual, []); + }); + + it('should accept falsey arguments', function() { + var expected = lodashStable.map(falsey, stubTrue); + + var actual = lodashStable.map(falsey, function(value, index) { + try { + var result = index ? _(value).shift() : _().shift(); + return result === undefined; + } catch (e) {} + }); + + assert.deepEqual(actual, expected); + }); +}); diff --git a/test/lodash(...).sort.js b/test/lodash(...).sort.js new file mode 100644 index 000000000..e10d5f5a2 --- /dev/null +++ b/test/lodash(...).sort.js @@ -0,0 +1,27 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubTrue } from './utils.js'; + +describe('lodash(...).sort', function() { + it('should return the wrapped sorted `array`', function() { + var array = [3, 1, 2], + wrapped = _(array).sort(), + actual = wrapped.value(); + + assert.strictEqual(actual, array); + assert.deepEqual(actual, [1, 2, 3]); + }); + + it('should accept falsey arguments', function() { + var expected = lodashStable.map(falsey, stubTrue); + + var actual = lodashStable.map(falsey, function(value, index) { + try { + var result = index ? _(value).sort().value() : _().sort().value(); + return lodashStable.eq(result, value); + } catch (e) {} + }); + + assert.deepEqual(actual, expected); + }); +}); diff --git a/test/lodash(...).splice.js b/test/lodash(...).splice.js new file mode 100644 index 000000000..8e300b39d --- /dev/null +++ b/test/lodash(...).splice.js @@ -0,0 +1,31 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubTrue } from './utils.js'; + +describe('lodash(...).splice', function() { + it('should support removing and inserting elements', function() { + var array = [1, 2], + wrapped = _(array); + + assert.deepEqual(wrapped.splice(1, 1, 3).value(), [2]); + assert.deepEqual(wrapped.value(), [1, 3]); + assert.deepEqual(wrapped.splice(0, 2).value(), [1, 3]); + + var actual = wrapped.value(); + assert.strictEqual(actual, array); + assert.deepEqual(actual, []); + }); + + it('should accept falsey arguments', function() { + var expected = lodashStable.map(falsey, stubTrue); + + var actual = lodashStable.map(falsey, function(value, index) { + try { + var result = index ? _(value).splice(0, 1).value() : _().splice(0, 1).value(); + return lodashStable.isEqual(result, []); + } catch (e) {} + }); + + assert.deepEqual(actual, expected); + }); +}); diff --git a/test/lodash(...).unshift.js b/test/lodash(...).unshift.js new file mode 100644 index 000000000..f679c4fd8 --- /dev/null +++ b/test/lodash(...).unshift.js @@ -0,0 +1,27 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubTrue } from './utils.js'; + +describe('lodash(...).unshift', function() { + it('should prepend elements to `array`', function() { + var array = [3], + wrapped = _(array).unshift(1, 2), + actual = wrapped.value(); + + assert.strictEqual(actual, array); + assert.deepEqual(actual, [1, 2, 3]); + }); + + it('should accept falsey arguments', function() { + var expected = lodashStable.map(falsey, stubTrue); + + var actual = lodashStable.map(falsey, function(value, index) { + try { + var result = index ? _(value).unshift(1).value() : _().unshift(1).value(); + return lodashStable.eq(result, value); + } catch (e) {} + }); + + assert.deepEqual(actual, expected); + }); +}); diff --git a/test/lodash(...).value.js b/test/lodash(...).value.js new file mode 100644 index 000000000..a883d23fc --- /dev/null +++ b/test/lodash(...).value.js @@ -0,0 +1,33 @@ +import assert from 'assert'; +import { isNpm } from './utils.js'; +import prototype from '../prototype.js'; + +describe('lodash(...).value', function() { + it('should execute the chained sequence and extract the unwrapped value', function() { + var array = [1], + wrapped = _(array).push(2).push(3); + + assert.deepEqual(array, [1]); + assert.deepEqual(wrapped.value(), [1, 2, 3]); + assert.deepEqual(wrapped.value(), [1, 2, 3, 2, 3]); + assert.deepEqual(array, [1, 2, 3, 2, 3]); + }); + + it('should return the `valueOf` result of the wrapped value', function() { + var wrapped = _(123); + assert.strictEqual(Number(wrapped), 123); + }); + + it('should stringify the wrapped value when used by `JSON.stringify`', function() { + if (!isNpm && JSON) { + var wrapped = _([1, 2, 3]); + assert.strictEqual(JSON.stringify(wrapped), '[1,2,3]'); + } + }); + + it('should be aliased', function() { + var expected = prototype.value; + assert.strictEqual(prototype.toJSON, expected); + assert.strictEqual(prototype.valueOf, expected); + }); +}); diff --git a/test/lodash-constructor.js b/test/lodash-constructor.js new file mode 100644 index 000000000..c59556eb7 --- /dev/null +++ b/test/lodash-constructor.js @@ -0,0 +1,39 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { empties, stubTrue, isNpm, lodashBizarro } from './utils.js'; + +describe('lodash constructor', function() { + var values = empties.concat(true, 1, 'a'), + expected = lodashStable.map(values, stubTrue); + + it('should create a new instance when called without the `new` operator', function() { + var actual = lodashStable.map(values, function(value) { + return _(value) instanceof _; + }); + + assert.deepEqual(actual, expected); + }); + + it('should return the given `lodash` instances', function() { + var actual = lodashStable.map(values, function(value) { + var wrapped = _(value); + return _(wrapped) === wrapped; + }); + + assert.deepEqual(actual, expected); + }); + + it('should convert foreign wrapped values to `lodash` instances', function() { + if (!isNpm && lodashBizarro) { + var actual = lodashStable.map(values, function(value) { + var wrapped = _(lodashBizarro(value)), + unwrapped = wrapped.value(); + + return wrapped instanceof _ && + ((unwrapped === value) || (unwrapped !== unwrapped && value !== value)); + }); + + assert.deepStrictEqual(actual, expected); + } + }); +}); diff --git a/test/lodash-methods.js b/test/lodash-methods.js new file mode 100644 index 000000000..2a92a00ba --- /dev/null +++ b/test/lodash-methods.js @@ -0,0 +1,194 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, falsey, stubArray, oldDash, stubTrue, FUNC_ERROR_TEXT } from './utils.js'; +import functions from '../functions.js'; +import bind from '../bind.js'; + +describe('lodash methods', function() { + var allMethods = lodashStable.reject(functions(_).sort(), function(methodName) { + return lodashStable.startsWith(methodName, '_'); + }); + + var checkFuncs = [ + 'after', + 'ary', + 'before', + 'bind', + 'curry', + 'curryRight', + 'debounce', + 'defer', + 'delay', + 'flip', + 'flow', + 'flowRight', + 'memoize', + 'negate', + 'once', + 'partial', + 'partialRight', + 'rearg', + 'rest', + 'spread', + 'throttle', + 'unary' + ]; + + var noBinding = [ + 'flip', + 'memoize', + 'negate', + 'once', + 'overArgs', + 'partial', + 'partialRight', + 'rearg', + 'rest', + 'spread' + ]; + + var rejectFalsey = [ + 'tap', + 'thru' + ].concat(checkFuncs); + + var returnArrays = [ + 'at', + 'chunk', + 'compact', + 'difference', + 'drop', + 'filter', + 'flatten', + 'functions', + 'initial', + 'intersection', + 'invokeMap', + 'keys', + 'map', + 'orderBy', + 'pull', + 'pullAll', + 'pullAt', + 'range', + 'rangeRight', + 'reject', + 'remove', + 'shuffle', + 'sortBy', + 'tail', + 'take', + 'times', + 'toArray', + 'toPairs', + 'toPairsIn', + 'union', + 'uniq', + 'values', + 'without', + 'xor', + 'zip' + ]; + + var acceptFalsey = lodashStable.difference(allMethods, rejectFalsey); + + it('should accept falsey arguments', function() { + var arrays = lodashStable.map(falsey, stubArray); + + lodashStable.each(acceptFalsey, function(methodName) { + var expected = arrays, + func = _[methodName]; + + var actual = lodashStable.map(falsey, function(value, index) { + return index ? func(value) : func(); + }); + + if (methodName == 'noConflict') { + root._ = oldDash; + } + else if (methodName == 'pull' || methodName == 'pullAll') { + expected = falsey; + } + if (lodashStable.includes(returnArrays, methodName) && methodName != 'sample') { + assert.deepStrictEqual(actual, expected, '_.' + methodName + ' returns an array'); + } + assert.ok(true, '`_.' + methodName + '` accepts falsey arguments'); + }); + + // Skip tests for missing methods of modularized builds. + lodashStable.each(['chain', 'noConflict', 'runInContext'], function(methodName) { + if (!_[methodName]) {} + }); + }); + + it('should return an array', function() { + var array = [1, 2, 3]; + + lodashStable.each(returnArrays, function(methodName) { + var actual, + func = _[methodName]; + + switch (methodName) { + case 'invokeMap': + actual = func(array, 'toFixed'); + break; + case 'sample': + actual = func(array, 1); + break; + default: + actual = func(array); + } + assert.ok(lodashStable.isArray(actual), '_.' + methodName + ' returns an array'); + + var isPull = methodName == 'pull' || methodName == 'pullAll'; + assert.strictEqual(actual === array, isPull, '_.' + methodName + ' should ' + (isPull ? '' : 'not ') + 'return the given array'); + }); + }); + + it('should throw an error for falsey arguments', function() { + lodashStable.each(rejectFalsey, function(methodName) { + var expected = lodashStable.map(falsey, stubTrue), + func = _[methodName]; + + var actual = lodashStable.map(falsey, function(value, index) { + var pass = !index && /^(?:backflow|compose|cond|flow(Right)?|over(?:Every|Some)?)$/.test(methodName); + + try { + index ? func(value) : func(); + } catch (e) { + pass = !pass && (e instanceof TypeError) && + (!lodashStable.includes(checkFuncs, methodName) || (e.message == FUNC_ERROR_TEXT)); + } + return pass; + }); + + assert.deepStrictEqual(actual, expected, '`_.' + methodName + '` rejects falsey arguments'); + }); + }); + + it('should use `this` binding of function', function() { + lodashStable.each(noBinding, function(methodName) { + var fn = function() { return this.a; }, + func = _[methodName], + isNegate = methodName == 'negate', + object = { 'a': 1 }, + expected = isNegate ? false : 1; + + var wrapper = func(bind(fn, object)); + assert.strictEqual(wrapper(), expected, '`_.' + methodName + '` can consume a bound function'); + + wrapper = bind(func(fn), object); + assert.strictEqual(wrapper(), expected, '`_.' + methodName + '` can be bound'); + + object.wrapper = func(fn); + assert.strictEqual(object.wrapper(), expected, '`_.' + methodName + '` uses the `this` of its parent object'); + }); + }); + + it('should not contain minified method names (test production builds)', function() { + var shortNames = ['_', 'at', 'eq', 'gt', 'lt']; + assert.ok(lodashStable.every(functions(_), function(methodName) { + return methodName.length > 2 || lodashStable.includes(shortNames, methodName); + })); + }); +}); diff --git a/test/lodash.methodName.js b/test/lodash.methodName.js new file mode 100644 index 000000000..632684595 --- /dev/null +++ b/test/lodash.methodName.js @@ -0,0 +1,75 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, empties } from './utils.js'; + +lodashStable.each(['find', 'findIndex', 'findKey', 'findLast', 'findLastIndex', 'findLastKey'], function(methodName) { + describe('lodash.' + methodName); + + var array = [1, 2, 3, 4], + func = _[methodName]; + + var objects = [ + { 'a': 0, 'b': 0 }, + { 'a': 1, 'b': 1 }, + { 'a': 2, 'b': 2 } + ]; + + var expected = ({ + 'find': [objects[1], undefined, objects[2]], + 'findIndex': [1, -1, 2], + 'findKey': ['1', undefined, '2'], + 'findLast': [objects[2], undefined, objects[2]], + 'findLastIndex': [2, -1, 2], + 'findLastKey': ['2', undefined, '2'] + })[methodName]; + + it('`_.' + methodName + '` should return the found value', function() { + assert.strictEqual(func(objects, function(object) { return object.a; }), expected[0]); + }); + + it('`_.' + methodName + '` should return `' + expected[1] + '` if value is not found', function() { + assert.strictEqual(func(objects, function(object) { return object.a === 3; }), expected[1]); + }); + + it('`_.' + methodName + '` should work with `_.matches` shorthands', function() { + assert.strictEqual(func(objects, { 'b': 2 }), expected[2]); + }); + + it('`_.' + methodName + '` should work with `_.matchesProperty` shorthands', function() { + assert.strictEqual(func(objects, ['b', 2]), expected[2]); + }); + + it('`_.' + methodName + '` should work with `_.property` shorthands', function() { + assert.strictEqual(func(objects, 'b'), expected[0]); + }); + + it('`_.' + methodName + '` should return `' + expected[1] + '` for empty collections', function() { + var emptyValues = lodashStable.endsWith(methodName, 'Index') ? lodashStable.reject(empties, lodashStable.isPlainObject) : empties, + expecting = lodashStable.map(emptyValues, lodashStable.constant(expected[1])); + + var actual = lodashStable.map(emptyValues, function(value) { + try { + return func(value, { 'a': 3 }); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expecting); + }); + + it('`_.' + methodName + '` should return an unwrapped value when implicitly chaining', function() { + var expected = ({ + 'find': 1, + 'findIndex': 0, + 'findKey': '0', + 'findLast': 4, + 'findLastIndex': 3, + 'findLastKey': '3' + })[methodName]; + }); + + it('`_.' + methodName + '` should return a wrapped value when explicitly chaining', function() {}); + + it('`_.' + methodName + '` should not execute immediately when explicitly chaining', function() {}); + + it('`_.' + methodName + '` should work in a lazy sequence', function() {}); +}); diff --git a/test/lowerCase.js b/test/lowerCase.js new file mode 100644 index 000000000..eca2b7726 --- /dev/null +++ b/test/lowerCase.js @@ -0,0 +1,10 @@ +import assert from 'assert'; +import lowerCase from '../lowerCase.js'; + +describe('lowerCase', function() { + it('should lowercase as space-separated words', function() { + assert.strictEqual(lowerCase('--Foo-Bar--'), 'foo bar'); + assert.strictEqual(lowerCase('fooBar'), 'foo bar'); + assert.strictEqual(lowerCase('__FOO_BAR__'), 'foo bar'); + }); +}); diff --git a/test/lowerFirst.test.js b/test/lowerFirst.test.js new file mode 100644 index 000000000..665e7e71e --- /dev/null +++ b/test/lowerFirst.test.js @@ -0,0 +1,10 @@ +import assert from 'assert'; +import lowerFirst from '../lowerFirst.js'; + +describe('lowerFirst', function() { + it('should lowercase only the first character', function() { + assert.strictEqual(lowerFirst('fred'), 'fred'); + assert.strictEqual(lowerFirst('Fred'), 'fred'); + assert.strictEqual(lowerFirst('FRED'), 'fRED'); + }); +}); diff --git a/test/lt.test.js b/test/lt.test.js new file mode 100644 index 000000000..6b4590cb5 --- /dev/null +++ b/test/lt.test.js @@ -0,0 +1,16 @@ +import assert from 'assert'; +import lt from '../lt.js'; + +describe('lt', function() { + it('should return `true` if `value` is less than `other`', function() { + assert.strictEqual(lt(1, 3), true); + assert.strictEqual(lt('abc', 'def'), true); + }); + + it('should return `false` if `value` >= `other`', function() { + assert.strictEqual(lt(3, 1), false); + assert.strictEqual(lt(3, 3), false); + assert.strictEqual(lt('def', 'abc'), false); + assert.strictEqual(lt('def', 'def'), false); + }); +}); diff --git a/test/lte.test.js b/test/lte.test.js new file mode 100644 index 000000000..010a4fefc --- /dev/null +++ b/test/lte.test.js @@ -0,0 +1,17 @@ +import assert from 'assert'; +import lte from '../lte.js'; +import lt from '../lt.js'; + +describe('lte', function() { + it('should return `true` if `value` is <= `other`', function() { + assert.strictEqual(lte(1, 3), true); + assert.strictEqual(lte(3, 3), true); + assert.strictEqual(lte('abc', 'def'), true); + assert.strictEqual(lte('def', 'def'), true); + }); + + it('should return `false` if `value` > `other`', function() { + assert.strictEqual(lt(3, 1), false); + assert.strictEqual(lt('def', 'abc'), false); + }); +}); diff --git a/test/map-caches.js b/test/map-caches.js new file mode 100644 index 000000000..08c2c9af3 --- /dev/null +++ b/test/map-caches.js @@ -0,0 +1,63 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { symbol, noop, mapCaches, LARGE_ARRAY_SIZE } from './utils.js'; + +describe('map caches', function() { + var keys = [null, undefined, false, true, 1, -Infinity, NaN, {}, 'a', symbol || noop]; + + var pairs = lodashStable.map(keys, function(key, index) { + var lastIndex = keys.length - 1; + return [key, keys[lastIndex - index]]; + }); + + function createCaches(pairs) { + var largeStack = new mapCaches.Stack(pairs), + length = pairs ? pairs.length : 0; + + lodashStable.times(LARGE_ARRAY_SIZE - length, function() { + largeStack.set({}, {}); + }); + + return { + 'hashes': new mapCaches.Hash(pairs), + 'list caches': new mapCaches.ListCache(pairs), + 'map caches': new mapCaches.MapCache(pairs), + 'stack caches': new mapCaches.Stack(pairs), + 'large stacks': largeStack + }; + } + + lodashStable.forOwn(createCaches(pairs), function(cache, kind) { + var isLarge = /^large/.test(kind); + + it('should implement a `Map` interface for ' + kind, function() { + lodashStable.each(keys, function(key, index) { + var value = pairs[index][1]; + + assert.deepStrictEqual(cache.get(key), value); + assert.strictEqual(cache.has(key), true); + assert.strictEqual(cache.delete(key), true); + assert.strictEqual(cache.has(key), false); + assert.strictEqual(cache.get(key), undefined); + assert.strictEqual(cache.delete(key), false); + assert.strictEqual(cache.set(key, value), cache); + assert.strictEqual(cache.has(key), true); + }); + + assert.strictEqual(cache.size, isLarge ? LARGE_ARRAY_SIZE : keys.length); + assert.strictEqual(cache.clear(), undefined); + assert.ok(lodashStable.every(keys, function(key) { + return !cache.has(key); + })); + }); + }); + + lodashStable.forOwn(createCaches(), function(cache, kind) { + it('should support changing values of ' + kind, function() { + lodashStable.each(keys, function(key) { + cache.set(key, 1).set(key, 2); + assert.strictEqual(cache.get(key), 2); + }); + }); + }); +}); diff --git a/test/map.js b/test/map.js new file mode 100644 index 000000000..c41b9756b --- /dev/null +++ b/test/map.js @@ -0,0 +1,122 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { identity, falsey, stubArray, document, noop, LARGE_ARRAY_SIZE, square } from './utils.js'; +import map from '../map.js'; + +describe('map', function() { + var array = [1, 2]; + + it('should map values in `collection` to a new array', function() { + var object = { 'a': 1, 'b': 2 }, + expected = ['1', '2']; + + assert.deepStrictEqual(map(array, String), expected); + assert.deepStrictEqual(map(object, String), expected); + }); + + it('should work with `_.property` shorthands', function() { + var objects = [{ 'a': 'x' }, { 'a': 'y' }]; + assert.deepStrictEqual(map(objects, 'a'), ['x', 'y']); + }); + + it('should iterate over own string keyed properties of objects', function() { + function Foo() { + this.a = 1; + } + Foo.prototype.b = 2; + + var actual = map(new Foo, identity); + assert.deepStrictEqual(actual, [1]); + }); + + it('should use `_.identity` when `iteratee` is nullish', function() { + var object = { 'a': 1, 'b': 2 }, + values = [, null, undefined], + expected = lodashStable.map(values, lodashStable.constant([1, 2])); + + lodashStable.each([array, object], function(collection) { + var actual = lodashStable.map(values, function(value, index) { + return index ? map(collection, value) : map(collection); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('should accept a falsey `collection`', function() { + var expected = lodashStable.map(falsey, stubArray); + + var actual = lodashStable.map(falsey, function(collection, index) { + try { + return index ? map(collection) : map(); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should treat number values for `collection` as empty', function() { + assert.deepStrictEqual(map(1), []); + }); + + it('should treat a nodelist as an array-like object', function() { + if (document) { + var actual = map(document.getElementsByTagName('body'), function(element) { + return element.nodeName.toLowerCase(); + }); + + assert.deepStrictEqual(actual, ['body']); + } + }); + + it('should work with objects with non-number length properties', function() { + var value = { 'value': 'x' }, + object = { 'length': { 'value': 'x' } }; + + assert.deepStrictEqual(map(object, identity), [value]); + }); + + it('should return a wrapped value when chaining', function() { + assert.ok(_(array).map(noop) instanceof _); + }); + + it('should provide correct `predicate` arguments in a lazy sequence', function() { + var args, + array = lodashStable.range(LARGE_ARRAY_SIZE + 1), + expected = [1, 0, map(array.slice(1), square)]; + + _(array).slice(1).map(function(value, index, array) { + args || (args = slice.call(arguments)); + }).value(); + + assert.deepEqual(args, [1, 0, array.slice(1)]); + + args = undefined; + _(array).slice(1).map(square).map(function(value, index, array) { + args || (args = slice.call(arguments)); + }).value(); + + assert.deepEqual(args, expected); + + args = undefined; + _(array).slice(1).map(square).map(function(value, index) { + args || (args = slice.call(arguments)); + }).value(); + + assert.deepEqual(args, expected); + + args = undefined; + _(array).slice(1).map(square).map(function(value) { + args || (args = slice.call(arguments)); + }).value(); + + assert.deepEqual(args, [1]); + + args = undefined; + _(array).slice(1).map(square).map(function() { + args || (args = slice.call(arguments)); + }).value(); + + assert.deepEqual(args, expected); + }); +}); diff --git a/test/mapKeys-and-mapValues.js b/test/mapKeys-and-mapValues.js new file mode 100644 index 000000000..8ba86f7c8 --- /dev/null +++ b/test/mapKeys-and-mapValues.js @@ -0,0 +1,36 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, falsey, stubObject, noop } from './utils.js'; + +describe('mapKeys and mapValues', function() { + lodashStable.each(['mapKeys', 'mapValues'], function(methodName) { + var func = _[methodName], + object = { 'a': 1, 'b': 2 }; + + it('`_.' + methodName + '` should iterate over own string keyed properties of objects', function() { + function Foo() { + this.a = 'a'; + } + Foo.prototype.b = 'b'; + + var actual = func(new Foo, function(value, key) { return key; }); + assert.deepStrictEqual(actual, { 'a': 'a' }); + }); + + it('`_.' + methodName + '` should accept a falsey `object`', function() { + var expected = lodashStable.map(falsey, stubObject); + + var actual = lodashStable.map(falsey, function(object, index) { + try { + return index ? func(object) : func(); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should return a wrapped value when chaining', function() { + assert.ok(_(object)[methodName](noop) instanceof _); + }); + }); +}); diff --git a/test/mapKeys.js b/test/mapKeys.js new file mode 100644 index 000000000..a40f90a6f --- /dev/null +++ b/test/mapKeys.js @@ -0,0 +1,35 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import mapKeys from '../mapKeys.js'; + +describe('mapKeys', function() { + var array = [1, 2], + object = { 'a': 1, 'b': 2 }; + + it('should map keys in `object` to a new object', function() { + var actual = mapKeys(object, String); + assert.deepStrictEqual(actual, { '1': 1, '2': 2 }); + }); + + it('should treat arrays like objects', function() { + var actual = mapKeys(array, String); + assert.deepStrictEqual(actual, { '1': 1, '2': 2 }); + }); + + it('should work with `_.property` shorthands', function() { + var actual = mapKeys({ 'a': { 'b': 'c' } }, 'b'); + assert.deepStrictEqual(actual, { 'c': { 'b': 'c' } }); + }); + + it('should use `_.identity` when `iteratee` is nullish', function() { + var object = { 'a': 1, 'b': 2 }, + values = [, null, undefined], + expected = lodashStable.map(values, lodashStable.constant({ '1': 1, '2': 2 })); + + var actual = lodashStable.map(values, function(value, index) { + return index ? mapKeys(object, value) : mapKeys(object); + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/mapValues.js b/test/mapValues.js new file mode 100644 index 000000000..64b5cdc89 --- /dev/null +++ b/test/mapValues.js @@ -0,0 +1,36 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import mapValues from '../mapValues.js'; + +describe('mapValues', function() { + var array = [1, 2], + object = { 'a': 1, 'b': 2 }; + + it('should map values in `object` to a new object', function() { + var actual = mapValues(object, String); + assert.deepStrictEqual(actual, { 'a': '1', 'b': '2' }); + }); + + it('should treat arrays like objects', function() { + var actual = mapValues(array, String); + assert.deepStrictEqual(actual, { '0': '1', '1': '2' }); + }); + + it('should work with `_.property` shorthands', function() { + var actual = mapValues({ 'a': { 'b': 2 } }, 'b'); + assert.deepStrictEqual(actual, { 'a': 2 }); + }); + + it('should use `_.identity` when `iteratee` is nullish', function() { + var object = { 'a': 1, 'b': 2 }, + values = [, null, undefined], + expected = lodashStable.map(values, lodashStable.constant([true, false])); + + var actual = lodashStable.map(values, function(value, index) { + var result = index ? mapValues(object, value) : mapValues(object); + return [lodashStable.isEqual(result, object), result === object]; + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/matches-methods.js b/test/matches-methods.js new file mode 100644 index 000000000..721fdfad5 --- /dev/null +++ b/test/matches-methods.js @@ -0,0 +1,294 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, stubTrue, noop, numberProto, stubFalse, empties } from './utils.js'; +import isMatch from '../isMatch.js'; + +describe('matches methods', function() { + lodashStable.each(['matches', 'isMatch'], function(methodName) { + var isMatches = methodName == 'matches'; + + function matches(source) { + return isMatches ? _.matches(source) : function(object) { + return isMatch(object, source); + }; + } + + it('`_.' + methodName + '` should perform a deep comparison between `source` and `object`', function() { + var object = { 'a': 1, 'b': 2, 'c': 3 }, + par = matches({ 'a': 1 }); + + assert.strictEqual(par(object), true); + + par = matches({ 'b': 1 }); + assert.strictEqual(par(object), false); + + par = matches({ 'a': 1, 'c': 3 }); + assert.strictEqual(par(object), true); + + par = matches({ 'c': 3, 'd': 4 }); + assert.strictEqual(par(object), false); + + object = { 'a': { 'b': { 'c': 1, 'd': 2 }, 'e': 3 }, 'f': 4 }; + par = matches({ 'a': { 'b': { 'c': 1 } } }); + + assert.strictEqual(par(object), true); + }); + + it('`_.' + methodName + '` should match inherited string keyed `object` properties', function() { + function Foo() { + this.a = 1; + } + Foo.prototype.b = 2; + + var object = { 'a': new Foo }, + par = matches({ 'a': { 'b': 2 } }); + + assert.strictEqual(par(object), true); + }); + + it('`_.' + methodName + '` should not match by inherited `source` properties', function() { + function Foo() { + this.a = 1; + } + Foo.prototype.b = 2; + + var objects = [{ 'a': 1 }, { 'a': 1, 'b': 2 }], + source = new Foo, + actual = lodashStable.map(objects, matches(source)), + expected = lodashStable.map(objects, stubTrue); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should compare a variety of `source` property values', function() { + var object1 = { 'a': false, 'b': true, 'c': '3', 'd': 4, 'e': [5], 'f': { 'g': 6 } }, + object2 = { 'a': 0, 'b': 1, 'c': 3, 'd': '4', 'e': ['5'], 'f': { 'g': '6' } }, + par = matches(object1); + + assert.strictEqual(par(object1), true); + assert.strictEqual(par(object2), false); + }); + + it('`_.' + methodName + '` should match `-0` as `0`', function() { + var object1 = { 'a': -0 }, + object2 = { 'a': 0 }, + par = matches(object1); + + assert.strictEqual(par(object2), true); + + par = matches(object2); + assert.strictEqual(par(object1), true); + }); + + it('`_.' + methodName + '` should compare functions by reference', function() { + var object1 = { 'a': lodashStable.noop }, + object2 = { 'a': noop }, + object3 = { 'a': {} }, + par = matches(object1); + + assert.strictEqual(par(object1), true); + assert.strictEqual(par(object2), false); + assert.strictEqual(par(object3), false); + }); + + it('`_.' + methodName + '` should work with a function for `object`', function() { + function Foo() {} + Foo.a = { 'b': 2, 'c': 3 }; + + var par = matches({ 'a': { 'b': 2 } }); + assert.strictEqual(par(Foo), true); + }); + + it('`_.' + methodName + '` should work with a function for `source`', function() { + function Foo() {} + Foo.a = 1; + Foo.b = function() {}; + Foo.c = 3; + + var objects = [{ 'a': 1 }, { 'a': 1, 'b': Foo.b, 'c': 3 }], + actual = lodashStable.map(objects, matches(Foo)); + + assert.deepStrictEqual(actual, [false, true]); + }); + + it('`_.' + methodName + '` should work with a non-plain `object`', function() { + function Foo(object) { lodashStable.assign(this, object); } + + var object = new Foo({ 'a': new Foo({ 'b': 2, 'c': 3 }) }), + par = matches({ 'a': { 'b': 2 } }); + + assert.strictEqual(par(object), true); + }); + + it('`_.' + methodName + '` should partial match arrays', function() { + var objects = [{ 'a': ['b'] }, { 'a': ['c', 'd'] }], + actual = lodashStable.filter(objects, matches({ 'a': ['d'] })); + + assert.deepStrictEqual(actual, [objects[1]]); + + actual = lodashStable.filter(objects, matches({ 'a': ['b', 'd'] })); + assert.deepStrictEqual(actual, []); + + actual = lodashStable.filter(objects, matches({ 'a': ['d', 'b'] })); + assert.deepStrictEqual(actual, []); + }); + + it('`_.' + methodName + '` should partial match arrays with duplicate values', function() { + var objects = [{ 'a': [1, 2] }, { 'a': [2, 2] }], + actual = lodashStable.filter(objects, matches({ 'a': [2, 2] })); + + assert.deepStrictEqual(actual, [objects[1]]); + }); + + it('should partial match arrays of objects', function() { + var objects = [ + { 'a': [{ 'b': 1, 'c': 2 }, { 'b': 4, 'c': 5, 'd': 6 }] }, + { 'a': [{ 'b': 1, 'c': 2 }, { 'b': 4, 'c': 6, 'd': 7 }] } + ]; + + var actual = lodashStable.filter(objects, matches({ 'a': [{ 'b': 1 }, { 'b': 4, 'c': 5 }] })); + assert.deepStrictEqual(actual, [objects[0]]); + }); + + it('`_.' + methodName + '` should partial match maps', function() { + if (Map) { + var objects = [{ 'a': new Map }, { 'a': new Map }]; + objects[0].a.set('a', 1); + objects[1].a.set('a', 1); + objects[1].a.set('b', 2); + + var map = new Map; + map.set('b', 2); + var actual = lodashStable.filter(objects, matches({ 'a': map })); + + assert.deepStrictEqual(actual, [objects[1]]); + + map.delete('b'); + actual = lodashStable.filter(objects, matches({ 'a': map })); + + assert.deepStrictEqual(actual, objects); + + map.set('c', 3); + actual = lodashStable.filter(objects, matches({ 'a': map })); + + assert.deepStrictEqual(actual, []); + } + }); + + it('`_.' + methodName + '` should partial match sets', function() { + if (Set) { + var objects = [{ 'a': new Set }, { 'a': new Set }]; + objects[0].a.add(1); + objects[1].a.add(1); + objects[1].a.add(2); + + var set = new Set; + set.add(2); + var actual = lodashStable.filter(objects, matches({ 'a': set })); + + assert.deepStrictEqual(actual, [objects[1]]); + + set.delete(2); + actual = lodashStable.filter(objects, matches({ 'a': set })); + + assert.deepStrictEqual(actual, objects); + + set.add(3); + actual = lodashStable.filter(objects, matches({ 'a': set })); + + assert.deepStrictEqual(actual, []); + } + }); + + it('`_.' + methodName + '` should match `undefined` values', function() { + var objects = [{ 'a': 1 }, { 'a': 1, 'b': 1 }, { 'a': 1, 'b': undefined }], + actual = lodashStable.map(objects, matches({ 'b': undefined })), + expected = [false, false, true]; + + assert.deepStrictEqual(actual, expected); + + actual = lodashStable.map(objects, matches({ 'a': 1, 'b': undefined })); + + assert.deepStrictEqual(actual, expected); + + objects = [{ 'a': { 'b': 2 } }, { 'a': { 'b': 2, 'c': 3 } }, { 'a': { 'b': 2, 'c': undefined } }]; + actual = lodashStable.map(objects, matches({ 'a': { 'c': undefined } })); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should match `undefined` values on primitives', function() { + numberProto.a = 1; + numberProto.b = undefined; + + try { + var par = matches({ 'b': undefined }); + assert.strictEqual(par(1), true); + } catch (e) { + assert.ok(false, e.message); + } + try { + par = matches({ 'a': 1, 'b': undefined }); + assert.strictEqual(par(1), true); + } catch (e) { + assert.ok(false, e.message); + } + numberProto.a = { 'b': 1, 'c': undefined }; + try { + par = matches({ 'a': { 'c': undefined } }); + assert.strictEqual(par(1), true); + } catch (e) { + assert.ok(false, e.message); + } + delete numberProto.a; + delete numberProto.b; + }); + + it('`_.' + methodName + '` should return `false` when `object` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, stubFalse), + par = matches({ 'a': 1 }); + + var actual = lodashStable.map(values, function(value, index) { + try { + return index ? par(value) : par(); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should return `true` when comparing an empty `source`', function() { + var object = { 'a': 1 }, + expected = lodashStable.map(empties, stubTrue); + + var actual = lodashStable.map(empties, function(value) { + var par = matches(value); + return par(object); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should return `true` when comparing an empty `source` to a nullish `object`', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, stubTrue), + par = matches({}); + + var actual = lodashStable.map(values, function(value, index) { + try { + return index ? par(value) : par(); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should return `true` when comparing a `source` of empty arrays and objects', function() { + var objects = [{ 'a': [1], 'b': { 'c': 1 } }, { 'a': [2, 3], 'b': { 'd': 2 } }], + actual = lodashStable.filter(objects, matches({ 'a': [], 'b': {} })); + + assert.deepStrictEqual(actual, objects); + }); + }); +}); diff --git a/test/matches.js b/test/matches.js new file mode 100644 index 000000000..5901655ee --- /dev/null +++ b/test/matches.js @@ -0,0 +1,32 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import matches from '../matches.js'; + +describe('matches', function() { + it('should not change behavior if `source` is modified', function() { + var sources = [ + { 'a': { 'b': 2, 'c': 3 } }, + { 'a': 1, 'b': 2 }, + { 'a': 1 } + ]; + + lodashStable.each(sources, function(source, index) { + var object = lodashStable.cloneDeep(source), + par = matches(source); + + assert.strictEqual(par(object), true); + + if (index) { + source.a = 2; + source.b = 1; + source.c = 3; + } else { + source.a.b = 1; + source.a.c = 2; + source.a.d = 3; + } + assert.strictEqual(par(object), true); + assert.strictEqual(par(source), false); + }); + }); +}); diff --git a/test/matchesProperty.js b/test/matchesProperty.js new file mode 100644 index 000000000..23eb04218 --- /dev/null +++ b/test/matchesProperty.js @@ -0,0 +1,368 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { stubTrue, stubFalse, noop, numberProto } from './utils.js'; +import matchesProperty from '../matchesProperty.js'; + +describe('matchesProperty', function() { + it('should create a function that performs a deep comparison between a property value and `srcValue`', function() { + var object = { 'a': 1, 'b': 2, 'c': 3 }, + matches = matchesProperty('a', 1); + + assert.strictEqual(matches.length, 1); + assert.strictEqual(matches(object), true); + + matches = matchesProperty('b', 3); + assert.strictEqual(matches(object), false); + + matches = matchesProperty('a', { 'a': 1, 'c': 3 }); + assert.strictEqual(matches({ 'a': object }), true); + + matches = matchesProperty('a', { 'c': 3, 'd': 4 }); + assert.strictEqual(matches(object), false); + + object = { 'a': { 'b': { 'c': 1, 'd': 2 }, 'e': 3 }, 'f': 4 }; + matches = matchesProperty('a', { 'b': { 'c': 1 } }); + + assert.strictEqual(matches(object), true); + }); + + it('should support deep paths', function() { + var object = { 'a': { 'b': 2 } }; + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + var matches = matchesProperty(path, 2); + assert.strictEqual(matches(object), true); + }); + }); + + it('should work with a non-string `path`', function() { + var array = [1, 2, 3]; + + lodashStable.each([1, [1]], function(path) { + var matches = matchesProperty(path, 2); + assert.strictEqual(matches(array), true); + }); + }); + + it('should preserve the sign of `0`', function() { + var object1 = { '-0': 'a' }, + object2 = { '0': 'b' }, + pairs = [[object1, object2], [object1, object2], [object2, object1], [object2, object1]], + props = [-0, Object(-0), 0, Object(0)], + values = ['a', 'a', 'b', 'b'], + expected = lodashStable.map(props, lodashStable.constant([true, false])); + + var actual = lodashStable.map(props, function(key, index) { + var matches = matchesProperty(key, values[index]), + pair = pairs[index]; + + return [matches(pair[0]), matches(pair[1])]; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should coerce `path` to a string', function() { + function fn() {} + fn.toString = lodashStable.constant('fn'); + + var object = { 'null': 1, 'undefined': 2, 'fn': 3, '[object Object]': 4 }, + paths = [null, undefined, fn, {}], + expected = lodashStable.map(paths, stubTrue); + + lodashStable.times(2, function(index) { + var actual = lodashStable.map(paths, function(path) { + var matches = matchesProperty(index ? [path] : path, object[path]); + return matches(object); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('should match a key over a path', function() { + var object = { 'a.b': 1, 'a': { 'b': 2 } }; + + lodashStable.each(['a.b', ['a.b']], function(path) { + var matches = matchesProperty(path, 1); + assert.strictEqual(matches(object), true); + }); + }); + + it('should return `false` when `object` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, stubFalse); + + lodashStable.each(['constructor', ['constructor']], function(path) { + var matches = matchesProperty(path, 1); + + var actual = lodashStable.map(values, function(value, index) { + try { + return index ? matches(value) : matches(); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('should return `false` for deep paths when `object` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, stubFalse); + + lodashStable.each(['constructor.prototype.valueOf', ['constructor', 'prototype', 'valueOf']], function(path) { + var matches = matchesProperty(path, 1); + + var actual = lodashStable.map(values, function(value, index) { + try { + return index ? matches(value) : matches(); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('should return `false` if parts of `path` are missing', function() { + var object = {}; + + lodashStable.each(['a', 'a[1].b.c', ['a'], ['a', '1', 'b', 'c']], function(path) { + var matches = matchesProperty(path, 1); + assert.strictEqual(matches(object), false); + }); + }); + + it('should match inherited string keyed `srcValue` properties', function() { + function Foo() {} + Foo.prototype.b = 2; + + var object = { 'a': new Foo }; + + lodashStable.each(['a', ['a']], function(path) { + var matches = matchesProperty(path, { 'b': 2 }); + assert.strictEqual(matches(object), true); + }); + }); + + it('should not match by inherited `srcValue` properties', function() { + function Foo() { + this.a = 1; + } + Foo.prototype.b = 2; + + var objects = [{ 'a': { 'a': 1 } }, { 'a': { 'a': 1, 'b': 2 } }], + expected = lodashStable.map(objects, stubTrue); + + lodashStable.each(['a', ['a']], function(path) { + assert.deepStrictEqual(lodashStable.map(objects, matchesProperty(path, new Foo)), expected); + }); + }); + + it('should compare a variety of values', function() { + var object1 = { 'a': false, 'b': true, 'c': '3', 'd': 4, 'e': [5], 'f': { 'g': 6 } }, + object2 = { 'a': 0, 'b': 1, 'c': 3, 'd': '4', 'e': ['5'], 'f': { 'g': '6' } }, + matches = matchesProperty('a', object1); + + assert.strictEqual(matches({ 'a': object1 }), true); + assert.strictEqual(matches({ 'a': object2 }), false); + }); + + it('should match `-0` as `0`', function() { + var matches = matchesProperty('a', -0); + assert.strictEqual(matches({ 'a': 0 }), true); + + matches = matchesProperty('a', 0); + assert.strictEqual(matches({ 'a': -0 }), true); + }); + + it('should compare functions by reference', function() { + var object1 = { 'a': lodashStable.noop }, + object2 = { 'a': noop }, + object3 = { 'a': {} }, + matches = matchesProperty('a', object1); + + assert.strictEqual(matches({ 'a': object1 }), true); + assert.strictEqual(matches({ 'a': object2 }), false); + assert.strictEqual(matches({ 'a': object3 }), false); + }); + + it('should work with a function for `srcValue`', function() { + function Foo() {} + Foo.a = 1; + Foo.b = function() {}; + Foo.c = 3; + + var objects = [{ 'a': { 'a': 1 } }, { 'a': { 'a': 1, 'b': Foo.b, 'c': 3 } }], + actual = lodashStable.map(objects, matchesProperty('a', Foo)); + + assert.deepStrictEqual(actual, [false, true]); + }); + + it('should work with a non-plain `srcValue`', function() { + function Foo(object) { lodashStable.assign(this, object); } + + var object = new Foo({ 'a': new Foo({ 'b': 1, 'c': 2 }) }), + matches = matchesProperty('a', { 'b': 1 }); + + assert.strictEqual(matches(object), true); + }); + + it('should partial match arrays', function() { + var objects = [{ 'a': ['b'] }, { 'a': ['c', 'd'] }], + actual = lodashStable.filter(objects, matchesProperty('a', ['d'])); + + assert.deepStrictEqual(actual, [objects[1]]); + + actual = lodashStable.filter(objects, matchesProperty('a', ['b', 'd'])); + assert.deepStrictEqual(actual, []); + + actual = lodashStable.filter(objects, matchesProperty('a', ['d', 'b'])); + assert.deepStrictEqual(actual, []); + }); + + it('should partial match arrays with duplicate values', function() { + var objects = [{ 'a': [1, 2] }, { 'a': [2, 2] }], + actual = lodashStable.filter(objects, matchesProperty('a', [2, 2])); + + assert.deepStrictEqual(actual, [objects[1]]); + }); + + it('should partial match arrays of objects', function() { + var objects = [ + { 'a': [{ 'a': 1, 'b': 2 }, { 'a': 4, 'b': 5, 'c': 6 }] }, + { 'a': [{ 'a': 1, 'b': 2 }, { 'a': 4, 'b': 6, 'c': 7 }] } + ]; + + var actual = lodashStable.filter(objects, matchesProperty('a', [{ 'a': 1 }, { 'a': 4, 'b': 5 }])); + assert.deepStrictEqual(actual, [objects[0]]); + }); + it('should partial match maps', function() { + if (Map) { + var objects = [{ 'a': new Map }, { 'a': new Map }]; + objects[0].a.set('a', 1); + objects[1].a.set('a', 1); + objects[1].a.set('b', 2); + + var map = new Map; + map.set('b', 2); + var actual = lodashStable.filter(objects, matchesProperty('a', map)); + + assert.deepStrictEqual(actual, [objects[1]]); + + map.delete('b'); + actual = lodashStable.filter(objects, matchesProperty('a', map)); + + assert.deepStrictEqual(actual, objects); + + map.set('c', 3); + actual = lodashStable.filter(objects, matchesProperty('a', map)); + + assert.deepStrictEqual(actual, []); + } + }); + + it('should partial match sets', function() { + if (Set) { + var objects = [{ 'a': new Set }, { 'a': new Set }]; + objects[0].a.add(1); + objects[1].a.add(1); + objects[1].a.add(2); + + var set = new Set; + set.add(2); + var actual = lodashStable.filter(objects, matchesProperty('a', set)); + + assert.deepStrictEqual(actual, [objects[1]]); + + set.delete(2); + actual = lodashStable.filter(objects, matchesProperty('a', set)); + + assert.deepStrictEqual(actual, objects); + + set.add(3); + actual = lodashStable.filter(objects, matchesProperty('a', set)); + + assert.deepStrictEqual(actual, []); + } + }); + + it('should match `undefined` values', function() { + var objects = [{ 'a': 1 }, { 'a': 1, 'b': 1 }, { 'a': 1, 'b': undefined }], + actual = lodashStable.map(objects, matchesProperty('b', undefined)), + expected = [false, false, true]; + + assert.deepStrictEqual(actual, expected); + + objects = [{ 'a': { 'a': 1 } }, { 'a': { 'a': 1, 'b': 1 } }, { 'a': { 'a': 1, 'b': undefined } }]; + actual = lodashStable.map(objects, matchesProperty('a', { 'b': undefined })); + + assert.deepStrictEqual(actual, expected); + }); + + it('should match `undefined` values of nested objects', function() { + var object = { 'a': { 'b': undefined } }; + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + var matches = matchesProperty(path, undefined); + assert.strictEqual(matches(object), true); + }); + + lodashStable.each(['a.a', ['a', 'a']], function(path) { + var matches = matchesProperty(path, undefined); + assert.strictEqual(matches(object), false); + }); + }); + + it('should match `undefined` values on primitives', function() { + numberProto.a = 1; + numberProto.b = undefined; + + try { + var matches = matchesProperty('b', undefined); + assert.strictEqual(matches(1), true); + } catch (e) { + assert.ok(false, e.message); + } + numberProto.a = { 'b': 1, 'c': undefined }; + try { + matches = matchesProperty('a', { 'c': undefined }); + assert.strictEqual(matches(1), true); + } catch (e) { + assert.ok(false, e.message); + } + delete numberProto.a; + delete numberProto.b; + }); + + it('should return `true` when comparing a `srcValue` of empty arrays and objects', function() { + var objects = [{ 'a': [1], 'b': { 'c': 1 } }, { 'a': [2, 3], 'b': { 'd': 2 } }], + matches = matchesProperty('a', { 'a': [], 'b': {} }); + + var actual = lodashStable.filter(objects, function(object) { + return matches({ 'a': object }); + }); + + assert.deepStrictEqual(actual, objects); + }); + + it('should not change behavior if `srcValue` is modified', function() { + lodashStable.each([{ 'a': { 'b': 2, 'c': 3 } }, { 'a': 1, 'b': 2 }, { 'a': 1 }], function(source, index) { + var object = lodashStable.cloneDeep(source), + matches = matchesProperty('a', source); + + assert.strictEqual(matches({ 'a': object }), true); + + if (index) { + source.a = 2; + source.b = 1; + source.c = 3; + } else { + source.a.b = 1; + source.a.c = 2; + source.a.d = 3; + } + assert.strictEqual(matches({ 'a': object }), true); + assert.strictEqual(matches({ 'a': source }), false); + }); + }); +}); diff --git a/test/math-operator-methods.js b/test/math-operator-methods.js new file mode 100644 index 000000000..cd5e88c4e --- /dev/null +++ b/test/math-operator-methods.js @@ -0,0 +1,56 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, symbol } from './utils.js'; + +describe('math operator methods', function() { + lodashStable.each(['add', 'divide', 'multiply', 'subtract'], function(methodName) { + var func = _[methodName], + isAddSub = methodName == 'add' || methodName == 'subtract'; + + it('`_.' + methodName + '` should return `' + (isAddSub ? 0 : 1) + '` when no arguments are given', function() { + assert.strictEqual(func(), isAddSub ? 0 : 1); + }); + + it('`_.' + methodName + '` should work with only one defined argument', function() { + assert.strictEqual(func(6), 6); + assert.strictEqual(func(6, undefined), 6); + assert.strictEqual(func(undefined, 4), 4); + }); + + it('`_.' + methodName + '` should preserve the sign of `0`', function() { + var values = [0, '0', -0, '-0'], + expected = [[0, Infinity], ['0', Infinity], [-0, -Infinity], ['-0', -Infinity]]; + + lodashStable.times(2, function(index) { + var actual = lodashStable.map(values, function(value) { + var result = index ? func(undefined, value) : func(value); + return [result, 1 / result]; + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('`_.' + methodName + '` should convert objects to `NaN`', function() { + assert.deepStrictEqual(func(0, {}), NaN); + assert.deepStrictEqual(func({}, 0), NaN); + }); + + it('`_.' + methodName + '` should convert symbols to `NaN`', function() { + if (Symbol) { + assert.deepStrictEqual(func(0, symbol), NaN); + assert.deepStrictEqual(func(symbol, 0), NaN); + } + }); + + it('`_.' + methodName + '` should return an unwrapped value when implicitly chaining', function() { + var actual = _(1)[methodName](2); + assert.notOk(actual instanceof _); + }); + + it('`_.' + methodName + '` should return a wrapped value when explicitly chaining', function() { + var actual = _(1).chain()[methodName](2); + assert.ok(actual instanceof _); + }); + }); +}); diff --git a/test/max.js b/test/max.js new file mode 100644 index 000000000..28fca7954 --- /dev/null +++ b/test/max.js @@ -0,0 +1,27 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, noop } from './utils.js'; +import max from '../max.js'; + +describe('max', function() { + it('should return the largest value from a collection', function() { + assert.strictEqual(max([1, 2, 3]), 3); + }); + + it('should return `undefined` for empty collections', function() { + var values = falsey.concat([[]]), + expected = lodashStable.map(values, noop); + + var actual = lodashStable.map(values, function(value, index) { + try { + return index ? max(value) : max(); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with non-numeric collection values', function() { + assert.strictEqual(max(['a', 'b']), 'b'); + }); +}); diff --git a/test/mean.test.js b/test/mean.test.js new file mode 100644 index 000000000..f0f09061b --- /dev/null +++ b/test/mean.test.js @@ -0,0 +1,18 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { empties, stubNaN } from './utils.js'; +import mean from '../mean.js'; + +describe('mean', function() { + it('should return the mean of an array of numbers', function() { + var array = [4, 2, 8, 6]; + assert.strictEqual(mean(array), 5); + }); + + it('should return `NaN` when passing empty `array` values', function() { + var expected = lodashStable.map(empties, stubNaN), + actual = lodashStable.map(empties, mean); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/meanBy.js b/test/meanBy.js new file mode 100644 index 000000000..987eebdd3 --- /dev/null +++ b/test/meanBy.js @@ -0,0 +1,31 @@ +import assert from 'assert'; +import { slice } from './utils.js'; +import meanBy from '../meanBy.js'; + +describe('meanBy', function() { + var objects = [{ 'a': 2 }, { 'a': 3 }, { 'a': 1 }]; + + it('should work with an `iteratee`', function() { + var actual = meanBy(objects, function(object) { + return object.a; + }); + + assert.deepStrictEqual(actual, 2); + }); + + it('should provide correct `iteratee` arguments', function() { + var args; + + meanBy(objects, function() { + args || (args = slice.call(arguments)); + }); + + assert.deepStrictEqual(args, [{ 'a': 2 }]); + }); + + it('should work with `_.property` shorthands', function() { + var arrays = [[2], [3], [1]]; + assert.strictEqual(meanBy(arrays, 0), 2); + assert.strictEqual(meanBy(objects, 'a'), 2); + }); +}); diff --git a/test/memoize.test.js b/test/memoize.test.js new file mode 100644 index 000000000..e3f92cd6d --- /dev/null +++ b/test/memoize.test.js @@ -0,0 +1,178 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { noop, stubTrue, identity } from './utils.js'; +import memoize from '../memoize.js'; +import isFunction from '../isFunction.js'; + +describe('memoize', function() { + function CustomCache() { + this.clear(); + } + + CustomCache.prototype = { + 'clear': function() { + this.__data__ = []; + return this; + }, + 'get': function(key) { + var entry = lodashStable.find(this.__data__, ['key', key]); + return entry && entry.value; + }, + 'has': function(key) { + return lodashStable.some(this.__data__, ['key', key]); + }, + 'set': function(key, value) { + this.__data__.push({ 'key': key, 'value': value }); + return this; + } + }; + + function ImmutableCache() { + this.__data__ = []; + } + + ImmutableCache.prototype = lodashStable.create(CustomCache.prototype, { + 'constructor': ImmutableCache, + 'clear': function() { + return new ImmutableCache; + }, + 'set': function(key, value) { + var result = new ImmutableCache; + result.__data__ = this.__data__.concat({ 'key': key, 'value': value }); + return result; + } + }); + + it('should memoize results based on the first argument given', function() { + var memoized = memoize(function(a, b, c) { + return a + b + c; + }); + + assert.strictEqual(memoized(1, 2, 3), 6); + assert.strictEqual(memoized(1, 3, 5), 6); + }); + + it('should support a `resolver`', function() { + var fn = function(a, b, c) { return a + b + c; }, + memoized = memoize(fn, fn); + + assert.strictEqual(memoized(1, 2, 3), 6); + assert.strictEqual(memoized(1, 3, 5), 9); + }); + + it('should use `this` binding of function for `resolver`', function() { + var fn = function(a, b, c) { return a + this.b + this.c; }, + memoized = memoize(fn, fn); + + var object = { 'memoized': memoized, 'b': 2, 'c': 3 }; + assert.strictEqual(object.memoized(1), 6); + + object.b = 3; + object.c = 5; + assert.strictEqual(object.memoized(1), 9); + }); + + it('should throw a TypeError if `resolve` is truthy and not a function', function() { + assert.throws(function() { memoize(noop, true); }, TypeError); + }); + + it('should not error if `resolver` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, stubTrue); + + var actual = lodashStable.map(values, function(resolver, index) { + try { + return isFunction(index ? memoize(noop, resolver) : memoize(noop)); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should check cache for own properties', function() { + var props = [ + 'constructor', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'toLocaleString', + 'toString', + 'valueOf' + ]; + + var memoized = memoize(identity); + + var actual = lodashStable.map(props, function(value) { + return memoized(value); + }); + + assert.deepStrictEqual(actual, props); + }); + + it('should cache the `__proto__` key', function() { + var array = [], + key = '__proto__'; + + lodashStable.times(2, function(index) { + var count = 0, + resolver = index ? identity : undefined; + + var memoized = memoize(function() { + count++; + return array; + }, resolver); + + var cache = memoized.cache; + + memoized(key); + memoized(key); + + assert.strictEqual(count, 1); + assert.strictEqual(cache.get(key), array); + assert.ok(!(cache.__data__ instanceof Array)); + assert.strictEqual(cache.delete(key), true); + }); + }); + + it('should allow `_.memoize.Cache` to be customized', function() { + var oldCache = memoize.Cache; + memoize.Cache = CustomCache; + + var memoized = memoize(function(object) { + return object.id; + }); + + var cache = memoized.cache, + key1 = { 'id': 'a' }, + key2 = { 'id': 'b' }; + + assert.strictEqual(memoized(key1), 'a'); + assert.strictEqual(cache.has(key1), true); + + assert.strictEqual(memoized(key2), 'b'); + assert.strictEqual(cache.has(key2), true); + + memoize.Cache = oldCache; + }); + + it('should works with an immutable `_.memoize.Cache` ', function() { + var oldCache = memoize.Cache; + memoize.Cache = ImmutableCache; + + var memoized = memoize(function(object) { + return object.id; + }); + + var key1 = { 'id': 'a' }, + key2 = { 'id': 'b' }; + + memoized(key1); + memoized(key2); + + var cache = memoized.cache; + assert.strictEqual(cache.has(key1), true); + assert.strictEqual(cache.has(key2), true); + + memoize.Cache = oldCache; + }); +}); diff --git a/test/memoizeCapped.test.js b/test/memoizeCapped.test.js new file mode 100644 index 000000000..c87920a2d --- /dev/null +++ b/test/memoizeCapped.test.js @@ -0,0 +1,21 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { identity, MAX_MEMOIZE_SIZE } from './utils.js'; +import _memoizeCapped from '../.internal/memoizeCapped.js'; + +describe('memoizeCapped', function() { + var func = _memoizeCapped; + + it('should enforce a max cache size of `MAX_MEMOIZE_SIZE`', function() { + if (func) { + var memoized = func(identity), + cache = memoized.cache; + + lodashStable.times(MAX_MEMOIZE_SIZE, memoized); + assert.strictEqual(cache.size, MAX_MEMOIZE_SIZE); + + memoized(MAX_MEMOIZE_SIZE); + assert.strictEqual(cache.size, 1); + } + }); +}); diff --git a/test/merge.js b/test/merge.js new file mode 100644 index 000000000..097bfb493 --- /dev/null +++ b/test/merge.js @@ -0,0 +1,349 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { args, typedArrays, stubTrue, defineProperty, document } from './utils.js'; +import merge from '../merge.js'; +import isArguments from '../isArguments.js'; + +describe('merge', function() { + it('should merge `source` into `object`', function() { + var names = { + 'characters': [ + { 'name': 'barney' }, + { 'name': 'fred' } + ] + }; + + var ages = { + 'characters': [ + { 'age': 36 }, + { 'age': 40 } + ] + }; + + var heights = { + 'characters': [ + { 'height': '5\'4"' }, + { 'height': '5\'5"' } + ] + }; + + var expected = { + 'characters': [ + { 'name': 'barney', 'age': 36, 'height': '5\'4"' }, + { 'name': 'fred', 'age': 40, 'height': '5\'5"' } + ] + }; + + assert.deepStrictEqual(merge(names, ages, heights), expected); + }); + + it('should merge sources containing circular references', function() { + var object = { + 'foo': { 'a': 1 }, + 'bar': { 'a': 2 } + }; + + var source = { + 'foo': { 'b': { 'c': { 'd': {} } } }, + 'bar': {} + }; + + source.foo.b.c.d = source; + source.bar.b = source.foo.b; + + var actual = merge(object, source); + + assert.notStrictEqual(actual.bar.b, actual.foo.b); + assert.strictEqual(actual.foo.b.c.d, actual.foo.b.c.d.foo.b.c.d); + }); + + it('should work with four arguments', function() { + var expected = { 'a': 4 }, + actual = merge({ 'a': 1 }, { 'a': 2 }, { 'a': 3 }, expected); + + assert.deepStrictEqual(actual, expected); + }); + + it('should merge onto function `object` values', function() { + function Foo() {} + + var source = { 'a': 1 }, + actual = merge(Foo, source); + + assert.strictEqual(actual, Foo); + assert.strictEqual(Foo.a, 1); + }); + + it('should merge first source object properties to function', function() { + var fn = function() {}, + object = { 'prop': {} }, + actual = merge({ 'prop': fn }, object); + + assert.deepStrictEqual(actual, object); + }); + + it('should merge first and second source object properties to function', function() { + var fn = function() {}, + object = { 'prop': {} }, + actual = merge({ 'prop': fn }, { 'prop': fn }, object); + + assert.deepStrictEqual(actual, object); + }); + + it('should not merge onto function values of sources', function() { + var source1 = { 'a': function() {} }, + source2 = { 'a': { 'b': 2 } }, + expected = { 'a': { 'b': 2 } }, + actual = merge({}, source1, source2); + + assert.deepStrictEqual(actual, expected); + assert.ok(!('b' in source1.a)); + + actual = merge(source1, source2); + assert.deepStrictEqual(actual, expected); + }); + + it('should merge onto non-plain `object` values', function() { + function Foo() {} + + var object = new Foo, + actual = merge(object, { 'a': 1 }); + + assert.strictEqual(actual, object); + assert.strictEqual(object.a, 1); + }); + + it('should treat sparse array sources as dense', function() { + var array = [1]; + array[2] = 3; + + var actual = merge([], array), + expected = array.slice(); + + expected[1] = undefined; + + assert.ok('1' in actual); + assert.deepStrictEqual(actual, expected); + }); + + it('should merge `arguments` objects', function() { + var object1 = { 'value': args }, + object2 = { 'value': { '3': 4 } }, + expected = { '0': 1, '1': 2, '2': 3, '3': 4 }, + actual = merge(object1, object2); + + assert.ok(!('3' in args)); + assert.ok(!isArguments(actual.value)); + assert.deepStrictEqual(actual.value, expected); + object1.value = args; + + actual = merge(object2, object1); + assert.ok(!isArguments(actual.value)); + assert.deepStrictEqual(actual.value, expected); + + expected = { '0': 1, '1': 2, '2': 3 }; + + actual = merge({}, object1); + assert.ok(!isArguments(actual.value)); + assert.deepStrictEqual(actual.value, expected); + }); + + it('should merge typed arrays', function() { + var array1 = [0], + array2 = [0, 0], + array3 = [0, 0, 0, 0], + array4 = [0, 0, 0, 0, 0, 0, 0, 0]; + + var arrays = [array2, array1, array4, array3, array2, array4, array4, array3, array2], + buffer = ArrayBuffer && new ArrayBuffer(8); + + var expected = lodashStable.map(typedArrays, function(type, index) { + var array = arrays[index].slice(); + array[0] = 1; + return root[type] ? { 'value': array } : false; + }); + + var actual = lodashStable.map(typedArrays, function(type) { + var Ctor = root[type]; + return Ctor ? merge({ 'value': new Ctor(buffer) }, { 'value': [1] }) : false; + }); + + assert.ok(lodashStable.isArray(actual)); + assert.deepStrictEqual(actual, expected); + + expected = lodashStable.map(typedArrays, function(type, index) { + var array = arrays[index].slice(); + array.push(1); + return root[type] ? { 'value': array } : false; + }); + + actual = lodashStable.map(typedArrays, function(type, index) { + var Ctor = root[type], + array = lodashStable.range(arrays[index].length); + + array.push(1); + return Ctor ? merge({ 'value': array }, { 'value': new Ctor(buffer) }) : false; + }); + + assert.ok(lodashStable.isArray(actual)); + assert.deepStrictEqual(actual, expected); + }); + + it('should assign `null` values', function() { + var actual = merge({ 'a': 1 }, { 'a': null }); + assert.strictEqual(actual.a, null); + }); + + it('should assign non array/buffer/typed-array/plain-object source values directly', function() { + function Foo() {} + + var values = [new Foo, new Boolean, new Date, Foo, new Number, new String, new RegExp], + expected = lodashStable.map(values, stubTrue); + + var actual = lodashStable.map(values, function(value) { + var object = merge({}, { 'a': value, 'b': { 'c': value } }); + return object.a === value && object.b.c === value; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should clone buffer source values', function() { + if (Buffer) { + var buffer = new Buffer([1]), + actual = merge({}, { 'value': buffer }).value; + + assert.ok(lodashStable.isBuffer(actual)); + assert.strictEqual(actual[0], buffer[0]); + assert.notStrictEqual(actual, buffer); + } + }); + + it('should deep clone array/typed-array/plain-object source values', function() { + var typedArray = Uint8Array + ? new Uint8Array([1]) + : { 'buffer': [1] }; + + var props = ['0', 'buffer', 'a'], + values = [[{ 'a': 1 }], typedArray, { 'a': [1] }], + expected = lodashStable.map(values, stubTrue); + + var actual = lodashStable.map(values, function(value, index) { + var key = props[index], + object = merge({}, { 'value': value }), + subValue = value[key], + newValue = object.value, + newSubValue = newValue[key]; + + return ( + newValue !== value && + newSubValue !== subValue && + lodashStable.isEqual(newValue, value) + ); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should not augment source objects', function() { + var source1 = { 'a': [{ 'a': 1 }] }, + source2 = { 'a': [{ 'b': 2 }] }, + actual = merge({}, source1, source2); + + assert.deepStrictEqual(source1.a, [{ 'a': 1 }]); + assert.deepStrictEqual(source2.a, [{ 'b': 2 }]); + assert.deepStrictEqual(actual.a, [{ 'a': 1, 'b': 2 }]); + + var source1 = { 'a': [[1, 2, 3]] }, + source2 = { 'a': [[3, 4]] }, + actual = merge({}, source1, source2); + + assert.deepStrictEqual(source1.a, [[1, 2, 3]]); + assert.deepStrictEqual(source2.a, [[3, 4]]); + assert.deepStrictEqual(actual.a, [[3, 4, 3]]); + }); + + it('should merge plain objects onto non-plain objects', function() { + function Foo(object) { + lodashStable.assign(this, object); + } + + var object = { 'a': 1 }, + actual = merge(new Foo, object); + + assert.ok(actual instanceof Foo); + assert.deepStrictEqual(actual, new Foo(object)); + + actual = merge([new Foo], [object]); + assert.ok(actual[0] instanceof Foo); + assert.deepStrictEqual(actual, [new Foo(object)]); + }); + + it('should not overwrite existing values with `undefined` values of object sources', function() { + var actual = merge({ 'a': 1 }, { 'a': undefined, 'b': undefined }); + assert.deepStrictEqual(actual, { 'a': 1, 'b': undefined }); + }); + + it('should not overwrite existing values with `undefined` values of array sources', function() { + var array = [1]; + array[2] = 3; + + var actual = merge([4, 5, 6], array), + expected = [1, 5, 3]; + + assert.deepStrictEqual(actual, expected); + + array = [1, , 3]; + array[1] = undefined; + + actual = merge([4, 5, 6], array); + assert.deepStrictEqual(actual, expected); + }); + + it('should skip merging when `object` and `source` are the same value', function() { + var object = {}, + pass = true; + + defineProperty(object, 'a', { + 'configurable': true, + 'enumerable': true, + 'get': function() { pass = false; }, + 'set': function() { pass = false; } + }); + + merge(object, object); + assert.ok(pass); + }); + + it('should convert values to arrays when merging arrays of `source`', function() { + var object = { 'a': { '1': 'y', 'b': 'z', 'length': 2 } }, + actual = merge(object, { 'a': ['x'] }); + + assert.deepStrictEqual(actual, { 'a': ['x', 'y'] }); + + actual = merge({ 'a': {} }, { 'a': [] }); + assert.deepStrictEqual(actual, { 'a': [] }); + }); + + it('should not convert strings to arrays when merging arrays of `source`', function() { + var object = { 'a': 'abcde' }, + actual = merge(object, { 'a': ['x', 'y', 'z'] }); + + assert.deepStrictEqual(actual, { 'a': ['x', 'y', 'z'] }); + }); + + it('should not error on DOM elements', function() { + var object1 = { 'el': document && document.createElement('div') }, + object2 = { 'el': document && document.createElement('div') }, + pairs = [[{}, object1], [object1, object2]], + expected = lodashStable.map(pairs, stubTrue); + + var actual = lodashStable.map(pairs, function(pair) { + try { + return merge(pair[0], pair[1]).el === pair[1].el; + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/mergeWith.js b/test/mergeWith.js new file mode 100644 index 000000000..45f10a907 --- /dev/null +++ b/test/mergeWith.js @@ -0,0 +1,64 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { noop, identity, isNpm, mapCaches } from './utils.js'; +import mergeWith from '../mergeWith.js'; +import last from '../last.js'; + +describe('mergeWith', function() { + it('should handle merging when `customizer` returns `undefined`', function() { + var actual = mergeWith({ 'a': { 'b': [1, 1] } }, { 'a': { 'b': [0] } }, noop); + assert.deepStrictEqual(actual, { 'a': { 'b': [0, 1] } }); + + actual = mergeWith([], [undefined], identity); + assert.deepStrictEqual(actual, [undefined]); + }); + + it('should clone sources when `customizer` returns `undefined`', function() { + var source1 = { 'a': { 'b': { 'c': 1 } } }, + source2 = { 'a': { 'b': { 'd': 2 } } }; + + mergeWith({}, source1, source2, noop); + assert.deepStrictEqual(source1.a.b, { 'c': 1 }); + }); + + it('should defer to `customizer` for non `undefined` results', function() { + var actual = mergeWith({ 'a': { 'b': [0, 1] } }, { 'a': { 'b': [2] } }, function(a, b) { + return lodashStable.isArray(a) ? a.concat(b) : undefined; + }); + + assert.deepStrictEqual(actual, { 'a': { 'b': [0, 1, 2] } }); + }); + + it('should provide `stack` to `customizer`', function() { + var actual; + + mergeWith({}, { 'a': { 'b': 2 } }, function() { + actual = last(arguments); + }); + + assert.ok(isNpm + ? actual.constructor.name == 'Stack' + : actual instanceof mapCaches.Stack + ); + }); + + it('should overwrite primitives with source object clones', function() { + var actual = mergeWith({ 'a': 0 }, { 'a': { 'b': ['c'] } }, function(a, b) { + return lodashStable.isArray(a) ? a.concat(b) : undefined; + }); + + assert.deepStrictEqual(actual, { 'a': { 'b': ['c'] } }); + }); + + it('should pop the stack of sources for each sibling property', function() { + var array = ['b', 'c'], + object = { 'a': ['a'] }, + source = { 'a': array, 'b': array }; + + var actual = mergeWith(object, source, function(a, b) { + return lodashStable.isArray(a) ? a.concat(b) : undefined; + }); + + assert.deepStrictEqual(actual, { 'a': ['a', 'b', 'c'], 'b': ['b', 'c'] }); + }); +}); diff --git a/test/method.js b/test/method.js new file mode 100644 index 000000000..617c89105 --- /dev/null +++ b/test/method.js @@ -0,0 +1,132 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { stubOne, _, stubTwo, stubThree, stubFour, noop, slice } from './utils.js'; +import constant from '../constant.js'; + +describe('method', function() { + it('should create a function that calls a method of a given object', function() { + var object = { 'a': stubOne }; + + lodashStable.each(['a', ['a']], function(path) { + var method = _.method(path); + assert.strictEqual(method.length, 1); + assert.strictEqual(method(object), 1); + }); + }); + + it('should work with deep property values', function() { + var object = { 'a': { 'b': stubTwo } }; + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + var method = _.method(path); + assert.strictEqual(method(object), 2); + }); + }); + + it('should work with a non-string `path`', function() { + var array = lodashStable.times(3, constant); + + lodashStable.each([1, [1]], function(path) { + var method = _.method(path); + assert.strictEqual(method(array), 1); + }); + }); + + it('should coerce `path` to a string', function() { + function fn() {} + fn.toString = lodashStable.constant('fn'); + + var expected = [1, 2, 3, 4], + object = { 'null': stubOne, 'undefined': stubTwo, 'fn': stubThree, '[object Object]': stubFour }, + paths = [null, undefined, fn, {}]; + + lodashStable.times(2, function(index) { + var actual = lodashStable.map(paths, function(path) { + var method = _.method(index ? [path] : path); + return method(object); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('should work with inherited property values', function() { + function Foo() {} + Foo.prototype.a = stubOne; + + lodashStable.each(['a', ['a']], function(path) { + var method = _.method(path); + assert.strictEqual(method(new Foo), 1); + }); + }); + + it('should use a key over a path', function() { + var object = { 'a.b': stubOne, 'a': { 'b': stubTwo } }; + + lodashStable.each(['a.b', ['a.b']], function(path) { + var method = _.method(path); + assert.strictEqual(method(object), 1); + }); + }); + + it('should return `undefined` when `object` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, noop); + + lodashStable.each(['constructor', ['constructor']], function(path) { + var method = _.method(path); + + var actual = lodashStable.map(values, function(value, index) { + return index ? method(value) : method(); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('should return `undefined` for deep paths when `object` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, noop); + + lodashStable.each(['constructor.prototype.valueOf', ['constructor', 'prototype', 'valueOf']], function(path) { + var method = _.method(path); + + var actual = lodashStable.map(values, function(value, index) { + return index ? method(value) : method(); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('should return `undefined` if parts of `path` are missing', function() { + var object = {}; + + lodashStable.each(['a', 'a[1].b.c', ['a'], ['a', '1', 'b', 'c']], function(path) { + var method = _.method(path); + assert.strictEqual(method(object), undefined); + }); + }); + + it('should apply partial arguments to function', function() { + var object = { + 'fn': function() { + return slice.call(arguments); + } + }; + + lodashStable.each(['fn', ['fn']], function(path) { + var method = _.method(path, 1, 2, 3); + assert.deepStrictEqual(method(object), [1, 2, 3]); + }); + }); + + it('should invoke deep property methods with the correct `this` binding', function() { + var object = { 'a': { 'b': function() { return this.c; }, 'c': 1 } }; + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + var method = _.method(path); + assert.strictEqual(method(object), 1); + }); + }); +}); diff --git a/test/methodOf.js b/test/methodOf.js new file mode 100644 index 000000000..29170a29f --- /dev/null +++ b/test/methodOf.js @@ -0,0 +1,131 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { stubOne, _, stubTwo, stubThree, stubFour, noop, slice } from './utils.js'; +import constant from '../constant.js'; + +describe('methodOf', function() { + it('should create a function that calls a method of a given key', function() { + var object = { 'a': stubOne }; + + lodashStable.each(['a', ['a']], function(path) { + var methodOf = _.methodOf(object); + assert.strictEqual(methodOf.length, 1); + assert.strictEqual(methodOf(path), 1); + }); + }); + + it('should work with deep property values', function() { + var object = { 'a': { 'b': stubTwo } }; + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + var methodOf = _.methodOf(object); + assert.strictEqual(methodOf(path), 2); + }); + }); + + it('should work with a non-string `path`', function() { + var array = lodashStable.times(3, constant); + + lodashStable.each([1, [1]], function(path) { + var methodOf = _.methodOf(array); + assert.strictEqual(methodOf(path), 1); + }); + }); + + it('should coerce `path` to a string', function() { + function fn() {} + fn.toString = lodashStable.constant('fn'); + + var expected = [1, 2, 3, 4], + object = { 'null': stubOne, 'undefined': stubTwo, 'fn': stubThree, '[object Object]': stubFour }, + paths = [null, undefined, fn, {}]; + + lodashStable.times(2, function(index) { + var actual = lodashStable.map(paths, function(path) { + var methodOf = _.methodOf(object); + return methodOf(index ? [path] : path); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('should work with inherited property values', function() { + function Foo() {} + Foo.prototype.a = stubOne; + + lodashStable.each(['a', ['a']], function(path) { + var methodOf = _.methodOf(new Foo); + assert.strictEqual(methodOf(path), 1); + }); + }); + + it('should use a key over a path', function() { + var object = { 'a.b': stubOne, 'a': { 'b': stubTwo } }; + + lodashStable.each(['a.b', ['a.b']], function(path) { + var methodOf = _.methodOf(object); + assert.strictEqual(methodOf(path), 1); + }); + }); + + it('should return `undefined` when `object` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, noop); + + lodashStable.each(['constructor', ['constructor']], function(path) { + var actual = lodashStable.map(values, function(value, index) { + var methodOf = index ? _.methodOf() : _.methodOf(value); + return methodOf(path); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('should return `undefined` for deep paths when `object` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, noop); + + lodashStable.each(['constructor.prototype.valueOf', ['constructor', 'prototype', 'valueOf']], function(path) { + var actual = lodashStable.map(values, function(value, index) { + var methodOf = index ? _.methodOf() : _.methodOf(value); + return methodOf(path); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('should return `undefined` if parts of `path` are missing', function() { + var object = {}, + methodOf = _.methodOf(object); + + lodashStable.each(['a', 'a[1].b.c', ['a'], ['a', '1', 'b', 'c']], function(path) { + assert.strictEqual(methodOf(path), undefined); + }); + }); + + it('should apply partial arguments to function', function() { + var object = { + 'fn': function() { + return slice.call(arguments); + } + }; + + var methodOf = _.methodOf(object, 1, 2, 3); + + lodashStable.each(['fn', ['fn']], function(path) { + assert.deepStrictEqual(methodOf(path), [1, 2, 3]); + }); + }); + + it('should invoke deep property methods with the correct `this` binding', function() { + var object = { 'a': { 'b': function() { return this.c; }, 'c': 1 } }, + methodOf = _.methodOf(object); + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + assert.strictEqual(methodOf(path), 1); + }); + }); +}); diff --git a/test/methods-using-createWrapper.js b/test/methods-using-createWrapper.js new file mode 100644 index 000000000..df44aa770 --- /dev/null +++ b/test/methods-using-createWrapper.js @@ -0,0 +1,198 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice, _, push, HOT_COUNT } from './utils.js'; +import bind from '../bind.js'; +import bindKey from '../bindKey.js'; +import partial from '../partial.js'; +import partialRight from '../partialRight.js'; +import last from '../last.js'; + +describe('methods using `createWrapper`', function() { + function fn() { + return slice.call(arguments); + } + + var ph1 = bind.placeholder, + ph2 = bindKey.placeholder, + ph3 = partial.placeholder, + ph4 = partialRight.placeholder; + + it('should work with combinations of partial functions', function() { + var a = partial(fn), + b = partialRight(a, 3), + c = partial(b, 1); + + assert.deepStrictEqual(c(2), [1, 2, 3]); + }); + + it('should work with combinations of bound and partial functions', function() { + var fn = function() { + var result = [this.a]; + push.apply(result, arguments); + return result; + }; + + var expected = [1, 2, 3, 4], + object = { 'a': 1, 'fn': fn }; + + var a = bindKey(object, 'fn'), + b = partialRight(a, 4), + c = partial(b, 2); + + assert.deepStrictEqual(c(3), expected); + + a = bind(fn, object); + b = partialRight(a, 4); + c = partial(b, 2); + + assert.deepStrictEqual(c(3), expected); + + a = partial(fn, 2); + b = bind(a, object); + c = partialRight(b, 4); + + assert.deepStrictEqual(c(3), expected); + }); + + it('should ensure `new combo` is an instance of `func`', function() { + function Foo(a, b, c) { + return b === 0 && object; + } + + var combo = partial(partialRight(Foo, 3), 1), + object = {}; + + assert.ok(new combo(2) instanceof Foo); + assert.strictEqual(new combo(0), object); + }); + + it('should work with combinations of functions with placeholders', function() { + var expected = [1, 2, 3, 4, 5, 6], + object = { 'fn': fn }; + + var a = bindKey(object, 'fn', ph2, 2), + b = partialRight(a, ph4, 6), + c = partial(b, 1, ph3, 4); + + assert.deepStrictEqual(c(3, 5), expected); + + a = bind(fn, object, ph1, 2); + b = partialRight(a, ph4, 6); + c = partial(b, 1, ph3, 4); + + assert.deepStrictEqual(c(3, 5), expected); + + a = partial(fn, ph3, 2); + b = bind(a, object, 1, ph1, 4); + c = partialRight(b, ph4, 6); + + assert.deepStrictEqual(c(3, 5), expected); + }); + + it('should work with combinations of functions with overlapping placeholders', function() { + var expected = [1, 2, 3, 4], + object = { 'fn': fn }; + + var a = bindKey(object, 'fn', ph2, 2), + b = partialRight(a, ph4, 4), + c = partial(b, ph3, 3); + + assert.deepStrictEqual(c(1), expected); + + a = bind(fn, object, ph1, 2); + b = partialRight(a, ph4, 4); + c = partial(b, ph3, 3); + + assert.deepStrictEqual(c(1), expected); + + a = partial(fn, ph3, 2); + b = bind(a, object, ph1, 3); + c = partialRight(b, ph4, 4); + + assert.deepStrictEqual(c(1), expected); + }); + + it('should work with recursively bound functions', function() { + var fn = function() { + return this.a; + }; + + var a = bind(fn, { 'a': 1 }), + b = bind(a, { 'a': 2 }), + c = bind(b, { 'a': 3 }); + + assert.strictEqual(c(), 1); + }); + + it('should work when hot', function() { + lodashStable.times(2, function(index) { + var fn = function() { + var result = [this]; + push.apply(result, arguments); + return result; + }; + + var object = {}, + bound1 = index ? bind(fn, object, 1) : bind(fn, object), + expected = [object, 1, 2, 3]; + + var actual = last(lodashStable.times(HOT_COUNT, function() { + var bound2 = index ? bind(bound1, null, 2) : bind(bound1); + return index ? bound2(3) : bound2(1, 2, 3); + })); + + assert.deepStrictEqual(actual, expected); + + actual = last(lodashStable.times(HOT_COUNT, function() { + var bound1 = index ? bind(fn, object, 1) : bind(fn, object), + bound2 = index ? bind(bound1, null, 2) : bind(bound1); + + return index ? bound2(3) : bound2(1, 2, 3); + })); + + assert.deepStrictEqual(actual, expected); + }); + + lodashStable.each(['curry', 'curryRight'], function(methodName, index) { + var fn = function(a, b, c) { return [a, b, c]; }, + curried = _[methodName](fn), + expected = index ? [3, 2, 1] : [1, 2, 3]; + + var actual = last(lodashStable.times(HOT_COUNT, function() { + return curried(1)(2)(3); + })); + + assert.deepStrictEqual(actual, expected); + + actual = last(lodashStable.times(HOT_COUNT, function() { + var curried = _[methodName](fn); + return curried(1)(2)(3); + })); + + assert.deepStrictEqual(actual, expected); + }); + + lodashStable.each(['partial', 'partialRight'], function(methodName, index) { + var func = _[methodName], + fn = function() { return slice.call(arguments); }, + par1 = func(fn, 1), + expected = index ? [3, 2, 1] : [1, 2, 3]; + + var actual = last(lodashStable.times(HOT_COUNT, function() { + var par2 = func(par1, 2); + return par2(3); + })); + + assert.deepStrictEqual(actual, expected); + + actual = last(lodashStable.times(HOT_COUNT, function() { + var par1 = func(fn, 1), + par2 = func(par1, 2); + + return par2(3); + })); + + assert.deepStrictEqual(actual, expected); + }); + }); +}); diff --git a/test/min.js b/test/min.js new file mode 100644 index 000000000..271d44431 --- /dev/null +++ b/test/min.js @@ -0,0 +1,27 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, noop } from './utils.js'; +import min from '../min.js'; + +describe('min', function() { + it('should return the smallest value from a collection', function() { + assert.strictEqual(min([1, 2, 3]), 1); + }); + + it('should return `undefined` for empty collections', function() { + var values = falsey.concat([[]]), + expected = lodashStable.map(values, noop); + + var actual = lodashStable.map(values, function(value, index) { + try { + return index ? min(value) : min(); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with non-numeric collection values', function() { + assert.strictEqual(min(['a', 'b']), 'a'); + }); +}); diff --git a/test/mixin.js b/test/mixin.js new file mode 100644 index 000000000..4b78cf848 --- /dev/null +++ b/test/mixin.js @@ -0,0 +1,189 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice, getUnwrappedValue, noop } from './utils.js'; +import has from '../has.js'; +import mixin from '../mixin.js'; +import prototype from '../prototype.js'; +import countBy from '../countBy.js'; +import filter from '../filter.js'; + +describe('mixin', function() { + function reset(wrapper) { + delete wrapper.a; + delete wrapper.prototype.a; + delete wrapper.b; + delete wrapper.prototype.b; + } + + function Wrapper(value) { + if (!(this instanceof Wrapper)) { + return new Wrapper(value); + } + if (has(value, '__wrapped__')) { + var actions = slice.call(value.__actions__), + chain = value.__chain__; + + value = value.__wrapped__; + } + this.__wrapped__ = value; + this.__actions__ = actions || []; + this.__chain__ = chain || false; + } + + Wrapper.prototype.value = function() { + return getUnwrappedValue(this); + }; + + var array = ['a'], + source = { 'a': function(array) { return array[0]; }, 'b': 'B' }; + + it('should mixin `source` methods into lodash', function() { + mixin(source); + + assert.strictEqual(_.a(array), 'a'); + assert.strictEqual(_(array).a().value(), 'a'); + assert.notOk('b' in _); + assert.notOk('b' in prototype); + + reset(_); + }); + + it('should mixin chaining methods by reference', function() { + mixin(source); + _.a = stubB; + + assert.strictEqual(_.a(array), 'b'); + assert.strictEqual(_(array).a().value(), 'a'); + + reset(_); + }); + + it('should use a default `object` of `this`', function() { + var object = lodashStable.create(_); + object.mixin(source); + + assert.strictEqual(object.a(array), 'a'); + assert.ok(!('a' in _)); + assert.ok(!('a' in prototype)); + + reset(_); + }); + + it('should accept an `object`', function() { + var object = {}; + mixin(object, source); + assert.strictEqual(object.a(array), 'a'); + }); + + it('should accept a function `object`', function() { + mixin(Wrapper, source); + + var wrapped = Wrapper(array), + actual = wrapped.a(); + + assert.strictEqual(actual.value(), 'a'); + assert.ok(actual instanceof Wrapper); + + reset(Wrapper); + }); + + it('should return `object`', function() { + var object = {}; + assert.strictEqual(mixin(object, source), object); + assert.strictEqual(mixin(Wrapper, source), Wrapper); + assert.strictEqual(mixin(), _); + + reset(Wrapper); + }); + + it('should not assign inherited `source` methods', function() { + function Foo() {} + Foo.prototype.a = noop; + + var object = {}; + assert.strictEqual(mixin(object, new Foo), object); + }); + + it('should accept an `options`', function() { + function message(func, chain) { + return (func === _ ? 'lodash' : 'given') + ' function should ' + (chain ? '' : 'not ') + 'chain'; + } + + lodashStable.each([_, Wrapper], function(func) { + lodashStable.each([{ 'chain': false }, { 'chain': true }], function(options) { + if (func === _) { + mixin(source, options); + } else { + mixin(func, source, options); + } + var wrapped = func(array), + actual = wrapped.a(); + + if (options.chain) { + assert.strictEqual(actual.value(), 'a', message(func, true)); + assert.ok(actual instanceof func, message(func, true)); + } else { + assert.strictEqual(actual, 'a', message(func, false)); + assert.notOk(actual instanceof func, message(func, false)); + } + reset(func); + }); + }); + }); + + it('should not extend lodash when an `object` is given with an empty `options` object', function() { + mixin({ 'a': noop }, {}); + assert.ok(!('a' in _)); + reset(_); + }); + + it('should not error for non-object `options` values', function() { + var pass = true; + + try { + mixin({}, source, 1); + } catch (e) { + pass = false; + } + assert.ok(pass); + + pass = true; + + try { + mixin(source, 1); + } catch (e) { + pass = false; + } + assert.ok(pass); + + reset(_); + }); + + it('should not return the existing wrapped value when chaining', function() { + lodashStable.each([_, Wrapper], function(func) { + if (func === _) { + var wrapped = _(source), + actual = wrapped.mixin(); + + assert.strictEqual(actual.value(), _); + } + else { + wrapped = _(func); + actual = wrapped.mixin(source); + assert.notStrictEqual(actual, wrapped); + } + reset(func); + }); + }); + + it('should produce methods that work in a lazy sequence', function() { + mixin({ 'a': countBy, 'b': filter }); + + var array = lodashStable.range(LARGE_ARRAY_SIZE), + actual = _(array).a().map(square).b(isEven).take().value(); + + assert.deepEqual(actual, _.take(_.b(_.map(_.a(array), square), isEven))); + + reset(_); + }); +}); diff --git a/test/multiply.test.js b/test/multiply.test.js new file mode 100644 index 000000000..23e966fb9 --- /dev/null +++ b/test/multiply.test.js @@ -0,0 +1,15 @@ +import assert from 'assert'; +import multiply from '../multiply.js'; + +describe('multiply', function() { + it('should multiply two numbers', function() { + assert.strictEqual(multiply(6, 4), 24); + assert.strictEqual(multiply(-6, 4), -24); + assert.strictEqual(multiply(-6, -4), 24); + }); + + it('should coerce arguments to numbers', function() { + assert.strictEqual(multiply('6', '4'), 24); + assert.deepStrictEqual(multiply('x', 'y'), NaN); + }); +}); diff --git a/test/negate.js b/test/negate.js new file mode 100644 index 000000000..3f7725592 --- /dev/null +++ b/test/negate.js @@ -0,0 +1,39 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, isEven, stubTrue } from './utils.js'; + +describe('negate', function() { + it('should create a function that negates the result of `func`', function() { + var negate = _.negate(isEven); + + assert.strictEqual(negate(1), true); + assert.strictEqual(negate(2), false); + }); + + it('should create a function that negates the result of `func`', function() { + var negate = _.negate(isEven); + + assert.strictEqual(negate(1), true); + assert.strictEqual(negate(2), false); + }); + + it('should create a function that accepts multiple arguments', function() { + var argCount, + count = 5, + negate = _.negate(function() { argCount = arguments.length; }), + expected = lodashStable.times(count, stubTrue); + + var actual = lodashStable.times(count, function(index) { + switch (index) { + case 0: negate(); break; + case 1: negate(1); break; + case 2: negate(1, 2); break; + case 3: negate(1, 2, 3); break; + case 4: negate(1, 2, 3, 4); + } + return argCount == index; + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/noConflict.js b/test/noConflict.js new file mode 100644 index 000000000..419130d5b --- /dev/null +++ b/test/noConflict.js @@ -0,0 +1,33 @@ +import assert from 'assert'; +import { oldDash, coverage, document, isModularize, realm, filePath } from './utils.js'; +import noConflict from '../noConflict.js'; + +describe('noConflict', function() { + it('should return the `lodash` function', function() { + assert.strictEqual(noConflict(), oldDash); + assert.notStrictEqual(root._, oldDash); + root._ = oldDash; + }); + + it('should restore `_` only if `lodash` is the current `_` value', function() { + var object = root._ = {}; + assert.strictEqual(noConflict(), oldDash); + assert.strictEqual(root._, object); + root._ = oldDash; + }); + + it('should work with a `root` of `this`', function() { + if (!coverage && !document && !isModularize && realm.object) { + var fs = require('fs'), + vm = require('vm'), + expected = {}, + context = vm.createContext({ '_': expected, 'console': console }), + source = fs.readFileSync(filePath, 'utf8'); + + vm.runInContext(source + '\nthis.lodash = this._.noConflict()', context); + + assert.strictEqual(context._, expected); + assert.ok(context.lodash); + } + }); +}); diff --git a/test/now.js b/test/now.js new file mode 100644 index 000000000..6b2febb24 --- /dev/null +++ b/test/now.js @@ -0,0 +1,26 @@ +import assert from 'assert'; +import { _, stubA } from './utils.js'; + +describe('now', function() { + it('should return the number of milliseconds that have elapsed since the Unix epoch', function(done) { + var stamp = +new Date, + actual = _.now(); + + assert.ok(actual >= stamp); + + setTimeout(function() { + assert.ok(_.now() > actual); + done(); + }, 32); + }); + + it('should work with mocked `Date.now`', function() { + var now = Date.now; + Date.now = stubA; + + var actual = _.now(); + Date.now = now; + + assert.strictEqual(actual, 'a'); + }); +}); diff --git a/test/nth.js b/test/nth.js new file mode 100644 index 000000000..49c0fcf2e --- /dev/null +++ b/test/nth.js @@ -0,0 +1,69 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubA, stubB, noop } from './utils.js'; +import nth from '../nth.js'; + +describe('nth', function() { + var array = ['a', 'b', 'c', 'd']; + + it('should get the nth element of `array`', function() { + var actual = lodashStable.map(array, function(value, index) { + return nth(array, index); + }); + + assert.deepStrictEqual(actual, array); + }); + + it('should work with a negative `n`', function() { + var actual = lodashStable.map(lodashStable.range(1, array.length + 1), function(n) { + return nth(array, -n); + }); + + assert.deepStrictEqual(actual, ['d', 'c', 'b', 'a']); + }); + + it('should coerce `n` to an integer', function() { + var values = falsey, + expected = lodashStable.map(values, stubA); + + var actual = lodashStable.map(values, function(n) { + return n ? nth(array, n) : nth(array); + }); + + assert.deepStrictEqual(actual, expected); + + values = ['1', 1.6]; + expected = lodashStable.map(values, stubB); + + actual = lodashStable.map(values, function(n) { + return nth(array, n); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return `undefined` for empty arrays', function() { + var values = [null, undefined, []], + expected = lodashStable.map(values, noop); + + var actual = lodashStable.map(values, function(array) { + return nth(array, 1); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return `undefined` for non-indexes', function() { + var array = [1, 2], + values = [Infinity, array.length], + expected = lodashStable.map(values, noop); + + array[-1] = 3; + + var actual = lodashStable.map(values, function(n) { + return nth(array, n); + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/nthArg.js b/test/nthArg.js new file mode 100644 index 000000000..387e15f7d --- /dev/null +++ b/test/nthArg.js @@ -0,0 +1,65 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { args, falsey, stubA, stubB, noop } from './utils.js'; +import nthArg from '../nthArg.js'; + +describe('nthArg', function() { + var args = ['a', 'b', 'c', 'd']; + + it('should create a function that returns its nth argument', function() { + var actual = lodashStable.map(args, function(value, index) { + var func = nthArg(index); + return func.apply(undefined, args); + }); + + assert.deepStrictEqual(actual, args); + }); + + it('should work with a negative `n`', function() { + var actual = lodashStable.map(lodashStable.range(1, args.length + 1), function(n) { + var func = nthArg(-n); + return func.apply(undefined, args); + }); + + assert.deepStrictEqual(actual, ['d', 'c', 'b', 'a']); + }); + + it('should coerce `n` to an integer', function() { + var values = falsey, + expected = lodashStable.map(values, stubA); + + var actual = lodashStable.map(values, function(n) { + var func = n ? nthArg(n) : nthArg(); + return func.apply(undefined, args); + }); + + assert.deepStrictEqual(actual, expected); + + values = ['1', 1.6]; + expected = lodashStable.map(values, stubB); + + actual = lodashStable.map(values, function(n) { + var func = nthArg(n); + return func.apply(undefined, args); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return `undefined` for empty arrays', function() { + var func = nthArg(1); + assert.strictEqual(func(), undefined); + }); + + it('should return `undefined` for non-indexes', function() { + var values = [Infinity, args.length], + expected = lodashStable.map(values, noop); + + var actual = lodashStable.map(values, function(n) { + var func = nthArg(n); + return func.apply(undefined, args); + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/number-coercion-methods.js b/test/number-coercion-methods.js new file mode 100644 index 000000000..edf982d62 --- /dev/null +++ b/test/number-coercion-methods.js @@ -0,0 +1,248 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; + +import { + _, + identity, + whitespace, + MAX_SAFE_INTEGER, + MAX_INTEGER, + MAX_ARRAY_LENGTH, + symbol, + falsey, +} from './utils.js'; + +describe('number coercion methods', function() { + lodashStable.each(['toFinite', 'toInteger', 'toNumber', 'toSafeInteger'], function(methodName) { + var func = _[methodName]; + + it('`_.' + methodName + '` should preserve the sign of `0`', function() { + var values = [0, '0', -0, '-0'], + expected = [[0, Infinity], [0, Infinity], [-0, -Infinity], [-0, -Infinity]]; + + lodashStable.times(2, function(index) { + var others = lodashStable.map(values, index ? Object : identity); + + var actual = lodashStable.map(others, function(value) { + var result = func(value); + return [result, 1 / result]; + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + }); + + lodashStable.each(['toFinite', 'toInteger', 'toLength', 'toNumber', 'toSafeInteger'], function(methodName) { + var func = _[methodName], + isToFinite = methodName == 'toFinite', + isToLength = methodName == 'toLength', + isToNumber = methodName == 'toNumber', + isToSafeInteger = methodName == 'toSafeInteger'; + + function negative(string) { + return '-' + string; + } + + function pad(string) { + return whitespace + string + whitespace; + } + + function positive(string) { + return '+' + string; + } + + it('`_.' + methodName + '` should pass thru primitive number values', function() { + var values = [0, 1, NaN]; + + var expected = lodashStable.map(values, function(value) { + return (!isToNumber && value !== value) ? 0 : value; + }); + + var actual = lodashStable.map(values, func); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should convert number primitives and objects to numbers', function() { + var values = [2, 1.2, MAX_SAFE_INTEGER, MAX_INTEGER, Infinity, NaN]; + + var expected = lodashStable.map(values, function(value) { + if (!isToNumber) { + if (!isToFinite && value == 1.2) { + value = 1; + } + else if (value == Infinity) { + value = MAX_INTEGER; + } + else if (value !== value) { + value = 0; + } + if (isToLength || isToSafeInteger) { + value = Math.min(value, isToLength ? MAX_ARRAY_LENGTH : MAX_SAFE_INTEGER); + } + } + var neg = isToLength ? 0 : -value; + return [value, value, neg, neg]; + }); + + var actual = lodashStable.map(values, function(value) { + return [func(value), func(Object(value)), func(-value), func(Object(-value))]; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should convert string primitives and objects to numbers', function() { + var transforms = [identity, pad, positive, negative]; + + var values = [ + '10', '1.234567890', (MAX_SAFE_INTEGER + ''), + '1e+308', '1e308', '1E+308', '1E308', + '5e-324', '5E-324', + 'Infinity', 'NaN' + ]; + + var expected = lodashStable.map(values, function(value) { + var n = +value; + if (!isToNumber) { + if (!isToFinite && n == 1.234567890) { + n = 1; + } + else if (n == Infinity) { + n = MAX_INTEGER; + } + else if ((!isToFinite && n == Number.MIN_VALUE) || n !== n) { + n = 0; + } + if (isToLength || isToSafeInteger) { + n = Math.min(n, isToLength ? MAX_ARRAY_LENGTH : MAX_SAFE_INTEGER); + } + } + var neg = isToLength ? 0 : -n; + return [n, n, n, n, n, n, neg, neg]; + }); + + var actual = lodashStable.map(values, function(value) { + return lodashStable.flatMap(transforms, function(mod) { + return [func(mod(value)), func(Object(mod(value)))]; + }); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should convert binary/octal strings to numbers', function() { + var numbers = [42, 5349, 1715004], + transforms = [identity, pad], + values = ['0b101010', '0o12345', '0x1a2b3c']; + + var expected = lodashStable.map(numbers, function(n) { + return lodashStable.times(8, lodashStable.constant(n)); + }); + + var actual = lodashStable.map(values, function(value) { + var upper = value.toUpperCase(); + return lodashStable.flatMap(transforms, function(mod) { + return [func(mod(value)), func(Object(mod(value))), func(mod(upper)), func(Object(mod(upper)))]; + }); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should convert invalid binary/octal strings to `' + (isToNumber ? 'NaN' : '0') + '`', function() { + var transforms = [identity, pad, positive, negative], + values = ['0b', '0o', '0x', '0b1010102', '0o123458', '0x1a2b3x']; + + var expected = lodashStable.map(values, function(n) { + return lodashStable.times(8, lodashStable.constant(isToNumber ? NaN : 0)); + }); + + var actual = lodashStable.map(values, function(value) { + return lodashStable.flatMap(transforms, function(mod) { + return [func(mod(value)), func(Object(mod(value)))]; + }); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should convert symbols to `' + (isToNumber ? 'NaN' : '0') + '`', function() { + if (Symbol) { + var object1 = Object(symbol), + object2 = Object(symbol), + values = [symbol, object1, object2], + expected = lodashStable.map(values, lodashStable.constant(isToNumber ? NaN : 0)); + + object2.valueOf = undefined; + var actual = lodashStable.map(values, func); + + assert.deepStrictEqual(actual, expected); + } + }); + + it('`_.' + methodName + '` should convert empty values to `0` or `NaN`', function() { + var values = falsey.concat(whitespace); + + var expected = lodashStable.map(values, function(value) { + return (isToNumber && value !== whitespace) ? Number(value) : 0; + }); + + var actual = lodashStable.map(values, function(value, index) { + return index ? func(value) : func(); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should coerce objects to numbers', function() { + var values = [ + {}, + [], + [1], + [1, 2], + { 'valueOf': '1.1' }, + { 'valueOf': '1.1', 'toString': lodashStable.constant('2.2') }, + { 'valueOf': lodashStable.constant('1.1'), 'toString': '2.2' }, + { 'valueOf': lodashStable.constant('1.1'), 'toString': lodashStable.constant('2.2') }, + { 'valueOf': lodashStable.constant('-0x1a2b3c') }, + { 'toString': lodashStable.constant('-0x1a2b3c') }, + { 'valueOf': lodashStable.constant('0o12345') }, + { 'toString': lodashStable.constant('0o12345') }, + { 'valueOf': lodashStable.constant('0b101010') }, + { 'toString': lodashStable.constant('0b101010') } + ]; + + var expected = [ + NaN, 0, 1, NaN, + NaN, 2.2, 1.1, 1.1, + NaN, NaN, + 5349, 5349, + 42, 42 + ]; + + if (isToFinite) { + expected = [ + 0, 0, 1, 0, + 0, 2.2, 1.1, 1.1, + 0, 0, + 5349, 5349, + 42, 42 + ]; + } + else if (!isToNumber) { + expected = [ + 0, 0, 1, 0, + 0, 2, 1, 1, + 0, 0, + 5349, 5349, + 42, 42 + ]; + } + var actual = lodashStable.map(values, func); + + assert.deepStrictEqual(actual, expected); + }); + }); +}); diff --git a/test/object-assignments.js b/test/object-assignments.js new file mode 100644 index 000000000..a9cb8b0cb --- /dev/null +++ b/test/object-assignments.js @@ -0,0 +1,180 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, primitives, stubTrue, defineProperty, slice } from './utils.js'; +import has from '../has.js'; + +describe('object assignments', function() { + lodashStable.each(['assign', 'assignIn', 'defaults', 'defaultsDeep', 'merge'], function(methodName) { + var func = _[methodName], + isAssign = methodName == 'assign', + isDefaults = /^defaults/.test(methodName); + + it('`_.' + methodName + '` should coerce primitives to objects', function() { + var expected = lodashStable.map(primitives, function(value) { + var object = Object(value); + object.a = 1; + return object; + }); + + var actual = lodashStable.map(primitives, function(value) { + return func(value, { 'a': 1 }); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should assign own ' + (isAssign ? '' : 'and inherited ') + 'string keyed source properties', function() { + function Foo() { + this.a = 1; + } + Foo.prototype.b = 2; + + var expected = isAssign ? { 'a': 1 } : { 'a': 1, 'b': 2 }; + assert.deepStrictEqual(func({}, new Foo), expected); + }); + + it('`_.' + methodName + '` should not skip a trailing function source', function() { + function fn() {} + fn.b = 2; + + assert.deepStrictEqual(func({}, { 'a': 1 }, fn), { 'a': 1, 'b': 2 }); + }); + + it('`_.' + methodName + '` should not error on nullish sources', function() { + try { + assert.deepStrictEqual(func({ 'a': 1 }, undefined, { 'b': 2 }, null), { 'a': 1, 'b': 2 }); + } catch (e) { + assert.ok(false, e.message); + } + }); + + it('`_.' + methodName + '` should create an object when `object` is nullish', function() { + var source = { 'a': 1 }, + values = [null, undefined], + expected = lodashStable.map(values, stubTrue); + + var actual = lodashStable.map(values, function(value) { + var object = func(value, source); + return object !== source && lodashStable.isEqual(object, source); + }); + + assert.deepStrictEqual(actual, expected); + + actual = lodashStable.map(values, function(value) { + return lodashStable.isEqual(func(value), {}); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should work as an iteratee for methods like `_.reduce`', function() { + var array = [{ 'a': 1 }, { 'b': 2 }, { 'c': 3 }], + expected = { 'a': isDefaults ? 0 : 1, 'b': 2, 'c': 3 }; + + function fn() {} + fn.a = array[0]; + fn.b = array[1]; + fn.c = array[2]; + + assert.deepStrictEqual(lodashStable.reduce(array, func, { 'a': 0 }), expected); + assert.deepStrictEqual(lodashStable.reduce(fn, func, { 'a': 0 }), expected); + }); + + it('`_.' + methodName + '` should not return the existing wrapped value when chaining', function() { + var wrapped = _({ 'a': 1 }), + actual = wrapped[methodName]({ 'b': 2 }); + + assert.notStrictEqual(actual, wrapped); + }); + }); + + lodashStable.each(['assign', 'assignIn', 'merge'], function(methodName) { + var func = _[methodName]; + + it('`_.' + methodName + '` should not treat `object` as `source`', function() { + function Foo() {} + Foo.prototype.a = 1; + + var actual = func(new Foo, { 'b': 2 }); + assert.ok(!has(actual, 'a')); + }); + }); + + lodashStable.each(['assign', 'assignIn', 'assignInWith', 'assignWith', 'defaults', 'defaultsDeep', 'merge', 'mergeWith'], function(methodName) { + var func = _[methodName]; + + it('`_.' + methodName + '` should not assign values that are the same as their destinations', function() { + lodashStable.each(['a', ['a'], { 'a': 1 }, NaN], function(value) { + var object = {}, + pass = true; + + defineProperty(object, 'a', { + 'configurable': true, + 'enumerable': true, + 'get': lodashStable.constant(value), + 'set': function() { pass = false; } + }); + + func(object, { 'a': value }); + assert.ok(pass); + }); + }); + }); + + lodashStable.each(['assignWith', 'assignInWith', 'mergeWith'], function(methodName) { + var func = _[methodName], + isMergeWith = methodName == 'mergeWith'; + + it('`_.' + methodName + '` should provide correct `customizer` arguments', function() { + var args, + object = { 'a': 1 }, + source = { 'a': 2 }, + expected = lodashStable.map([1, 2, 'a', object, source], lodashStable.cloneDeep); + + func(object, source, function() { + args || (args = lodashStable.map(slice.call(arguments, 0, 5), lodashStable.cloneDeep)); + }); + + assert.deepStrictEqual(args, expected, 'primitive values'); + + var argsList = [], + objectValue = [1, 2], + sourceValue = { 'b': 2 }; + + object = { 'a': objectValue }; + source = { 'a': sourceValue }; + expected = [lodashStable.map([objectValue, sourceValue, 'a', object, source], lodashStable.cloneDeep)]; + + if (isMergeWith) { + expected.push(lodashStable.map([undefined, 2, 'b', objectValue, sourceValue], lodashStable.cloneDeep)); + } + func(object, source, function() { + argsList.push(lodashStable.map(slice.call(arguments, 0, 5), lodashStable.cloneDeep)); + }); + + assert.deepStrictEqual(argsList, expected, 'object values'); + + args = undefined; + object = { 'a': 1 }; + source = { 'b': 2 }; + expected = lodashStable.map([undefined, 2, 'b', object, source], lodashStable.cloneDeep); + + func(object, source, function() { + args || (args = lodashStable.map(slice.call(arguments, 0, 5), lodashStable.cloneDeep)); + }); + + assert.deepStrictEqual(args, expected, 'undefined properties'); + }); + + it('`_.' + methodName + '` should not treat the second argument as a `customizer` callback', function() { + function callback() {} + callback.b = 2; + + var actual = func({ 'a': 1 }, callback); + assert.deepStrictEqual(actual, { 'a': 1, 'b': 2 }); + + actual = func({ 'a': 1 }, callback, { 'c': 3 }); + assert.deepStrictEqual(actual, { 'a': 1, 'b': 2, 'c': 3 }); + }); + }); +}); diff --git a/test/omit-methods.js b/test/omit-methods.js new file mode 100644 index 000000000..750cb9f34 --- /dev/null +++ b/test/omit-methods.js @@ -0,0 +1,114 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, symbol, defineProperty } from './utils.js'; + +describe('omit methods', function() { + lodashStable.each(['omit', 'omitBy'], function(methodName) { + var expected = { 'b': 2, 'd': 4 }, + func = _[methodName], + object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }, + resolve = lodashStable.nthArg(1); + + if (methodName == 'omitBy') { + resolve = function(object, props) { + props = lodashStable.castArray(props); + return function(value) { + return lodashStable.some(props, function(key) { + key = lodashStable.isSymbol(key) ? key : lodashStable.toString(key); + return object[key] === value; + }); + }; + }; + } + it('`_.' + methodName + '` should create an object with omitted string keyed properties', function() { + assert.deepStrictEqual(func(object, resolve(object, 'a')), { 'b': 2, 'c': 3, 'd': 4 }); + assert.deepStrictEqual(func(object, resolve(object, ['a', 'c'])), expected); + }); + + it('`_.' + methodName + '` should include inherited string keyed properties', function() { + function Foo() {} + Foo.prototype = object; + + assert.deepStrictEqual(func(new Foo, resolve(object, ['a', 'c'])), expected); + }); + + it('`_.' + methodName + '` should preserve the sign of `0`', function() { + var object = { '-0': 'a', '0': 'b' }, + props = [-0, Object(-0), 0, Object(0)], + expected = [{ '0': 'b' }, { '0': 'b' }, { '-0': 'a' }, { '-0': 'a' }]; + + var actual = lodashStable.map(props, function(key) { + return func(object, resolve(object, key)); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should include symbols', function() { + function Foo() { + this.a = 0; + this[symbol] = 1; + } + + if (Symbol) { + var symbol2 = Symbol('b'); + Foo.prototype[symbol2] = 2; + + var symbol3 = Symbol('c'); + defineProperty(Foo.prototype, symbol3, { + 'configurable': true, + 'enumerable': false, + 'writable': true, + 'value': 3 + }); + + var foo = new Foo, + actual = func(foo, resolve(foo, 'a')); + + assert.strictEqual(actual[symbol], 1); + assert.strictEqual(actual[symbol2], 2); + assert.ok(!(symbol3 in actual)); + } + }); + + it('`_.' + methodName + '` should create an object with omitted symbols', function() { + function Foo() { + this.a = 0; + this[symbol] = 1; + } + + if (Symbol) { + var symbol2 = Symbol('b'); + Foo.prototype[symbol2] = 2; + + var symbol3 = Symbol('c'); + defineProperty(Foo.prototype, symbol3, { + 'configurable': true, + 'enumerable': false, + 'writable': true, + 'value': 3 + }); + + var foo = new Foo, + actual = func(foo, resolve(foo, symbol)); + + assert.strictEqual(actual.a, 0); + assert.ok(!(symbol in actual)); + assert.strictEqual(actual[symbol2], 2); + assert.ok(!(symbol3 in actual)); + + actual = func(foo, resolve(foo, symbol2)); + + assert.strictEqual(actual.a, 0); + assert.strictEqual(actual[symbol], 1); + assert.ok(!(symbol2 in actual)); + assert.ok(!(symbol3 in actual)); + } + }); + + it('`_.' + methodName + '` should work with an array `object`', function() { + var array = [1, 2, 3]; + assert.deepStrictEqual(func(array, resolve(array, ['0', '2'])), { '1': 2 }); + }); + }); +}); diff --git a/test/omit.js b/test/omit.js new file mode 100644 index 000000000..7bde8dc8d --- /dev/null +++ b/test/omit.js @@ -0,0 +1,69 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { args, toArgs, objectProto, stringProto } from './utils.js'; +import omit from '../omit.js'; + +describe('omit', function() { + var args = toArgs(['a', 'c']), + object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }, + nested = { 'a': 1, 'b': { 'c': 2, 'd': 3 } }; + + it('should flatten `paths`', function() { + assert.deepStrictEqual(omit(object, 'a', 'c'), { 'b': 2, 'd': 4 }); + assert.deepStrictEqual(omit(object, ['a', 'd'], 'c'), { 'b': 2 }); + }); + + it('should support deep paths', function() { + assert.deepStrictEqual(omit(nested, 'b.c'), { 'a': 1, 'b': { 'd': 3} }); + }); + + it('should support path arrays', function() { + var object = { 'a.b': 1, 'a': { 'b': 2 } }, + actual = omit(object, [['a.b']]); + + assert.deepStrictEqual(actual, { 'a': { 'b': 2 } }); + }); + + it('should omit a key over a path', function() { + var object = { 'a.b': 1, 'a': { 'b': 2 } }; + + lodashStable.each(['a.b', ['a.b']], function(path) { + assert.deepStrictEqual(omit(object, path), { 'a': { 'b': 2 } }); + }); + }); + + it('should coerce `paths` to strings', function() { + assert.deepStrictEqual(omit({ '0': 'a' }, 0), {}); + }); + + it('should return an empty object when `object` is nullish', function() { + lodashStable.each([null, undefined], function(value) { + objectProto.a = 1; + var actual = omit(value, 'valueOf'); + delete objectProto.a; + assert.deepStrictEqual(actual, {}); + }); + }); + + it('should work with a primitive `object`', function() { + stringProto.a = 1; + stringProto.b = 2; + + assert.deepStrictEqual(omit('', 'b'), { 'a': 1 }); + + delete stringProto.a; + delete stringProto.b; + }); + + it('should work with `arguments` object `paths`', function() { + assert.deepStrictEqual(omit(object, args), { 'b': 2, 'd': 4 }); + }); + + it('should not mutate `object`', function() { + lodashStable.each(['a', ['a'], 'a.b', ['a.b']], function(path) { + var object = { 'a': { 'b': 2 } }; + omit(object, path); + assert.deepStrictEqual(object, { 'a': { 'b': 2 } }); + }); + }); +}); diff --git a/test/omitBy.js b/test/omitBy.js new file mode 100644 index 000000000..28c6a9aff --- /dev/null +++ b/test/omitBy.js @@ -0,0 +1,14 @@ +import assert from 'assert'; +import omitBy from '../omitBy.js'; + +describe('omitBy', function() { + it('should work with a predicate argument', function() { + var object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }; + + var actual = omitBy(object, function(n) { + return n != 2 && n != 4; + }); + + assert.deepStrictEqual(actual, { 'b': 2, 'd': 4 }); + }); +}); diff --git a/test/once.js b/test/once.js new file mode 100644 index 000000000..7279125e8 --- /dev/null +++ b/test/once.js @@ -0,0 +1,36 @@ +import assert from 'assert'; +import { _ } from './utils.js'; + +describe('once', function() { + it('should invoke `func` once', function() { + var count = 0, + once = _.once(function() { return ++count; }); + + once(); + assert.strictEqual(once(), 1); + assert.strictEqual(count, 1); + }); + + it('should ignore recursive calls', function() { + var count = 0; + + var once = _.once(function() { + once(); + return ++count; + }); + + assert.strictEqual(once(), 1); + assert.strictEqual(count, 1); + }); + + it('should not throw more than once', function() { + var once = _.once(function() { + throw new Error; + }); + + assert.throws(once); + + once(); + assert.ok(true); + }); +}); diff --git a/test/orderBy.js b/test/orderBy.js new file mode 100644 index 000000000..51f591812 --- /dev/null +++ b/test/orderBy.js @@ -0,0 +1,43 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey } from './utils.js'; +import orderBy from '../orderBy.js'; + +describe('orderBy', function() { + var objects = [ + { 'a': 'x', 'b': 3 }, + { 'a': 'y', 'b': 4 }, + { 'a': 'x', 'b': 1 }, + { 'a': 'y', 'b': 2 } + ]; + + it('should sort by a single property by a specified order', function() { + var actual = orderBy(objects, 'a', 'desc'); + assert.deepStrictEqual(actual, [objects[1], objects[3], objects[0], objects[2]]); + }); + + it('should sort by multiple properties by specified orders', function() { + var actual = orderBy(objects, ['a', 'b'], ['desc', 'asc']); + assert.deepStrictEqual(actual, [objects[3], objects[1], objects[2], objects[0]]); + }); + + it('should sort by a property in ascending order when its order is not specified', function() { + var expected = [objects[2], objects[0], objects[3], objects[1]], + actual = orderBy(objects, ['a', 'b']); + + assert.deepStrictEqual(actual, expected); + + expected = lodashStable.map(falsey, lodashStable.constant([objects[3], objects[1], objects[2], objects[0]])); + + actual = lodashStable.map(falsey, function(order, index) { + return orderBy(objects, ['a', 'b'], index ? ['desc', order] : ['desc']); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with `orders` specified as string objects', function() { + var actual = orderBy(objects, ['a'], [Object('desc')]); + assert.deepStrictEqual(actual, [objects[1], objects[3], objects[0], objects[2]]); + }); +}); diff --git a/test/over.js b/test/over.js new file mode 100644 index 000000000..93ce95186 --- /dev/null +++ b/test/over.js @@ -0,0 +1,58 @@ +import assert from 'assert'; +import { _, slice } from './utils.js'; + +describe('over', function() { + it('should create a function that invokes `iteratees`', function() { + var over = _.over(Math.max, Math.min); + assert.deepStrictEqual(over(1, 2, 3, 4), [4, 1]); + }); + + it('should use `_.identity` when a predicate is nullish', function() { + var over = _.over(undefined, null); + assert.deepStrictEqual(over('a', 'b', 'c'), ['a', 'a']); + }); + + it('should work with `_.property` shorthands', function() { + var over = _.over('b', 'a'); + assert.deepStrictEqual(over({ 'a': 1, 'b': 2 }), [2, 1]); + }); + + it('should work with `_.matches` shorthands', function() { + var over = _.over({ 'b': 1 }, { 'a': 1 }); + assert.deepStrictEqual(over({ 'a': 1, 'b': 2 }), [false, true]); + }); + + it('should work with `_.matchesProperty` shorthands', function() { + var over = _.over([['b', 2], ['a', 2]]); + + assert.deepStrictEqual(over({ 'a': 1, 'b': 2 }), [true, false]); + assert.deepStrictEqual(over({ 'a': 2, 'b': 1 }), [false, true]); + }); + + it('should differentiate between `_.property` and `_.matchesProperty` shorthands', function() { + var over = _.over(['a', 1]); + + assert.deepStrictEqual(over({ 'a': 1, '1': 2 }), [1, 2]); + assert.deepStrictEqual(over({ 'a': 2, '1': 1 }), [2, 1]); + + over = _.over([['a', 1]]); + + assert.deepStrictEqual(over({ 'a': 1 }), [true]); + assert.deepStrictEqual(over({ 'a': 2 }), [false]); + }); + + it('should provide arguments to predicates', function() { + var over = _.over(function() { + return slice.call(arguments); + }); + + assert.deepStrictEqual(over('a', 'b', 'c'), [['a', 'b', 'c']]); + }); + + it('should use `this` binding of function for `iteratees`', function() { + var over = _.over(function() { return this.b; }, function() { return this.a; }), + object = { 'over': over, 'a': 1, 'b': 2 }; + + assert.deepStrictEqual(object.over(), [2, 1]); + }); +}); diff --git a/test/overArgs.js b/test/overArgs.js new file mode 100644 index 000000000..f6e20db59 --- /dev/null +++ b/test/overArgs.js @@ -0,0 +1,82 @@ +import assert from 'assert'; +import { slice, doubled, square, identity, noop } from './utils.js'; +import overArgs from '../overArgs.js'; + +describe('overArgs', function() { + function fn() { + return slice.call(arguments); + } + + it('should transform each argument', function() { + var over = overArgs(fn, doubled, square); + assert.deepStrictEqual(over(5, 10), [10, 100]); + }); + + it('should use `_.identity` when a predicate is nullish', function() { + var over = overArgs(fn, undefined, null); + assert.deepStrictEqual(over('a', 'b'), ['a', 'b']); + }); + + it('should work with `_.property` shorthands', function() { + var over = overArgs(fn, 'b', 'a'); + assert.deepStrictEqual(over({ 'b': 2 }, { 'a': 1 }), [2, 1]); + }); + + it('should work with `_.matches` shorthands', function() { + var over = overArgs(fn, { 'b': 1 }, { 'a': 1 }); + assert.deepStrictEqual(over({ 'b': 2 }, { 'a': 1 }), [false, true]); + }); + + it('should work with `_.matchesProperty` shorthands', function() { + var over = overArgs(fn, [['b', 1], ['a', 1]]); + assert.deepStrictEqual(over({ 'b': 2 }, { 'a': 1 }), [false, true]); + }); + + it('should differentiate between `_.property` and `_.matchesProperty` shorthands', function() { + var over = overArgs(fn, ['a', 1]); + assert.deepStrictEqual(over({ 'a': 1 }, { '1': 2 }), [1, 2]); + + over = overArgs(fn, [['a', 1]]); + assert.deepStrictEqual(over({ 'a': 1 }), [true]); + }); + + it('should flatten `transforms`', function() { + var over = overArgs(fn, [doubled, square], String); + assert.deepStrictEqual(over(5, 10, 15), [10, 100, '15']); + }); + + it('should not transform any argument greater than the number of transforms', function() { + var over = overArgs(fn, doubled, square); + assert.deepStrictEqual(over(5, 10, 18), [10, 100, 18]); + }); + + it('should not transform any arguments if no transforms are given', function() { + var over = overArgs(fn); + assert.deepStrictEqual(over(5, 10, 18), [5, 10, 18]); + }); + + it('should not pass `undefined` if there are more transforms than arguments', function() { + var over = overArgs(fn, doubled, identity); + assert.deepStrictEqual(over(5), [10]); + }); + + it('should provide the correct argument to each transform', function() { + var argsList = [], + transform = function() { argsList.push(slice.call(arguments)); }, + over = overArgs(noop, transform, transform, transform); + + over('a', 'b'); + assert.deepStrictEqual(argsList, [['a'], ['b']]); + }); + + it('should use `this` binding of function for `transforms`', function() { + var over = overArgs(function(x) { + return this[x]; + }, function(x) { + return this === x; + }); + + var object = { 'over': over, 'true': 1 }; + assert.strictEqual(object.over(object), 1); + }); +}); diff --git a/test/overEvery.js b/test/overEvery.js new file mode 100644 index 000000000..7ce135cf9 --- /dev/null +++ b/test/overEvery.js @@ -0,0 +1,87 @@ +import assert from 'assert'; +import { stubTrue, stubOne, stubA, stubFalse, slice } from './utils.js'; +import overEvery from '../overEvery.js'; + +describe('overEvery', function() { + it('should create a function that returns `true` if all predicates return truthy', function() { + var over = overEvery(stubTrue, stubOne, stubA); + assert.strictEqual(over(), true); + }); + + it('should return `false` as soon as a predicate returns falsey', function() { + var count = 0, + countFalse = function() { count++; return false; }, + countTrue = function() { count++; return true; }, + over = overEvery(countTrue, countFalse, countTrue); + + assert.strictEqual(over(), false); + assert.strictEqual(count, 2); + }); + + it('should use `_.identity` when a predicate is nullish', function() { + var over = overEvery(undefined, null); + + assert.strictEqual(over(true), true); + assert.strictEqual(over(false), false); + }); + + it('should work with `_.property` shorthands', function() { + var over = overEvery('b', 'a'); + + assert.strictEqual(over({ 'a': 1, 'b': 1 }), true); + assert.strictEqual(over({ 'a': 0, 'b': 1 }), false); + }); + + it('should work with `_.matches` shorthands', function() { + var over = overEvery({ 'b': 2 }, { 'a': 1 }); + + assert.strictEqual(over({ 'a': 1, 'b': 2 }), true); + assert.strictEqual(over({ 'a': 0, 'b': 2 }), false); + }); + + it('should work with `_.matchesProperty` shorthands', function() { + var over = overEvery([['b', 2], ['a', 1]]); + + assert.strictEqual(over({ 'a': 1, 'b': 2 }), true); + assert.strictEqual(over({ 'a': 0, 'b': 2 }), false); + }); + + it('should differentiate between `_.property` and `_.matchesProperty` shorthands', function() { + var over = overEvery(['a', 1]); + + assert.strictEqual(over({ 'a': 1, '1': 1 }), true); + assert.strictEqual(over({ 'a': 1, '1': 0 }), false); + assert.strictEqual(over({ 'a': 0, '1': 1 }), false); + + over = overEvery([['a', 1]]); + + assert.strictEqual(over({ 'a': 1 }), true); + assert.strictEqual(over({ 'a': 2 }), false); + }); + + it('should flatten `predicates`', function() { + var over = overEvery(stubTrue, [stubFalse]); + assert.strictEqual(over(), false); + }); + + it('should provide arguments to predicates', function() { + var args; + + var over = overEvery(function() { + args = slice.call(arguments); + }); + + over('a', 'b', 'c'); + assert.deepStrictEqual(args, ['a', 'b', 'c']); + }); + + it('should use `this` binding of function for `predicates`', function() { + var over = overEvery(function() { return this.b; }, function() { return this.a; }), + object = { 'over': over, 'a': 1, 'b': 2 }; + + assert.strictEqual(object.over(), true); + + object.a = 0; + assert.strictEqual(object.over(), false); + }); +}); diff --git a/test/overSome.js b/test/overSome.js new file mode 100644 index 000000000..07ecaa22c --- /dev/null +++ b/test/overSome.js @@ -0,0 +1,98 @@ +import assert from 'assert'; +import { stubFalse, stubOne, stubString, stubNull, stubA, stubZero, stubTrue, slice } from './utils.js'; +import overSome from '../overSome.js'; + +describe('overSome', function() { + it('should create a function that returns `true` if any predicates return truthy', function() { + var over = overSome(stubFalse, stubOne, stubString); + assert.strictEqual(over(), true); + + over = overSome(stubNull, stubA, stubZero); + assert.strictEqual(over(), true); + }); + + it('should return `true` as soon as `predicate` returns truthy', function() { + var count = 0, + countFalse = function() { count++; return false; }, + countTrue = function() { count++; return true; }, + over = overSome(countFalse, countTrue, countFalse); + + assert.strictEqual(over(), true); + assert.strictEqual(count, 2); + }); + + it('should return `false` if all predicates return falsey', function() { + var over = overSome(stubFalse, stubFalse, stubFalse); + assert.strictEqual(over(), false); + + over = overSome(stubNull, stubZero, stubString); + assert.strictEqual(over(), false); + }); + + it('should use `_.identity` when a predicate is nullish', function() { + var over = overSome(undefined, null); + + assert.strictEqual(over(true), true); + assert.strictEqual(over(false), false); + }); + + it('should work with `_.property` shorthands', function() { + var over = overSome('b', 'a'); + + assert.strictEqual(over({ 'a': 1, 'b': 0 }), true); + assert.strictEqual(over({ 'a': 0, 'b': 0 }), false); + }); + + it('should work with `_.matches` shorthands', function() { + var over = overSome({ 'b': 2 }, { 'a': 1 }); + + assert.strictEqual(over({ 'a': 0, 'b': 2 }), true); + assert.strictEqual(over({ 'a': 0, 'b': 0 }), false); + }); + + it('should work with `_.matchesProperty` shorthands', function() { + var over = overSome([['b', 2], ['a', 1]]); + + assert.strictEqual(over({ 'a': 0, 'b': 2 }), true); + assert.strictEqual(over({ 'a': 0, 'b': 0 }), false); + }); + + it('should differentiate between `_.property` and `_.matchesProperty` shorthands', function() { + var over = overSome(['a', 1]); + + assert.strictEqual(over({ 'a': 0, '1': 0 }), false); + assert.strictEqual(over({ 'a': 1, '1': 0 }), true); + assert.strictEqual(over({ 'a': 0, '1': 1 }), true); + + over = overSome([['a', 1]]); + + assert.strictEqual(over({ 'a': 1 }), true); + assert.strictEqual(over({ 'a': 2 }), false); + }); + + it('should flatten `predicates`', function() { + var over = overSome(stubFalse, [stubTrue]); + assert.strictEqual(over(), true); + }); + + it('should provide arguments to predicates', function() { + var args; + + var over = overSome(function() { + args = slice.call(arguments); + }); + + over('a', 'b', 'c'); + assert.deepStrictEqual(args, ['a', 'b', 'c']); + }); + + it('should use `this` binding of function for `predicates`', function() { + var over = overSome(function() { return this.b; }, function() { return this.a; }), + object = { 'over': over, 'a': 1, 'b': 2 }; + + assert.strictEqual(object.over(), true); + + object.a = object.b = 0; + assert.strictEqual(object.over(), false); + }); +}); diff --git a/test/pad-methods.js b/test/pad-methods.js new file mode 100644 index 000000000..8d2e61a04 --- /dev/null +++ b/test/pad-methods.js @@ -0,0 +1,51 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _ } from './utils.js'; +import pad from '../pad.js'; + +describe('pad methods', function() { + lodashStable.each(['pad', 'padStart', 'padEnd'], function(methodName) { + var func = _[methodName], + isPad = methodName == 'pad', + isStart = methodName == 'padStart', + string = 'abc'; + + it('`_.' + methodName + '` should not pad if string is >= `length`', function() { + assert.strictEqual(func(string, 2), string); + assert.strictEqual(func(string, 3), string); + }); + + it('`_.' + methodName + '` should treat negative `length` as `0`', function() { + lodashStable.each([0, -2], function(length) { + assert.strictEqual(func(string, length), string); + }); + }); + + it('`_.' + methodName + '` should coerce `length` to a number', function() { + lodashStable.each(['', '4'], function(length) { + var actual = length ? (isStart ? ' abc' : 'abc ') : string; + assert.strictEqual(func(string, length), actual); + }); + }); + + it('`_.' + methodName + '` should treat nullish values as empty strings', function() { + lodashStable.each([undefined, '_-'], function(chars) { + var expected = chars ? (isPad ? '__' : chars) : ' '; + assert.strictEqual(func(null, 2, chars), expected); + assert.strictEqual(func(undefined, 2, chars), expected); + assert.strictEqual(func('', 2, chars), expected); + }); + }); + + it('`_.' + methodName + '` should return `string` when `chars` coerces to an empty string', function() { + var values = ['', Object('')], + expected = lodashStable.map(values, lodashStable.constant(string)); + + var actual = lodashStable.map(values, function(value) { + return pad(string, 6, value); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); +}); diff --git a/test/pad.js b/test/pad.js new file mode 100644 index 000000000..8a62fe036 --- /dev/null +++ b/test/pad.js @@ -0,0 +1,35 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { stubTrue } from './utils.js'; +import pad from '../pad.js'; + +describe('pad', function() { + var string = 'abc'; + + it('should pad a string to a given length', function() { + var values = [, undefined], + expected = lodashStable.map(values, lodashStable.constant(' abc ')); + + var actual = lodashStable.map(values, function(value, index) { + return index ? pad(string, 6, value) : pad(string, 6); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should truncate pad characters to fit the pad length', function() { + assert.strictEqual(pad(string, 8), ' abc '); + assert.strictEqual(pad(string, 8, '_-'), '_-abc_-_'); + }); + + it('should coerce `string` to a string', function() { + var values = [Object(string), { 'toString': lodashStable.constant(string) }], + expected = lodashStable.map(values, stubTrue); + + var actual = lodashStable.map(values, function(value) { + return pad(value, 6) === ' abc '; + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/padEnd.js b/test/padEnd.js new file mode 100644 index 000000000..6edb5232c --- /dev/null +++ b/test/padEnd.js @@ -0,0 +1,34 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { stubTrue } from './utils.js'; +import padEnd from '../padEnd.js'; + +describe('padEnd', function() { + var string = 'abc'; + + it('should pad a string to a given length', function() { + var values = [, undefined], + expected = lodashStable.map(values, lodashStable.constant('abc ')); + + var actual = lodashStable.map(values, function(value, index) { + return index ? padEnd(string, 6, value) : padEnd(string, 6); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should truncate pad characters to fit the pad length', function() { + assert.strictEqual(padEnd(string, 6, '_-'), 'abc_-_'); + }); + + it('should coerce `string` to a string', function() { + var values = [Object(string), { 'toString': lodashStable.constant(string) }], + expected = lodashStable.map(values, stubTrue); + + var actual = lodashStable.map(values, function(value) { + return padEnd(value, 6) === 'abc '; + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/padStart.js b/test/padStart.js new file mode 100644 index 000000000..9ec998858 --- /dev/null +++ b/test/padStart.js @@ -0,0 +1,34 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { stubTrue } from './utils.js'; +import padStart from '../padStart.js'; + +describe('padStart', function() { + var string = 'abc'; + + it('should pad a string to a given length', function() { + var values = [, undefined], + expected = lodashStable.map(values, lodashStable.constant(' abc')); + + var actual = lodashStable.map(values, function(value, index) { + return index ? padStart(string, 6, value) : padStart(string, 6); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should truncate pad characters to fit the pad length', function() { + assert.strictEqual(padStart(string, 6, '_-'), '_-_abc'); + }); + + it('should coerce `string` to a string', function() { + var values = [Object(string), { 'toString': lodashStable.constant(string) }], + expected = lodashStable.map(values, stubTrue); + + var actual = lodashStable.map(values, function(value) { + return padStart(value, 6) === ' abc'; + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/parseInt.js b/test/parseInt.js new file mode 100644 index 000000000..d068cbfe4 --- /dev/null +++ b/test/parseInt.js @@ -0,0 +1,81 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { lodashBizarro, whitespace, stubZero } from './utils.js'; +import parseInt from '../parseInt.js'; + +describe('parseInt', function() { + it('should accept a `radix`', function() { + var expected = lodashStable.range(2, 37); + + var actual = lodashStable.map(expected, function(radix) { + return parseInt('10', radix); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should use a radix of `10`, for non-hexadecimals, if `radix` is `undefined` or `0`', function() { + assert.strictEqual(parseInt('10'), 10); + assert.strictEqual(parseInt('10', 0), 10); + assert.strictEqual(parseInt('10', 10), 10); + assert.strictEqual(parseInt('10', undefined), 10); + }); + + it('should use a radix of `16`, for hexadecimals, if `radix` is `undefined` or `0`', function() { + lodashStable.each(['0x20', '0X20'], function(string) { + assert.strictEqual(parseInt(string), 32); + assert.strictEqual(parseInt(string, 0), 32); + assert.strictEqual(parseInt(string, 16), 32); + assert.strictEqual(parseInt(string, undefined), 32); + }); + }); + + it('should use a radix of `10` for string with leading zeros', function() { + assert.strictEqual(parseInt('08'), 8); + assert.strictEqual(parseInt('08', 10), 8); + }); + + it('should parse strings with leading whitespace', function() { + var expected = [8, 8, 10, 10, 32, 32, 32, 32]; + + lodashStable.times(2, function(index) { + var actual = [], + func = (index ? (lodashBizarro || {}) : _).parseInt; + + if (func) { + lodashStable.times(2, function(otherIndex) { + var string = otherIndex ? '10' : '08'; + actual.push( + func(whitespace + string, 10), + func(whitespace + string) + ); + }); + + lodashStable.each(['0x20', '0X20'], function(string) { + actual.push( + func(whitespace + string), + func(whitespace + string, 16) + ); + }); + + assert.deepStrictEqual(actual, expected); + } + }); + }); + + it('should coerce `radix` to a number', function() { + var object = { 'valueOf': stubZero }; + assert.strictEqual(parseInt('08', object), 8); + assert.strictEqual(parseInt('0x20', object), 32); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var strings = lodashStable.map(['6', '08', '10'], Object), + actual = lodashStable.map(strings, parseInt); + + assert.deepStrictEqual(actual, [6, 8, 10]); + + actual = lodashStable.map('123', parseInt); + assert.deepStrictEqual(actual, [1, 2, 3]); + }); +}); diff --git a/test/partial-methods.js b/test/partial-methods.js new file mode 100644 index 000000000..4509c9916 --- /dev/null +++ b/test/partial-methods.js @@ -0,0 +1,113 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, identity, slice } from './utils.js'; +import placeholder from '../placeholder.js'; +import curry from '../curry.js'; + +describe('partial methods', function() { + lodashStable.each(['partial', 'partialRight'], function(methodName) { + var func = _[methodName], + isPartial = methodName == 'partial', + ph = func.placeholder; + + it('`_.' + methodName + '` partially applies arguments', function() { + var par = func(identity, 'a'); + assert.strictEqual(par(), 'a'); + }); + + it('`_.' + methodName + '` creates a function that can be invoked with additional arguments', function() { + var fn = function(a, b) { return [a, b]; }, + par = func(fn, 'a'), + expected = isPartial ? ['a', 'b'] : ['b', 'a']; + + assert.deepStrictEqual(par('b'), expected); + }); + + it('`_.' + methodName + '` works when there are no partially applied arguments and the created function is invoked without additional arguments', function() { + var fn = function() { return arguments.length; }, + par = func(fn); + + assert.strictEqual(par(), 0); + }); + + it('`_.' + methodName + '` works when there are no partially applied arguments and the created function is invoked with additional arguments', function() { + var par = func(identity); + assert.strictEqual(par('a'), 'a'); + }); + + it('`_.' + methodName + '` should support placeholders', function() { + var fn = function() { return slice.call(arguments); }, + par = func(fn, ph, 'b', ph); + + assert.deepStrictEqual(par('a', 'c'), ['a', 'b', 'c']); + assert.deepStrictEqual(par('a'), ['a', 'b', undefined]); + assert.deepStrictEqual(par(), [undefined, 'b', undefined]); + + if (isPartial) { + assert.deepStrictEqual(par('a', 'c', 'd'), ['a', 'b', 'c', 'd']); + } else { + par = func(fn, ph, 'c', ph); + assert.deepStrictEqual(par('a', 'b', 'd'), ['a', 'b', 'c', 'd']); + } + }); + + it('`_.' + methodName + '` should use `_.placeholder` when set', function() { + var _ph = placeholder = {}, + fn = function() { return slice.call(arguments); }, + par = func(fn, _ph, 'b', ph), + expected = isPartial ? ['a', 'b', ph, 'c'] : ['a', 'c', 'b', ph]; + + assert.deepEqual(par('a', 'c'), expected); + delete placeholder; + }); + + it('`_.' + methodName + '` creates a function with a `length` of `0`', function() { + var fn = function(a, b, c) {}, + par = func(fn, 'a'); + + assert.strictEqual(par.length, 0); + }); + + it('`_.' + methodName + '` should ensure `new par` is an instance of `func`', function() { + function Foo(value) { + return value && object; + } + + var object = {}, + par = func(Foo); + + assert.ok(new par instanceof Foo); + assert.strictEqual(new par(true), object); + }); + + it('`_.' + methodName + '` should clone metadata for created functions', function() { + function greet(greeting, name) { + return greeting + ' ' + name; + } + + var par1 = func(greet, 'hi'), + par2 = func(par1, 'barney'), + par3 = func(par1, 'pebbles'); + + assert.strictEqual(par1('fred'), isPartial ? 'hi fred' : 'fred hi'); + assert.strictEqual(par2(), isPartial ? 'hi barney' : 'barney hi'); + assert.strictEqual(par3(), isPartial ? 'hi pebbles' : 'pebbles hi'); + }); + + it('`_.' + methodName + '` should work with curried functions', function() { + var fn = function(a, b, c) { return a + b + c; }, + curried = curry(func(fn, 1), 2); + + assert.strictEqual(curried(2, 3), 6); + assert.strictEqual(curried(2)(3), 6); + }); + + it('should work with placeholders and curried functions', function() { + var fn = function() { return slice.call(arguments); }, + curried = curry(fn), + par = func(curried, ph, 'b', ph, 'd'); + + assert.deepStrictEqual(par('a', 'c'), ['a', 'b', 'c', 'd']); + }); + }); +}); diff --git a/test/partialRight.js b/test/partialRight.js new file mode 100644 index 000000000..87d48e4cc --- /dev/null +++ b/test/partialRight.js @@ -0,0 +1,18 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import partialRight from '../partialRight.js'; +import mergeWith from '../mergeWith.js'; + +describe('partialRight', function() { + it('should work as a deep `_.defaults`', function() { + var object = { 'a': { 'b': 2 } }, + source = { 'a': { 'b': 3, 'c': 3 } }, + expected = { 'a': { 'b': 2, 'c': 3 } }; + + var defaultsDeep = partialRight(mergeWith, function deep(value, other) { + return lodashStable.isObject(value) ? mergeWith(value, other, deep) : value; + }); + + assert.deepStrictEqual(defaultsDeep(object, source), expected); + }); +}); diff --git a/test/partition.js b/test/partition.js new file mode 100644 index 000000000..1767548a1 --- /dev/null +++ b/test/partition.js @@ -0,0 +1,48 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { identity, stubTrue, stubFalse } from './utils.js'; +import partition from '../partition.js'; + +describe('partition', function() { + var array = [1, 0, 1]; + + it('should split elements into two groups by `predicate`', function() { + assert.deepStrictEqual(partition([], identity), [[], []]); + assert.deepStrictEqual(partition(array, stubTrue), [array, []]); + assert.deepStrictEqual(partition(array, stubFalse), [[], array]); + }); + + it('should use `_.identity` when `predicate` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, lodashStable.constant([[1, 1], [0]])); + + var actual = lodashStable.map(values, function(value, index) { + return index ? partition(array, value) : partition(array); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with `_.property` shorthands', function() { + var objects = [{ 'a': 1 }, { 'a': 1 }, { 'b': 2 }], + actual = partition(objects, 'a'); + + assert.deepStrictEqual(actual, [objects.slice(0, 2), objects.slice(2)]); + }); + + it('should work with a number for `predicate`', function() { + var array = [ + [1, 0], + [0, 1], + [1, 0] + ]; + + assert.deepStrictEqual(partition(array, 0), [[array[0], array[2]], [array[1]]]); + assert.deepStrictEqual(partition(array, 1), [[array[1]], [array[0], array[2]]]); + }); + + it('should work with an object for `collection`', function() { + var actual = partition({ 'a': 1.1, 'b': 0.2, 'c': 1.3 }, Math.floor); + assert.deepStrictEqual(actual, [[1.1, 1.3], [0.2]]); + }); +}); diff --git a/test/pick-methods.js b/test/pick-methods.js new file mode 100644 index 000000000..50eb1188d --- /dev/null +++ b/test/pick-methods.js @@ -0,0 +1,85 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, symbol, defineProperty } from './utils.js'; + +describe('pick methods', function() { + lodashStable.each(['pick', 'pickBy'], function(methodName) { + var expected = { 'a': 1, 'c': 3 }, + func = _[methodName], + isPick = methodName == 'pick', + object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }, + resolve = lodashStable.nthArg(1); + + if (methodName == 'pickBy') { + resolve = function(object, props) { + props = lodashStable.castArray(props); + return function(value) { + return lodashStable.some(props, function(key) { + key = lodashStable.isSymbol(key) ? key : lodashStable.toString(key); + return object[key] === value; + }); + }; + }; + } + it('`_.' + methodName + '` should create an object of picked string keyed properties', function() { + assert.deepStrictEqual(func(object, resolve(object, 'a')), { 'a': 1 }); + assert.deepStrictEqual(func(object, resolve(object, ['a', 'c'])), expected); + }); + + it('`_.' + methodName + '` should pick inherited string keyed properties', function() { + function Foo() {} + Foo.prototype = object; + + var foo = new Foo; + assert.deepStrictEqual(func(foo, resolve(foo, ['a', 'c'])), expected); + }); + + it('`_.' + methodName + '` should preserve the sign of `0`', function() { + var object = { '-0': 'a', '0': 'b' }, + props = [-0, Object(-0), 0, Object(0)], + expected = [{ '-0': 'a' }, { '-0': 'a' }, { '0': 'b' }, { '0': 'b' }]; + + var actual = lodashStable.map(props, function(key) { + return func(object, resolve(object, key)); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should pick symbols', function() { + function Foo() { + this[symbol] = 1; + } + + if (Symbol) { + var symbol2 = Symbol('b'); + Foo.prototype[symbol2] = 2; + + var symbol3 = Symbol('c'); + defineProperty(Foo.prototype, symbol3, { + 'configurable': true, + 'enumerable': false, + 'writable': true, + 'value': 3 + }); + + var foo = new Foo, + actual = func(foo, resolve(foo, [symbol, symbol2, symbol3])); + + assert.strictEqual(actual[symbol], 1); + assert.strictEqual(actual[symbol2], 2); + + if (isPick) { + assert.strictEqual(actual[symbol3], 3); + } else { + assert.ok(!(symbol3 in actual)); + } + } + }); + + it('`_.' + methodName + '` should work with an array `object`', function() { + var array = [1, 2, 3]; + assert.deepStrictEqual(func(array, resolve(array, '1')), { '1': 2 }); + }); + }); +}); diff --git a/test/pick.js b/test/pick.js new file mode 100644 index 000000000..08d62911d --- /dev/null +++ b/test/pick.js @@ -0,0 +1,52 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { args, toArgs } from './utils.js'; +import pick from '../pick.js'; + +describe('pick', function() { + var args = toArgs(['a', 'c']), + object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }, + nested = { 'a': 1, 'b': { 'c': 2, 'd': 3 } }; + + it('should flatten `paths`', function() { + assert.deepStrictEqual(pick(object, 'a', 'c'), { 'a': 1, 'c': 3 }); + assert.deepStrictEqual(pick(object, ['a', 'd'], 'c'), { 'a': 1, 'c': 3, 'd': 4 }); + }); + + it('should support deep paths', function() { + assert.deepStrictEqual(pick(nested, 'b.c'), { 'b': { 'c': 2 } }); + }); + + it('should support path arrays', function() { + var object = { 'a.b': 1, 'a': { 'b': 2 } }, + actual = pick(object, [['a.b']]); + + assert.deepStrictEqual(actual, { 'a.b': 1 }); + }); + + it('should pick a key over a path', function() { + var object = { 'a.b': 1, 'a': { 'b': 2 } }; + + lodashStable.each(['a.b', ['a.b']], function(path) { + assert.deepStrictEqual(pick(object, path), { 'a.b': 1 }); + }); + }); + + it('should coerce `paths` to strings', function() { + assert.deepStrictEqual(pick({ '0': 'a', '1': 'b' }, 0), { '0': 'a' }); + }); + + it('should return an empty object when `object` is nullish', function() { + lodashStable.each([null, undefined], function(value) { + assert.deepStrictEqual(pick(value, 'valueOf'), {}); + }); + }); + + it('should work with a primitive `object`', function() { + assert.deepStrictEqual(pick('', 'slice'), { 'slice': ''.slice }); + }); + + it('should work with `arguments` object `paths`', function() { + assert.deepStrictEqual(pick(object, args), { 'a': 1, 'c': 3 }); + }); +}); diff --git a/test/pickBy.test.js b/test/pickBy.test.js new file mode 100644 index 000000000..aa074daee --- /dev/null +++ b/test/pickBy.test.js @@ -0,0 +1,22 @@ +import assert from 'assert'; +import { stubTrue } from './utils.js'; +import pickBy from '../pickBy.js'; + +describe('pickBy', function() { + it('should work with a predicate argument', function() { + var object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }; + + var actual = pickBy(object, function(n) { + return n == 1 || n == 3; + }); + + assert.deepStrictEqual(actual, { 'a': 1, 'c': 3 }); + }); + + it('should not treat keys with dots as deep paths', function() { + var object = { 'a.b.c': 1 }, + actual = pickBy(object, stubTrue); + + assert.deepStrictEqual(actual, { 'a.b.c': 1 }); + }); +}); diff --git a/test/property.test.js b/test/property.test.js new file mode 100644 index 000000000..a846e1e10 --- /dev/null +++ b/test/property.test.js @@ -0,0 +1,122 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { noop } from './utils.js'; +import property from '../property.js'; + +describe('property', function() { + it('should create a function that plucks a property value of a given object', function() { + var object = { 'a': 1 }; + + lodashStable.each(['a', ['a']], function(path) { + var prop = property(path); + assert.strictEqual(prop.length, 1); + assert.strictEqual(prop(object), 1); + }); + }); + + it('should pluck deep property values', function() { + var object = { 'a': { 'b': 2 } }; + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + var prop = property(path); + assert.strictEqual(prop(object), 2); + }); + }); + + it('should pluck inherited property values', function() { + function Foo() {} + Foo.prototype.a = 1; + + lodashStable.each(['a', ['a']], function(path) { + var prop = property(path); + assert.strictEqual(prop(new Foo), 1); + }); + }); + + it('should work with a non-string `path`', function() { + var array = [1, 2, 3]; + + lodashStable.each([1, [1]], function(path) { + var prop = property(path); + assert.strictEqual(prop(array), 2); + }); + }); + + it('should preserve the sign of `0`', function() { + var object = { '-0': 'a', '0': 'b' }, + props = [-0, Object(-0), 0, Object(0)]; + + var actual = lodashStable.map(props, function(key) { + var prop = property(key); + return prop(object); + }); + + assert.deepStrictEqual(actual, ['a', 'a', 'b', 'b']); + }); + + it('should coerce `path` to a string', function() { + function fn() {} + fn.toString = lodashStable.constant('fn'); + + var expected = [1, 2, 3, 4], + object = { 'null': 1, 'undefined': 2, 'fn': 3, '[object Object]': 4 }, + paths = [null, undefined, fn, {}]; + + lodashStable.times(2, function(index) { + var actual = lodashStable.map(paths, function(path) { + var prop = property(index ? [path] : path); + return prop(object); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('should pluck a key over a path', function() { + var object = { 'a.b': 1, 'a': { 'b': 2 } }; + + lodashStable.each(['a.b', ['a.b']], function(path) { + var prop = property(path); + assert.strictEqual(prop(object), 1); + }); + }); + + it('should return `undefined` when `object` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, noop); + + lodashStable.each(['constructor', ['constructor']], function(path) { + var prop = property(path); + + var actual = lodashStable.map(values, function(value, index) { + return index ? prop(value) : prop(); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('should return `undefined` for deep paths when `object` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, noop); + + lodashStable.each(['constructor.prototype.valueOf', ['constructor', 'prototype', 'valueOf']], function(path) { + var prop = property(path); + + var actual = lodashStable.map(values, function(value, index) { + return index ? prop(value) : prop(); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('should return `undefined` if parts of `path` are missing', function() { + var object = {}; + + lodashStable.each(['a', 'a[1].b.c', ['a'], ['a', '1', 'b', 'c']], function(path) { + var prop = property(path); + assert.strictEqual(prop(object), undefined); + }); + }); +}); diff --git a/test/propertyOf.test.js b/test/propertyOf.test.js new file mode 100644 index 000000000..0fe43b7b3 --- /dev/null +++ b/test/propertyOf.test.js @@ -0,0 +1,122 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { noop } from './utils.js'; +import propertyOf from '../propertyOf.js'; + +describe('propertyOf', function() { + it('should create a function that plucks a property value of a given key', function() { + var object = { 'a': 1 }, + propOf = propertyOf(object); + + assert.strictEqual(propOf.length, 1); + lodashStable.each(['a', ['a']], function(path) { + assert.strictEqual(propOf(path), 1); + }); + }); + + it('should pluck deep property values', function() { + var object = { 'a': { 'b': 2 } }, + propOf = propertyOf(object); + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + assert.strictEqual(propOf(path), 2); + }); + }); + + it('should pluck inherited property values', function() { + function Foo() { + this.a = 1; + } + Foo.prototype.b = 2; + + var propOf = propertyOf(new Foo); + + lodashStable.each(['b', ['b']], function(path) { + assert.strictEqual(propOf(path), 2); + }); + }); + + it('should work with a non-string `path`', function() { + var array = [1, 2, 3], + propOf = propertyOf(array); + + lodashStable.each([1, [1]], function(path) { + assert.strictEqual(propOf(path), 2); + }); + }); + + it('should preserve the sign of `0`', function() { + var object = { '-0': 'a', '0': 'b' }, + props = [-0, Object(-0), 0, Object(0)]; + + var actual = lodashStable.map(props, function(key) { + var propOf = propertyOf(object); + return propOf(key); + }); + + assert.deepStrictEqual(actual, ['a', 'a', 'b', 'b']); + }); + + it('should coerce `path` to a string', function() { + function fn() {} + fn.toString = lodashStable.constant('fn'); + + var expected = [1, 2, 3, 4], + object = { 'null': 1, 'undefined': 2, 'fn': 3, '[object Object]': 4 }, + paths = [null, undefined, fn, {}]; + + lodashStable.times(2, function(index) { + var actual = lodashStable.map(paths, function(path) { + var propOf = propertyOf(object); + return propOf(index ? [path] : path); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('should pluck a key over a path', function() { + var object = { 'a.b': 1, 'a': { 'b': 2 } }, + propOf = propertyOf(object); + + lodashStable.each(['a.b', ['a.b']], function(path) { + assert.strictEqual(propOf(path), 1); + }); + }); + + it('should return `undefined` when `object` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, noop); + + lodashStable.each(['constructor', ['constructor']], function(path) { + var actual = lodashStable.map(values, function(value, index) { + var propOf = index ? propertyOf(value) : propertyOf(); + return propOf(path); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('should return `undefined` for deep paths when `object` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, noop); + + lodashStable.each(['constructor.prototype.valueOf', ['constructor', 'prototype', 'valueOf']], function(path) { + var actual = lodashStable.map(values, function(value, index) { + var propOf = index ? propertyOf(value) : propertyOf(); + return propOf(path); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('should return `undefined` if parts of `path` are missing', function() { + var propOf = propertyOf({}); + + lodashStable.each(['a', 'a[1].b.c', ['a'], ['a', '1', 'b', 'c']], function(path) { + assert.strictEqual(propOf(path), undefined); + }); + }); +}); diff --git a/test/pull-methods.js b/test/pull-methods.js new file mode 100644 index 000000000..4336b48e8 --- /dev/null +++ b/test/pull-methods.js @@ -0,0 +1,49 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _ } from './utils.js'; + +describe('pull methods', function() { + lodashStable.each(['pull', 'pullAll', 'pullAllWith'], function(methodName) { + var func = _[methodName], + isPull = methodName == 'pull'; + + function pull(array, values) { + return isPull + ? func.apply(undefined, [array].concat(values)) + : func(array, values); + } + + it('`_.' + methodName + '` should modify and return the array', function() { + var array = [1, 2, 3], + actual = pull(array, [1, 3]); + + assert.strictEqual(actual, array); + assert.deepStrictEqual(array, [2]); + }); + + it('`_.' + methodName + '` should preserve holes in arrays', function() { + var array = [1, 2, 3, 4]; + delete array[1]; + delete array[3]; + + pull(array, [1]); + assert.ok(!('0' in array)); + assert.ok(!('2' in array)); + }); + + it('`_.' + methodName + '` should treat holes as `undefined`', function() { + var array = [1, 2, 3]; + delete array[1]; + + pull(array, [undefined]); + assert.deepStrictEqual(array, [1, 3]); + }); + + it('`_.' + methodName + '` should match `NaN`', function() { + var array = [1, NaN, 3, NaN]; + + pull(array, [NaN]); + assert.deepStrictEqual(array, [1, 3]); + }); + }); +}); diff --git a/test/pullAll.test.js b/test/pullAll.test.js new file mode 100644 index 000000000..7579e6a0f --- /dev/null +++ b/test/pullAll.test.js @@ -0,0 +1,11 @@ +import assert from 'assert'; +import pullAll from '../pullAll.js'; + +describe('pullAll', function() { + it('should work with the same value for `array` and `values`', function() { + var array = [{ 'a': 1 }, { 'b': 2 }], + actual = pullAll(array, array); + + assert.deepStrictEqual(actual, []); + }); +}); diff --git a/test/pullAllBy.test.js b/test/pullAllBy.test.js new file mode 100644 index 000000000..a7fb64f61 --- /dev/null +++ b/test/pullAllBy.test.js @@ -0,0 +1,26 @@ +import assert from 'assert'; +import { slice } from './utils.js'; +import pullAllBy from '../pullAllBy.js'; + +describe('pullAllBy', function() { + it('should accept an `iteratee`', function() { + var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }]; + + var actual = pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], function(object) { + return object.x; + }); + + assert.deepStrictEqual(actual, [{ 'x': 2 }]); + }); + + it('should provide correct `iteratee` arguments', function() { + var args, + array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }]; + + pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], function() { + args || (args = slice.call(arguments)); + }); + + assert.deepStrictEqual(args, [{ 'x': 1 }]); + }); +}); diff --git a/test/pullAllWith.test.js b/test/pullAllWith.test.js new file mode 100644 index 000000000..3a47ebaae --- /dev/null +++ b/test/pullAllWith.test.js @@ -0,0 +1,13 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import pullAllWith from '../pullAllWith.js'; + +describe('pullAllWith', function() { + it('should work with a `comparator`', function() { + var objects = [{ 'x': 1, 'y': 1 }, { 'x': 2, 'y': 2 }, { 'x': 3, 'y': 3 }], + expected = [objects[0], objects[2]], + actual = pullAllWith(objects, [{ 'x': 2, 'y': 2 }], lodashStable.isEqual); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/pullAt.js b/test/pullAt.js new file mode 100644 index 000000000..c70020eb2 --- /dev/null +++ b/test/pullAt.js @@ -0,0 +1,122 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { empties, stubOne, noop, falsey } from './utils.js'; +import pullAt from '../pullAt.js'; + +describe('pullAt', function() { + it('should modify the array and return removed elements', function() { + var array = [1, 2, 3], + actual = pullAt(array, [0, 1]); + + assert.deepStrictEqual(array, [3]); + assert.deepStrictEqual(actual, [1, 2]); + }); + + it('should work with unsorted indexes', function() { + var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + actual = pullAt(array, [1, 3, 11, 7, 5, 9]); + + assert.deepStrictEqual(array, [1, 3, 5, 7, 9, 11]); + assert.deepStrictEqual(actual, [2, 4, 12, 8, 6, 10]); + }); + + it('should work with repeated indexes', function() { + var array = [1, 2, 3, 4], + actual = pullAt(array, [0, 2, 0, 1, 0, 2]); + + assert.deepStrictEqual(array, [4]); + assert.deepStrictEqual(actual, [1, 3, 1, 2, 1, 3]); + }); + + it('should use `undefined` for nonexistent indexes', function() { + var array = ['a', 'b', 'c'], + actual = pullAt(array, [2, 4, 0]); + + assert.deepStrictEqual(array, ['b']); + assert.deepStrictEqual(actual, ['c', undefined, 'a']); + }); + + it('should flatten `indexes`', function() { + var array = ['a', 'b', 'c']; + assert.deepStrictEqual(pullAt(array, 2, 0), ['c', 'a']); + assert.deepStrictEqual(array, ['b']); + + array = ['a', 'b', 'c', 'd']; + assert.deepStrictEqual(pullAt(array, [3, 0], 2), ['d', 'a', 'c']); + assert.deepStrictEqual(array, ['b']); + }); + + it('should return an empty array when no indexes are given', function() { + var array = ['a', 'b', 'c'], + actual = pullAt(array); + + assert.deepStrictEqual(array, ['a', 'b', 'c']); + assert.deepStrictEqual(actual, []); + + actual = pullAt(array, [], []); + + assert.deepStrictEqual(array, ['a', 'b', 'c']); + assert.deepStrictEqual(actual, []); + }); + + it('should work with non-index paths', function() { + var values = lodashStable.reject(empties, function(value) { + return (value === 0) || lodashStable.isArray(value); + }).concat(-1, 1.1); + + var array = lodashStable.transform(values, function(result, value) { + result[value] = 1; + }, []); + + var expected = lodashStable.map(values, stubOne), + actual = pullAt(array, values); + + assert.deepStrictEqual(actual, expected); + + expected = lodashStable.map(values, noop); + actual = lodashStable.at(array, values); + + assert.deepStrictEqual(actual, expected); + }); + + it('should preserve the sign of `0`', function() { + var props = [-0, Object(-0), 0, Object(0)]; + + var actual = lodashStable.map(props, function(key) { + var array = [-1]; + array['-0'] = -2; + return pullAt(array, key); + }); + + assert.deepStrictEqual(actual, [[-2], [-2], [-1], [-1]]); + }); + + it('should support deep paths', function() { + var array = []; + array.a = { 'b': 2 }; + + var actual = pullAt(array, 'a.b'); + + assert.deepStrictEqual(actual, [2]); + assert.deepStrictEqual(array.a, {}); + + try { + actual = pullAt(array, 'a.b.c'); + } catch (e) {} + + assert.deepStrictEqual(actual, [undefined]); + }); + + it('should work with a falsey `array` when keys are given', function() { + var values = falsey.slice(), + expected = lodashStable.map(values, lodashStable.constant(Array(4))); + + var actual = lodashStable.map(values, function(array) { + try { + return pullAt(array, 0, 1, 'pop', 'push'); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/random.js b/test/random.js new file mode 100644 index 000000000..a111dc84f --- /dev/null +++ b/test/random.js @@ -0,0 +1,104 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { MAX_INTEGER, stubTrue } from './utils.js'; +import random from '../random.js'; + +describe('random', function() { + var array = Array(1000); + + it('should return `0` or `1` when no arguments are given', function() { + var actual = lodashStable.uniq(lodashStable.map(array, function() { + return random(); + })).sort(); + + assert.deepStrictEqual(actual, [0, 1]); + }); + + it('should support a `min` and `max`', function() { + var min = 5, + max = 10; + + assert.ok(lodashStable.some(array, function() { + var result = random(min, max); + return result >= min && result <= max; + })); + }); + + it('should support not providing a `max`', function() { + var min = 0, + max = 5; + + assert.ok(lodashStable.some(array, function() { + var result = random(max); + return result >= min && result <= max; + })); + }); + + it('should swap `min` and `max` when `min` > `max`', function() { + var min = 4, + max = 2, + expected = [2, 3, 4]; + + var actual = lodashStable.uniq(lodashStable.map(array, function() { + return random(min, max); + })).sort(); + + assert.deepStrictEqual(actual, expected); + }); + + it('should support large integer values', function() { + var min = Math.pow(2, 31), + max = Math.pow(2, 62); + + assert.ok(lodashStable.every(array, function() { + var result = random(min, max); + return result >= min && result <= max; + })); + + assert.ok(lodashStable.some(array, function() { + return random(MAX_INTEGER); + })); + }); + + it('should coerce arguments to finite numbers', function() { + var actual = [ + random(NaN, NaN), + random('1', '1'), + random(Infinity, Infinity) + ]; + + assert.deepStrictEqual(actual, [0, 1, MAX_INTEGER]); + }); + + it('should support floats', function() { + var min = 1.5, + max = 1.6, + actual = random(min, max); + + assert.ok(actual % 1); + assert.ok(actual >= min && actual <= max); + }); + + it('should support providing a `floating`', function() { + var actual = random(true); + assert.ok(actual % 1 && actual >= 0 && actual <= 1); + + actual = random(2, true); + assert.ok(actual % 1 && actual >= 0 && actual <= 2); + + actual = random(2, 4, true); + assert.ok(actual % 1 && actual >= 2 && actual <= 4); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var array = [1, 2, 3], + expected = lodashStable.map(array, stubTrue), + randoms = lodashStable.map(array, random); + + var actual = lodashStable.map(randoms, function(result, index) { + return result >= 0 && result <= array[index] && (result % 1) == 0; + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/range-methods.js b/test/range-methods.js new file mode 100644 index 000000000..7f011e9d0 --- /dev/null +++ b/test/range-methods.js @@ -0,0 +1,82 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, falsey } from './utils.js'; + +describe('range methods', function() { + lodashStable.each(['range', 'rangeRight'], function(methodName) { + var func = _[methodName], + isRange = methodName == 'range'; + + function resolve(range) { + return isRange ? range : range.reverse(); + } + + it('`_.' + methodName + '` should infer the sign of `step` when only `end` is given', function() { + assert.deepStrictEqual(func(4), resolve([0, 1, 2, 3])); + assert.deepStrictEqual(func(-4), resolve([0, -1, -2, -3])); + }); + + it('`_.' + methodName + '` should infer the sign of `step` when only `start` and `end` are given', function() { + assert.deepStrictEqual(func(1, 5), resolve([1, 2, 3, 4])); + assert.deepStrictEqual(func(5, 1), resolve([5, 4, 3, 2])); + }); + + it('`_.' + methodName + '` should work with a `start`, `end`, and `step`', function() { + assert.deepStrictEqual(func(0, -4, -1), resolve([0, -1, -2, -3])); + assert.deepStrictEqual(func(5, 1, -1), resolve([5, 4, 3, 2])); + assert.deepStrictEqual(func(0, 20, 5), resolve([0, 5, 10, 15])); + }); + + it('`_.' + methodName + '` should support a `step` of `0`', function() { + assert.deepStrictEqual(func(1, 4, 0), [1, 1, 1]); + }); + + it('`_.' + methodName + '` should work with a `step` larger than `end`', function() { + assert.deepStrictEqual(func(1, 5, 20), [1]); + }); + + it('`_.' + methodName + '` should work with a negative `step`', function() { + assert.deepStrictEqual(func(0, -4, -1), resolve([0, -1, -2, -3])); + assert.deepStrictEqual(func(21, 10, -3), resolve([21, 18, 15, 12])); + }); + + it('`_.' + methodName + '` should support `start` of `-0`', function() { + var actual = func(-0, 1); + assert.strictEqual(1 / actual[0], -Infinity); + }); + + it('`_.' + methodName + '` should treat falsey `start` as `0`', function() { + lodashStable.each(falsey, function(value, index) { + if (index) { + assert.deepStrictEqual(func(value), []); + assert.deepStrictEqual(func(value, 1), [0]); + } else { + assert.deepStrictEqual(func(), []); + } + }); + }); + + it('`_.' + methodName + '` should coerce arguments to finite numbers', function() { + var actual = [ + func('1'), + func('0', 1), + func(0, 1, '1'), + func(NaN), + func(NaN, NaN) + ]; + + assert.deepStrictEqual(actual, [[0], [0], [0], [], []]); + }); + + it('`_.' + methodName + '` should work as an iteratee for methods like `_.map`', function() { + var array = [1, 2, 3], + object = { 'a': 1, 'b': 2, 'c': 3 }, + expected = lodashStable.map([[0], [0, 1], [0, 1, 2]], resolve); + + lodashStable.each([array, object], function(collection) { + var actual = lodashStable.map(collection, func); + assert.deepStrictEqual(actual, expected); + }); + }); + }); +}); diff --git a/test/rearg.js b/test/rearg.js new file mode 100644 index 000000000..1e76fdb51 --- /dev/null +++ b/test/rearg.js @@ -0,0 +1,70 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice, empties } from './utils.js'; +import rearg from '../rearg.js'; + +describe('rearg', function() { + function fn() { + return slice.call(arguments); + } + + it('should reorder arguments provided to `func`', function() { + var rearged = rearg(fn, [2, 0, 1]); + assert.deepStrictEqual(rearged('b', 'c', 'a'), ['a', 'b', 'c']); + }); + + it('should work with repeated indexes', function() { + var rearged = rearg(fn, [1, 1, 1]); + assert.deepStrictEqual(rearged('c', 'a', 'b'), ['a', 'a', 'a']); + }); + + it('should use `undefined` for nonexistent indexes', function() { + var rearged = rearg(fn, [1, 4]); + assert.deepStrictEqual(rearged('b', 'a', 'c'), ['a', undefined, 'c']); + }); + + it('should use `undefined` for non-index values', function() { + var values = lodashStable.reject(empties, function(value) { + return (value === 0) || lodashStable.isArray(value); + }).concat(-1, 1.1); + + var expected = lodashStable.map(values, lodashStable.constant([undefined, 'b', 'c'])); + + var actual = lodashStable.map(values, function(value) { + var rearged = rearg(fn, [value]); + return rearged('a', 'b', 'c'); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should not rearrange arguments when no indexes are given', function() { + var rearged = rearg(fn); + assert.deepStrictEqual(rearged('a', 'b', 'c'), ['a', 'b', 'c']); + + rearged = rearg(fn, [], []); + assert.deepStrictEqual(rearged('a', 'b', 'c'), ['a', 'b', 'c']); + }); + + it('should accept multiple index arguments', function() { + var rearged = rearg(fn, 2, 0, 1); + assert.deepStrictEqual(rearged('b', 'c', 'a'), ['a', 'b', 'c']); + }); + + it('should accept multiple arrays of indexes', function() { + var rearged = rearg(fn, [2], [0, 1]); + assert.deepStrictEqual(rearged('b', 'c', 'a'), ['a', 'b', 'c']); + }); + + it('should work with fewer indexes than arguments', function() { + var rearged = rearg(fn, [1, 0]); + assert.deepStrictEqual(rearged('b', 'a', 'c'), ['a', 'b', 'c']); + }); + + it('should work on functions that have been rearged', function() { + var rearged1 = rearg(fn, 2, 1, 0), + rearged2 = rearg(rearged1, 1, 0, 2); + + assert.deepStrictEqual(rearged2('b', 'c', 'a'), ['a', 'b', 'c']); + }); +}); diff --git a/test/reduce-methods.js b/test/reduce-methods.js new file mode 100644 index 000000000..34f4b34c3 --- /dev/null +++ b/test/reduce-methods.js @@ -0,0 +1,68 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, empties, noop, add } from './utils.js'; + +describe('reduce methods', function() { + lodashStable.each(['reduce', 'reduceRight'], function(methodName) { + var func = _[methodName], + array = [1, 2, 3], + isReduce = methodName == 'reduce'; + + it('`_.' + methodName + '` should reduce a collection to a single value', function() { + var actual = func(['a', 'b', 'c'], function(accumulator, value) { + return accumulator + value; + }, ''); + + assert.strictEqual(actual, isReduce ? 'abc' : 'cba'); + }); + + it('`_.' + methodName + '` should support empty collections without an initial `accumulator` value', function() { + var actual = [], + expected = lodashStable.map(empties, noop); + + lodashStable.each(empties, function(value) { + try { + actual.push(func(value, noop)); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should support empty collections with an initial `accumulator` value', function() { + var expected = lodashStable.map(empties, lodashStable.constant('x')); + + var actual = lodashStable.map(empties, function(value) { + try { + return func(value, noop, 'x'); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should handle an initial `accumulator` value of `undefined`', function() { + var actual = func([], noop, undefined); + assert.strictEqual(actual, undefined); + }); + + it('`_.' + methodName + '` should return `undefined` for empty collections when no `accumulator` is given (test in IE > 9 and modern browsers)', function() { + var array = [], + object = { '0': 1, 'length': 0 }; + + if ('__proto__' in array) { + array.__proto__ = object; + assert.strictEqual(func(array, noop), undefined); + } + assert.strictEqual(func(object, noop), undefined); + }); + + it('`_.' + methodName + '` should return an unwrapped value when implicitly chaining', function() { + assert.strictEqual(_(array)[methodName](add), 6); + }); + + it('`_.' + methodName + '` should return a wrapped value when explicitly chaining', function() { + assert.ok(_(array).chain()[methodName](add) instanceof _); + }); + }); +}); diff --git a/test/reduce.js b/test/reduce.js new file mode 100644 index 000000000..bae164750 --- /dev/null +++ b/test/reduce.js @@ -0,0 +1,57 @@ +import assert from 'assert'; +import { slice } from './utils.js'; +import reduce from '../reduce.js'; +import head from '../head.js'; +import keys from '../keys.js'; + +describe('reduce', function() { + var array = [1, 2, 3]; + + it('should use the first element of a collection as the default `accumulator`', function() { + assert.strictEqual(reduce(array), 1); + }); + + it('should provide correct `iteratee` arguments when iterating an array', function() { + var args; + + reduce(array, function() { + args || (args = slice.call(arguments)); + }, 0); + + assert.deepStrictEqual(args, [0, 1, 0, array]); + + args = undefined; + reduce(array, function() { + args || (args = slice.call(arguments)); + }); + + assert.deepStrictEqual(args, [1, 2, 1, array]); + }); + + it('should provide correct `iteratee` arguments when iterating an object', function() { + var args, + object = { 'a': 1, 'b': 2 }, + firstKey = head(keys(object)); + + var expected = firstKey == 'a' + ? [0, 1, 'a', object] + : [0, 2, 'b', object]; + + reduce(object, function() { + args || (args = slice.call(arguments)); + }, 0); + + assert.deepStrictEqual(args, expected); + + args = undefined; + expected = firstKey == 'a' + ? [1, 2, 'b', object] + : [2, 1, 'a', object]; + + reduce(object, function() { + args || (args = slice.call(arguments)); + }); + + assert.deepStrictEqual(args, expected); + }); +}); diff --git a/test/reduceRight.js b/test/reduceRight.js new file mode 100644 index 000000000..ea2beae02 --- /dev/null +++ b/test/reduceRight.js @@ -0,0 +1,56 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice } from './utils.js'; +import reduceRight from '../reduceRight.js'; + +describe('reduceRight', function() { + var array = [1, 2, 3]; + + it('should use the last element of a collection as the default `accumulator`', function() { + assert.strictEqual(reduceRight(array), 3); + }); + + it('should provide correct `iteratee` arguments when iterating an array', function() { + var args; + + reduceRight(array, function() { + args || (args = slice.call(arguments)); + }, 0); + + assert.deepStrictEqual(args, [0, 3, 2, array]); + + args = undefined; + reduceRight(array, function() { + args || (args = slice.call(arguments)); + }); + + assert.deepStrictEqual(args, [3, 2, 1, array]); + }); + + it('should provide correct `iteratee` arguments when iterating an object', function() { + var args, + object = { 'a': 1, 'b': 2 }, + isFIFO = lodashStable.keys(object)[0] == 'a'; + + var expected = isFIFO + ? [0, 2, 'b', object] + : [0, 1, 'a', object]; + + reduceRight(object, function() { + args || (args = slice.call(arguments)); + }, 0); + + assert.deepStrictEqual(args, expected); + + args = undefined; + expected = isFIFO + ? [2, 1, 'a', object] + : [1, 2, 'b', object]; + + reduceRight(object, function() { + args || (args = slice.call(arguments)); + }); + + assert.deepStrictEqual(args, expected); + }); +}); diff --git a/test/reject.test.js b/test/reject.test.js new file mode 100644 index 000000000..fcf305041 --- /dev/null +++ b/test/reject.test.js @@ -0,0 +1,11 @@ +import assert from 'assert'; +import { isEven } from './utils.js'; +import reject from '../reject.js'; + +describe('reject', function() { + var array = [1, 2, 3]; + + it('should return elements the `predicate` returns falsey for', function() { + assert.deepStrictEqual(reject(array, isEven), [1, 3]); + }); +}); diff --git a/test/remove.js b/test/remove.js new file mode 100644 index 000000000..92b025151 --- /dev/null +++ b/test/remove.js @@ -0,0 +1,80 @@ +import assert from 'assert'; +import { isEven, slice } from './utils.js'; +import remove from '../remove.js'; + +describe('remove', function() { + it('should modify the array and return removed elements', function() { + var array = [1, 2, 3, 4], + actual = remove(array, isEven); + + assert.deepStrictEqual(array, [1, 3]); + assert.deepStrictEqual(actual, [2, 4]); + }); + + it('should provide correct `predicate` arguments', function() { + var argsList = [], + array = [1, 2, 3], + clone = array.slice(); + + remove(array, function(n, index) { + var args = slice.call(arguments); + args[2] = args[2].slice(); + argsList.push(args); + return isEven(index); + }); + + assert.deepStrictEqual(argsList, [[1, 0, clone], [2, 1, clone], [3, 2, clone]]); + }); + + it('should work with `_.matches` shorthands', function() { + var objects = [{ 'a': 0, 'b': 1 }, { 'a': 1, 'b': 2 }]; + remove(objects, { 'a': 1 }); + assert.deepStrictEqual(objects, [{ 'a': 0, 'b': 1 }]); + }); + + it('should work with `_.matchesProperty` shorthands', function() { + var objects = [{ 'a': 0, 'b': 1 }, { 'a': 1, 'b': 2 }]; + remove(objects, ['a', 1]); + assert.deepStrictEqual(objects, [{ 'a': 0, 'b': 1 }]); + }); + + it('should work with `_.property` shorthands', function() { + var objects = [{ 'a': 0 }, { 'a': 1 }]; + remove(objects, 'a'); + assert.deepStrictEqual(objects, [{ 'a': 0 }]); + }); + + it('should preserve holes in arrays', function() { + var array = [1, 2, 3, 4]; + delete array[1]; + delete array[3]; + + remove(array, function(n) { + return n === 1; + }); + + assert.ok(!('0' in array)); + assert.ok(!('2' in array)); + }); + + it('should treat holes as `undefined`', function() { + var array = [1, 2, 3]; + delete array[1]; + + remove(array, function(n) { + return n == null; + }); + + assert.deepStrictEqual(array, [1, 3]); + }); + + it('should not mutate the array until all elements to remove are determined', function() { + var array = [1, 2, 3]; + + remove(array, function(n, index) { + return isEven(index); + }); + + assert.deepStrictEqual(array, [2]); + }); +}); diff --git a/test/repeat.js b/test/repeat.js new file mode 100644 index 000000000..caa4e2e60 --- /dev/null +++ b/test/repeat.js @@ -0,0 +1,46 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubThree } from './utils.js'; +import repeat from '../repeat.js'; + +describe('repeat', function() { + var string = 'abc'; + + it('should repeat a string `n` times', function() { + assert.strictEqual(repeat('*', 3), '***'); + assert.strictEqual(repeat(string, 2), 'abcabc'); + }); + + it('should treat falsey `n` values, except `undefined`, as `0`', function() { + var expected = lodashStable.map(falsey, function(value) { + return value === undefined ? string : ''; + }); + + var actual = lodashStable.map(falsey, function(n, index) { + return index ? repeat(string, n) : repeat(string); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return an empty string if `n` is <= `0`', function() { + assert.strictEqual(repeat(string, 0), ''); + assert.strictEqual(repeat(string, -2), ''); + }); + + it('should coerce `n` to an integer', function() { + assert.strictEqual(repeat(string, '2'), 'abcabc'); + assert.strictEqual(repeat(string, 2.6), 'abcabc'); + assert.strictEqual(repeat('*', { 'valueOf': stubThree }), '***'); + }); + + it('should coerce `string` to a string', function() { + assert.strictEqual(repeat(Object(string), 2), 'abcabc'); + assert.strictEqual(repeat({ 'toString': lodashStable.constant('*') }, 3), '***'); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var actual = lodashStable.map(['a', 'b', 'c'], repeat); + assert.deepStrictEqual(actual, ['a', 'b', 'c']); + }); +}); diff --git a/test/replace.test.js b/test/replace.test.js new file mode 100644 index 000000000..f28a9f03c --- /dev/null +++ b/test/replace.test.js @@ -0,0 +1,10 @@ +import assert from 'assert'; +import replace from '../replace.js'; + +describe('replace', function() { + it('should replace the matched pattern', function() { + var string = 'abcde'; + assert.strictEqual(replace(string, 'de', '123'), 'abc123'); + assert.strictEqual(replace(string, /[bd]/g, '-'), 'a-c-e'); + }); +}); diff --git a/test/rest.js b/test/rest.js new file mode 100644 index 000000000..d7b98de46 --- /dev/null +++ b/test/rest.js @@ -0,0 +1,49 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice, _ } from './utils.js'; + +describe('rest', function() { + function fn(a, b, c) { + return slice.call(arguments); + } + + it('should apply a rest parameter to `func`', function() { + var rest = _.rest(fn); + assert.deepStrictEqual(rest(1, 2, 3, 4), [1, 2, [3, 4]]); + }); + + it('should work with `start`', function() { + var rest = _.rest(fn, 1); + assert.deepStrictEqual(rest(1, 2, 3, 4), [1, [2, 3, 4]]); + }); + + it('should treat `start` as `0` for `NaN` or negative values', function() { + var values = [-1, NaN, 'a'], + expected = lodashStable.map(values, lodashStable.constant([[1, 2, 3, 4]])); + + var actual = lodashStable.map(values, function(value) { + var rest = _.rest(fn, value); + return rest(1, 2, 3, 4); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should coerce `start` to an integer', function() { + var rest = _.rest(fn, 1.6); + assert.deepStrictEqual(rest(1, 2, 3), [1, [2, 3]]); + }); + + it('should use an empty array when `start` is not reached', function() { + var rest = _.rest(fn); + assert.deepStrictEqual(rest(1), [1, undefined, []]); + }); + + it('should work on functions with more than three parameters', function() { + var rest = _.rest(function(a, b, c, d) { + return slice.call(arguments); + }); + + assert.deepStrictEqual(rest(1, 2, 3, 4, 5), [1, 2, 3, [4, 5]]); + }); +}); diff --git a/test/result.test.js b/test/result.test.js new file mode 100644 index 000000000..3087ac0c6 --- /dev/null +++ b/test/result.test.js @@ -0,0 +1,33 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { stubB } from './utils.js'; +import result from '../result.js'; + +describe('result', function() { + var object = { 'a': 1, 'b': stubB }; + + it('should invoke function values', function() { + assert.strictEqual(result(object, 'b'), 'b'); + }); + + it('should invoke default function values', function() { + var actual = result(object, 'c', object.b); + assert.strictEqual(actual, 'b'); + }); + + it('should invoke nested function values', function() { + var value = { 'a': lodashStable.constant({ 'b': stubB }) }; + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + assert.strictEqual(result(value, path), 'b'); + }); + }); + + it('should invoke deep property methods with the correct `this` binding', function() { + var value = { 'a': { 'b': function() { return this.c; }, 'c': 1 } }; + + lodashStable.each(['a.b', ['a', 'b']], function(path) { + assert.strictEqual(result(value, path), 1); + }); + }); +}); diff --git a/test/reverse.js b/test/reverse.js new file mode 100644 index 000000000..f6c80ca59 --- /dev/null +++ b/test/reverse.js @@ -0,0 +1,94 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { LARGE_ARRAY_SIZE, identity } from './utils.js'; +import reverse from '../reverse.js'; +import compact from '../compact.js'; +import head from '../head.js'; + +describe('reverse', function() { + var largeArray = lodashStable.range(LARGE_ARRAY_SIZE).concat(null), + smallArray = [0, 1, 2, null]; + + it('should reverse `array`', function() { + var array = [1, 2, 3], + actual = reverse(array); + + assert.strictEqual(actual, array); + assert.deepStrictEqual(array, [3, 2, 1]); + }); + + it('should return the wrapped reversed `array`', function() { + lodashStable.times(2, function(index) { + var array = (index ? largeArray : smallArray).slice(), + clone = array.slice(), + wrapped = _(array).reverse(), + actual = wrapped.value(); + + assert.ok(wrapped instanceof _); + assert.strictEqual(actual, array); + assert.deepStrictEqual(actual, clone.slice().reverse()); + }); + }); + + it('should work in a lazy sequence', function() { + lodashStable.times(2, function(index) { + var array = (index ? largeArray : smallArray).slice(), + expected = array.slice(), + actual = _(array).slice(1).reverse().value(); + + assert.deepStrictEqual(actual, expected.slice(1).reverse()); + assert.deepStrictEqual(array, expected); + }); + }); + + it('should be lazy when in a lazy sequence', function() { + var spy = { + 'toString': function() { + throw new Error('spy was revealed'); + } + }; + + var array = largeArray.concat(spy), + expected = array.slice(); + + try { + var wrapped = _(array).slice(1).map(String).reverse(), + actual = wrapped.last(); + } catch (e) {} + + assert.ok(wrapped instanceof _); + assert.strictEqual(actual, '1'); + assert.deepEqual(array, expected); + }); + + it('should work in a hybrid sequence', function() { + lodashStable.times(2, function(index) { + var clone = (index ? largeArray : smallArray).slice(); + + lodashStable.each(['map', 'filter'], function(methodName) { + var array = clone.slice(), + expected = clone.slice(1, -1).reverse(), + actual = _(array)[methodName](identity).thru(compact).reverse().value(); + + assert.deepStrictEqual(actual, expected); + + array = clone.slice(); + actual = _(array).thru(compact)[methodName](identity).pull(1).push(3).reverse().value(); + + assert.deepStrictEqual(actual, [3].concat(expected.slice(0, -1))); + }); + }); + }); + + it('should track the `__chain__` value of a wrapper', function() { + lodashStable.times(2, function(index) { + var array = (index ? largeArray : smallArray).slice(), + expected = array.slice().reverse(), + wrapped = _(array).chain().reverse().head(); + + assert.ok(wrapped instanceof _); + assert.strictEqual(wrapped.value(), head(expected)); + assert.deepStrictEqual(array, expected); + }); + }); +}); diff --git a/test/round-methods.js b/test/round-methods.js new file mode 100644 index 000000000..be0a92498 --- /dev/null +++ b/test/round-methods.js @@ -0,0 +1,82 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, MAX_SAFE_INTEGER, stubFalse } from './utils.js'; +import round from '../round.js'; + +describe('round methods', function() { + lodashStable.each(['ceil', 'floor', 'round'], function(methodName) { + var func = _[methodName], + isCeil = methodName == 'ceil', + isFloor = methodName == 'floor'; + + it('`_.' + methodName + '` should return a rounded number without a precision', function() { + var actual = func(4.006); + assert.strictEqual(actual, isCeil ? 5 : 4); + }); + + it('`_.' + methodName + '` should work with a precision of `0`', function() { + var actual = func(4.006, 0); + assert.strictEqual(actual, isCeil ? 5 : 4); + }); + + it('`_.' + methodName + '` should work with a positive precision', function() { + var actual = func(4.016, 2); + assert.strictEqual(actual, isFloor ? 4.01 : 4.02); + + actual = func(4.1, 2); + assert.strictEqual(actual, 4.1); + }); + + it('`_.' + methodName + '` should work with a negative precision', function() { + var actual = func(4160, -2); + assert.strictEqual(actual, isFloor ? 4100 : 4200); + }); + + it('`_.' + methodName + '` should coerce `precision` to an integer', function() { + var actual = func(4.006, NaN); + assert.strictEqual(actual, isCeil ? 5 : 4); + + var expected = isFloor ? 4.01 : 4.02; + + actual = func(4.016, 2.6); + assert.strictEqual(actual, expected); + + actual = func(4.016, '+2'); + assert.strictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should work with exponential notation and `precision`', function() { + var actual = func(5e1, 2); + assert.deepStrictEqual(actual, 50); + + actual = func('5e', 1); + assert.deepStrictEqual(actual, NaN); + + actual = func('5e1e1', 1); + assert.deepStrictEqual(actual, NaN); + }); + + it('`_.' + methodName + '` should preserve the sign of `0`', function() { + var values = [[0], [-0], ['0'], ['-0'], [0, 1], [-0, 1], ['0', 1], ['-0', 1]], + expected = [Infinity, -Infinity, Infinity, -Infinity, Infinity, -Infinity, Infinity, -Infinity]; + + var actual = lodashStable.map(values, function(args) { + return 1 / func.apply(undefined, args); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should not return `NaN` for large `precision` values', function() { + var results = [ + round(10.0000001, 1000), + round(MAX_SAFE_INTEGER, 293) + ]; + + var expected = lodashStable.map(results, stubFalse), + actual = lodashStable.map(results, lodashStable.isNaN); + + assert.deepStrictEqual(actual, expected); + }); + }); +}); diff --git a/test/runInContext.js b/test/runInContext.js new file mode 100644 index 000000000..402ddac66 --- /dev/null +++ b/test/runInContext.js @@ -0,0 +1,29 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import runInContext from '../runInContext.js'; +import uniqueId from '../uniqueId.js'; + +describe('runInContext', function() { + it('should not require a fully populated `context` object', function() { + var lodash = runInContext({ + 'setTimeout': function(func) { func(); } + }); + + var pass = false; + lodash.delay(function() { pass = true; }, 32); + assert.ok(pass); + }); + + it('should use a zeroed `_.uniqueId` counter', function() { + lodashStable.times(2, uniqueId); + + var oldId = Number(uniqueId()), + lodash = runInContext(); + + assert.ok(uniqueId() > oldId); + + var id = lodash.uniqueId(); + assert.strictEqual(id, '1'); + assert.ok(id < oldId); + }); +}); diff --git a/test/sample.js b/test/sample.js new file mode 100644 index 000000000..b29634a42 --- /dev/null +++ b/test/sample.js @@ -0,0 +1,32 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { empties, noop } from './utils.js'; +import sample from '../sample.js'; + +describe('sample', function() { + var array = [1, 2, 3]; + + it('should return a random element', function() { + var actual = sample(array); + assert.ok(lodashStable.includes(array, actual)); + }); + + it('should return `undefined` when sampling empty collections', function() { + var expected = lodashStable.map(empties, noop); + + var actual = lodashStable.transform(empties, function(result, value) { + try { + result.push(sample(value)); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should sample an object', function() { + var object = { 'a': 1, 'b': 2, 'c': 3 }, + actual = sample(object); + + assert.ok(lodashStable.includes(array, actual)); + }); +}); diff --git a/test/sampleSize.js b/test/sampleSize.js new file mode 100644 index 000000000..e394c00c5 --- /dev/null +++ b/test/sampleSize.js @@ -0,0 +1,76 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, empties, stubArray } from './utils.js'; +import sampleSize from '../sampleSize.js'; + +describe('sampleSize', function() { + var array = [1, 2, 3]; + + it('should return an array of random elements', function() { + var actual = sampleSize(array, 2); + + assert.strictEqual(actual.length, 2); + assert.deepStrictEqual(lodashStable.difference(actual, array), []); + }); + + it('should contain elements of the collection', function() { + var actual = sampleSize(array, array.length).sort(); + + assert.deepStrictEqual(actual, array); + }); + + it('should treat falsey `size` values, except `undefined`, as `0`', function() { + var expected = lodashStable.map(falsey, function(value) { + return value === undefined ? ['a'] : []; + }); + + var actual = lodashStable.map(falsey, function(size, index) { + return index ? sampleSize(['a'], size) : sampleSize(['a']); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return an empty array when `n` < `1` or `NaN`', function() { + lodashStable.each([0, -1, -Infinity], function(n) { + assert.deepStrictEqual(sampleSize(array, n), []); + }); + }); + + it('should return all elements when `n` >= `length`', function() { + lodashStable.each([3, 4, Math.pow(2, 32), Infinity], function(n) { + var actual = sampleSize(array, n).sort(); + assert.deepStrictEqual(actual, array); + }); + }); + + it('should coerce `n` to an integer', function() { + var actual = sampleSize(array, 1.6); + assert.strictEqual(actual.length, 1); + }); + + it('should return an empty array for empty collections', function() { + var expected = lodashStable.map(empties, stubArray); + + var actual = lodashStable.transform(empties, function(result, value) { + try { + result.push(sampleSize(value, 1)); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should sample an object', function() { + var object = { 'a': 1, 'b': 2, 'c': 3 }, + actual = sampleSize(object, 2); + + assert.strictEqual(actual.length, 2); + assert.deepStrictEqual(lodashStable.difference(actual, lodashStable.values(object)), []); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var actual = lodashStable.map([['a']], sampleSize); + assert.deepStrictEqual(actual, [['a']]); + }); +}); diff --git a/test/set-methods.js b/test/set-methods.js new file mode 100644 index 000000000..c7c69a349 --- /dev/null +++ b/test/set-methods.js @@ -0,0 +1,172 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, symbol, defineProperty } from './utils.js'; +import unset from '../unset.js'; + +describe('set methods', function() { + lodashStable.each(['update', 'updateWith', 'set', 'setWith'], function(methodName) { + var func = _[methodName], + isUpdate = /^update/.test(methodName); + + var oldValue = 1, + value = 2, + updater = isUpdate ? lodashStable.constant(value) : value; + + it('`_.' + methodName + '` should set property values', function() { + lodashStable.each(['a', ['a']], function(path) { + var object = { 'a': oldValue }, + actual = func(object, path, updater); + + assert.strictEqual(actual, object); + assert.strictEqual(object.a, value); + }); + }); + + it('`_.' + methodName + '` should preserve the sign of `0`', function() { + var props = [-0, Object(-0), 0, Object(0)], + expected = lodashStable.map(props, lodashStable.constant(value)); + + var actual = lodashStable.map(props, function(key) { + var object = { '-0': 'a', '0': 'b' }; + func(object, key, updater); + return object[lodashStable.toString(key)]; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should unset symbol keyed property values', function() { + if (Symbol) { + var object = {}; + object[symbol] = 1; + + assert.strictEqual(unset(object, symbol), true); + assert.ok(!(symbol in object)); + } + }); + + it('`_.' + methodName + '` should set deep property values', function() { + lodashStable.each(['a.b', ['a', 'b']], function(path) { + var object = { 'a': { 'b': oldValue } }, + actual = func(object, path, updater); + + assert.strictEqual(actual, object); + assert.strictEqual(object.a.b, value); + }); + }); + + it('`_.' + methodName + '` should set a key over a path', function() { + lodashStable.each(['a.b', ['a.b']], function(path) { + var object = { 'a.b': oldValue }, + actual = func(object, path, updater); + + assert.strictEqual(actual, object); + assert.deepStrictEqual(object, { 'a.b': value }); + }); + }); + + it('`_.' + methodName + '` should not coerce array paths to strings', function() { + var object = { 'a,b,c': 1, 'a': { 'b': { 'c': 1 } } }; + + func(object, ['a', 'b', 'c'], updater); + assert.strictEqual(object.a.b.c, value); + }); + + it('`_.' + methodName + '` should not ignore empty brackets', function() { + var object = {}; + + func(object, 'a[]', updater); + assert.deepStrictEqual(object, { 'a': { '': value } }); + }); + + it('`_.' + methodName + '` should handle empty paths', function() { + lodashStable.each([['', ''], [[], ['']]], function(pair, index) { + var object = {}; + + func(object, pair[0], updater); + assert.deepStrictEqual(object, index ? {} : { '': value }); + + func(object, pair[1], updater); + assert.deepStrictEqual(object, { '': value }); + }); + }); + + it('`_.' + methodName + '` should handle complex paths', function() { + var object = { 'a': { '1.23': { '["b"]': { 'c': { "['d']": { '\ne\n': { 'f': { 'g': oldValue } } } } } } } }; + + var paths = [ + 'a[-1.23]["[\\"b\\"]"].c[\'[\\\'d\\\']\'][\ne\n][f].g', + ['a', '-1.23', '["b"]', 'c', "['d']", '\ne\n', 'f', 'g'] + ]; + + lodashStable.each(paths, function(path) { + func(object, path, updater); + assert.strictEqual(object.a[-1.23]['["b"]'].c["['d']"]['\ne\n'].f.g, value); + object.a[-1.23]['["b"]'].c["['d']"]['\ne\n'].f.g = oldValue; + }); + }); + + it('`_.' + methodName + '` should create parts of `path` that are missing', function() { + var object = {}; + + lodashStable.each(['a[1].b.c', ['a', '1', 'b', 'c']], function(path) { + var actual = func(object, path, updater); + + assert.strictEqual(actual, object); + assert.deepStrictEqual(actual, { 'a': [undefined, { 'b': { 'c': value } }] }); + assert.ok(!('0' in object.a)); + + delete object.a; + }); + }); + + it('`_.' + methodName + '` should not error when `object` is nullish', function() { + var values = [null, undefined], + expected = [[null, null], [undefined, undefined]]; + + var actual = lodashStable.map(values, function(value) { + try { + return [func(value, 'a.b', updater), func(value, ['a', 'b'], updater)]; + } catch (e) { + return e.message; + } + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should overwrite primitives in the path', function() { + lodashStable.each(['a.b', ['a', 'b']], function(path) { + var object = { 'a': '' }; + + func(object, path, updater); + assert.deepStrictEqual(object, { 'a': { 'b': 2 } }); + }); + }); + + it('`_.' + methodName + '` should not create an array for missing non-index property names that start with numbers', function() { + var object = {}; + + func(object, ['1a', '2b', '3c'], updater); + assert.deepStrictEqual(object, { '1a': { '2b': { '3c': value } } }); + }); + + it('`_.' + methodName + '` should not assign values that are the same as their destinations', function() { + lodashStable.each(['a', ['a'], { 'a': 1 }, NaN], function(value) { + var object = {}, + pass = true, + updater = isUpdate ? lodashStable.constant(value) : value; + + defineProperty(object, 'a', { + 'configurable': true, + 'enumerable': true, + 'get': lodashStable.constant(value), + 'set': function() { pass = false; } + }); + + func(object, 'a', updater); + assert.ok(pass); + }); + }); + }); +}); diff --git a/test/setWith.js b/test/setWith.js new file mode 100644 index 000000000..87cc2a2a1 --- /dev/null +++ b/test/setWith.js @@ -0,0 +1,19 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { noop } from './utils.js'; +import setWith from '../setWith.js'; + +describe('setWith', function() { + it('should work with a `customizer` callback', function() { + var actual = setWith({ '0': {} }, '[0][1][2]', 3, function(value) { + return lodashStable.isObject(value) ? undefined : {}; + }); + + assert.deepStrictEqual(actual, { '0': { '1': { '2': 3 } } }); + }); + + it('should work with a `customizer` that returns `undefined`', function() { + var actual = setWith({}, 'a[0].b.c', 4, noop); + assert.deepStrictEqual(actual, { 'a': [{ 'b': { 'c': 4 } }] }); + }); +}); diff --git a/test/shuffle.js b/test/shuffle.js new file mode 100644 index 000000000..fb86b89fd --- /dev/null +++ b/test/shuffle.js @@ -0,0 +1,29 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import shuffle from '../shuffle.js'; + +describe('shuffle', function() { + var array = [1, 2, 3], + object = { 'a': 1, 'b': 2, 'c': 3 }; + + it('should return a new array', function() { + assert.notStrictEqual(shuffle(array), array); + }); + + it('should contain the same elements after a collection is shuffled', function() { + assert.deepStrictEqual(shuffle(array).sort(), array); + assert.deepStrictEqual(shuffle(object).sort(), array); + }); + + it('should shuffle small collections', function() { + var actual = lodashStable.times(1000, function() { + return shuffle([1, 2]); + }); + + assert.deepStrictEqual(lodashStable.sortBy(lodashStable.uniqBy(actual, String), '0'), [[1, 2], [2, 1]]); + }); + + it('should treat number values for `collection` as empty', function() { + assert.deepStrictEqual(shuffle(1), []); + }); +}); diff --git a/test/size.test.js b/test/size.test.js new file mode 100644 index 000000000..286173453 --- /dev/null +++ b/test/size.test.js @@ -0,0 +1,75 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubZero, args, push, arrayProto, realm, MAX_SAFE_INTEGER } from './utils.js'; +import size from '../size.js'; + +describe('size', function() { + var array = [1, 2, 3]; + + it('should return the number of own enumerable string keyed properties of an object', function() { + assert.strictEqual(size({ 'one': 1, 'two': 2, 'three': 3 }), 3); + }); + + it('should return the length of an array', function() { + assert.strictEqual(size(array), 3); + }); + + it('should accept a falsey `object`', function() { + var expected = lodashStable.map(falsey, stubZero); + + var actual = lodashStable.map(falsey, function(object, index) { + try { + return index ? size(object) : size(); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with `arguments` objects', function() { + assert.strictEqual(size(args), 3); + }); + + it('should work with jQuery/MooTools DOM query collections', function() { + function Foo(elements) { + push.apply(this, elements); + } + Foo.prototype = { 'length': 0, 'splice': arrayProto.splice }; + + assert.strictEqual(size(new Foo(array)), 3); + }); + + it('should work with maps', function() { + if (Map) { + lodashStable.each([new Map, realm.map], function(map) { + map.set('a', 1); + map.set('b', 2); + assert.strictEqual(size(map), 2); + map.clear(); + }); + } + }); + + it('should work with sets', function() { + if (Set) { + lodashStable.each([new Set, realm.set], function(set) { + set.add(1); + set.add(2); + assert.strictEqual(size(set), 2); + set.clear(); + }); + } + }); + + it('should not treat objects with negative lengths as array-like', function() { + assert.strictEqual(size({ 'length': -1 }), 1); + }); + + it('should not treat objects with lengths larger than `MAX_SAFE_INTEGER` as array-like', function() { + assert.strictEqual(size({ 'length': MAX_SAFE_INTEGER + 1 }), 1); + }); + + it('should not treat objects with non-number lengths as array-like', function() { + assert.strictEqual(size({ 'length': '0' }), 1); + }); +}); diff --git a/test/slice-and-toArray.js b/test/slice-and-toArray.js new file mode 100644 index 000000000..7c8607015 --- /dev/null +++ b/test/slice-and-toArray.js @@ -0,0 +1,43 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, args, document, body } from './utils.js'; + +describe('slice and toArray', function() { + lodashStable.each(['slice', 'toArray'], function(methodName) { + var array = [1, 2, 3], + func = _[methodName]; + + it('`_.' + methodName + '` should return a dense array', function() { + var sparse = Array(3); + sparse[1] = 2; + + var actual = func(sparse); + + assert.ok('0' in actual); + assert.ok('2' in actual); + assert.deepStrictEqual(actual, sparse); + }); + + it('`_.' + methodName + '` should treat array-like objects like arrays', function() { + var object = { '0': 'a', 'length': 1 }; + assert.deepStrictEqual(func(object), ['a']); + assert.deepStrictEqual(func(args), array); + }); + + it('`_.' + methodName + '` should return a shallow clone of arrays', function() { + var actual = func(array); + assert.deepStrictEqual(actual, array); + assert.notStrictEqual(actual, array); + }); + + it('`_.' + methodName + '` should work with a node list for `collection`', function() { + if (document) { + try { + var actual = func(document.getElementsByTagName('body')); + } catch (e) {} + + assert.deepStrictEqual(actual, [body]); + } + }); + }); +}); diff --git a/test/slice.js b/test/slice.js new file mode 100644 index 000000000..1e47e1283 --- /dev/null +++ b/test/slice.js @@ -0,0 +1,133 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, LARGE_ARRAY_SIZE } from './utils.js'; +import slice from '../slice.js'; + +describe('slice', function() { + var array = [1, 2, 3]; + + it('should use a default `start` of `0` and a default `end` of `length`', function() { + var actual = slice(array); + assert.deepStrictEqual(actual, array); + assert.notStrictEqual(actual, array); + }); + + it('should work with a positive `start`', function() { + assert.deepStrictEqual(slice(array, 1), [2, 3]); + assert.deepStrictEqual(slice(array, 1, 3), [2, 3]); + }); + + it('should work with a `start` >= `length`', function() { + lodashStable.each([3, 4, Math.pow(2, 32), Infinity], function(start) { + assert.deepStrictEqual(slice(array, start), []); + }); + }); + + it('should treat falsey `start` values as `0`', function() { + var expected = lodashStable.map(falsey, lodashStable.constant(array)); + + var actual = lodashStable.map(falsey, function(start) { + return slice(array, start); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with a negative `start`', function() { + assert.deepStrictEqual(slice(array, -1), [3]); + }); + + it('should work with a negative `start` <= negative `length`', function() { + lodashStable.each([-3, -4, -Infinity], function(start) { + assert.deepStrictEqual(slice(array, start), array); + }); + }); + + it('should work with `start` >= `end`', function() { + lodashStable.each([2, 3], function(start) { + assert.deepStrictEqual(slice(array, start, 2), []); + }); + }); + + it('should work with a positive `end`', function() { + assert.deepStrictEqual(slice(array, 0, 1), [1]); + }); + + it('should work with a `end` >= `length`', function() { + lodashStable.each([3, 4, Math.pow(2, 32), Infinity], function(end) { + assert.deepStrictEqual(slice(array, 0, end), array); + }); + }); + + it('should treat falsey `end` values, except `undefined`, as `0`', function() { + var expected = lodashStable.map(falsey, function(value) { + return value === undefined ? array : []; + }); + + var actual = lodashStable.map(falsey, function(end, index) { + return index ? slice(array, 0, end) : slice(array, 0); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with a negative `end`', function() { + assert.deepStrictEqual(slice(array, 0, -1), [1, 2]); + }); + + it('should work with a negative `end` <= negative `length`', function() { + lodashStable.each([-3, -4, -Infinity], function(end) { + assert.deepStrictEqual(slice(array, 0, end), []); + }); + }); + + it('should coerce `start` and `end` to integers', function() { + var positions = [[0.1, 1.6], ['0', 1], [0, '1'], ['1'], [NaN, 1], [1, NaN]]; + + var actual = lodashStable.map(positions, function(pos) { + return slice.apply(_, [array].concat(pos)); + }); + + assert.deepStrictEqual(actual, [[1], [1], [1], [2, 3], [1], []]); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var array = [[1], [2, 3]], + actual = lodashStable.map(array, slice); + + assert.deepStrictEqual(actual, array); + assert.notStrictEqual(actual, array); + }); + + it('should work in a lazy sequence', function() { + var array = lodashStable.range(1, LARGE_ARRAY_SIZE + 1), + length = array.length, + wrapped = _(array); + + lodashStable.each(['map', 'filter'], function(methodName) { + assert.deepEqual(wrapped[methodName]().slice(0, -1).value(), array.slice(0, -1)); + assert.deepEqual(wrapped[methodName]().slice(1).value(), array.slice(1)); + assert.deepEqual(wrapped[methodName]().slice(1, 3).value(), array.slice(1, 3)); + assert.deepEqual(wrapped[methodName]().slice(-1).value(), array.slice(-1)); + + assert.deepEqual(wrapped[methodName]().slice(length).value(), array.slice(length)); + assert.deepEqual(wrapped[methodName]().slice(3, 2).value(), array.slice(3, 2)); + assert.deepEqual(wrapped[methodName]().slice(0, -length).value(), array.slice(0, -length)); + assert.deepEqual(wrapped[methodName]().slice(0, null).value(), array.slice(0, null)); + + assert.deepEqual(wrapped[methodName]().slice(0, length).value(), array.slice(0, length)); + assert.deepEqual(wrapped[methodName]().slice(-length).value(), array.slice(-length)); + assert.deepEqual(wrapped[methodName]().slice(null).value(), array.slice(null)); + + assert.deepEqual(wrapped[methodName]().slice(0, 1).value(), array.slice(0, 1)); + assert.deepEqual(wrapped[methodName]().slice(NaN, '1').value(), array.slice(NaN, '1')); + + assert.deepEqual(wrapped[methodName]().slice(0.1, 1.1).value(), array.slice(0.1, 1.1)); + assert.deepEqual(wrapped[methodName]().slice('0', 1).value(), array.slice('0', 1)); + assert.deepEqual(wrapped[methodName]().slice(0, '1').value(), array.slice(0, '1')); + assert.deepEqual(wrapped[methodName]().slice('1').value(), array.slice('1')); + assert.deepEqual(wrapped[methodName]().slice(NaN, 1).value(), array.slice(NaN, 1)); + assert.deepEqual(wrapped[methodName]().slice(1, NaN).value(), array.slice(1, NaN)); + }); + }); +}); diff --git a/test/some.js b/test/some.js new file mode 100644 index 000000000..5f93f4145 --- /dev/null +++ b/test/some.js @@ -0,0 +1,76 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { identity, empties, stubFalse, stubTrue } from './utils.js'; +import some from '../some.js'; + +describe('some', function() { + it('should return `true` if `predicate` returns truthy for any element', function() { + assert.strictEqual(some([false, 1, ''], identity), true); + assert.strictEqual(some([null, 'a', 0], identity), true); + }); + + it('should return `false` for empty collections', function() { + var expected = lodashStable.map(empties, stubFalse); + + var actual = lodashStable.map(empties, function(value) { + try { + return some(value, identity); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return `true` as soon as `predicate` returns truthy', function() { + var count = 0; + + assert.strictEqual(some([null, true, null], function(value) { + count++; + return value; + }), true); + + assert.strictEqual(count, 2); + }); + + it('should return `false` if `predicate` returns falsey for all elements', function() { + assert.strictEqual(some([false, false, false], identity), false); + assert.strictEqual(some([null, 0, ''], identity), false); + }); + + it('should use `_.identity` when `predicate` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, stubFalse); + + var actual = lodashStable.map(values, function(value, index) { + var array = [0, 0]; + return index ? some(array, value) : some(array); + }); + + assert.deepStrictEqual(actual, expected); + + expected = lodashStable.map(values, stubTrue); + actual = lodashStable.map(values, function(value, index) { + var array = [0, 1]; + return index ? some(array, value) : some(array); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with `_.property` shorthands', function() { + var objects = [{ 'a': 0, 'b': 0 }, { 'a': 0, 'b': 1 }]; + assert.strictEqual(some(objects, 'a'), false); + assert.strictEqual(some(objects, 'b'), true); + }); + + it('should work with `_.matches` shorthands', function() { + var objects = [{ 'a': 0, 'b': 0 }, { 'a': 1, 'b': 1}]; + assert.strictEqual(some(objects, { 'a': 0 }), true); + assert.strictEqual(some(objects, { 'b': 2 }), false); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var actual = lodashStable.map([[1]], some); + assert.deepStrictEqual(actual, [true]); + }); +}); diff --git a/test/sortBy-methods.js b/test/sortBy-methods.js new file mode 100644 index 000000000..fb4748d0f --- /dev/null +++ b/test/sortBy-methods.js @@ -0,0 +1,87 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _ } from './utils.js'; + +describe('sortBy methods', function() { + lodashStable.each(['orderBy', 'sortBy'], function(methodName) { + var func = _[methodName]; + + function Pair(a, b, c) { + this.a = a; + this.b = b; + this.c = c; + } + + var objects = [ + { 'a': 'x', 'b': 3 }, + { 'a': 'y', 'b': 4 }, + { 'a': 'x', 'b': 1 }, + { 'a': 'y', 'b': 2 } + ]; + + var stableArray = [ + new Pair(1, 1, 1), new Pair(1, 2, 1), + new Pair(1, 1, 1), new Pair(1, 2, 1), + new Pair(1, 3, 1), new Pair(1, 4, 1), + new Pair(1, 5, 1), new Pair(1, 6, 1), + new Pair(2, 1, 2), new Pair(2, 2, 2), + new Pair(2, 3, 2), new Pair(2, 4, 2), + new Pair(2, 5, 2), new Pair(2, 6, 2), + new Pair(undefined, 1, 1), new Pair(undefined, 2, 1), + new Pair(undefined, 3, 1), new Pair(undefined, 4, 1), + new Pair(undefined, 5, 1), new Pair(undefined, 6, 1) + ]; + + var stableObject = lodashStable.zipObject('abcdefghijklmnopqrst'.split(''), stableArray); + + it('`_.' + methodName + '` should sort multiple properties in ascending order', function() { + var actual = func(objects, ['a', 'b']); + assert.deepStrictEqual(actual, [objects[2], objects[0], objects[3], objects[1]]); + }); + + it('`_.' + methodName + '` should support iteratees', function() { + var actual = func(objects, ['a', function(object) { return object.b; }]); + assert.deepStrictEqual(actual, [objects[2], objects[0], objects[3], objects[1]]); + }); + + it('`_.' + methodName + '` should perform a stable sort (test in IE > 8 and V8)', function() { + lodashStable.each([stableArray, stableObject], function(value, index) { + var actual = func(value, ['a', 'c']); + assert.deepStrictEqual(actual, stableArray, index ? 'object' : 'array'); + }); + }); + + it('`_.' + methodName + '` should not error on nullish elements', function() { + try { + var actual = func(objects.concat(null, undefined), ['a', 'b']); + } catch (e) {} + + assert.deepStrictEqual(actual, [objects[2], objects[0], objects[3], objects[1], null, undefined]); + }); + + it('`_.' + methodName + '` should work as an iteratee for methods like `_.reduce`', function() { + var objects = [ + { 'a': 'x', '0': 3 }, + { 'a': 'y', '0': 4 }, + { 'a': 'x', '0': 1 }, + { 'a': 'y', '0': 2 } + ]; + + var funcs = [func, lodashStable.partialRight(func, 'bogus')]; + + lodashStable.each(['a', 0, [0]], function(props, index) { + var expected = lodashStable.map(funcs, lodashStable.constant( + index + ? [objects[2], objects[3], objects[0], objects[1]] + : [objects[0], objects[2], objects[1], objects[3]] + )); + + var actual = lodashStable.map(funcs, function(func) { + return lodashStable.reduce([props], func, objects); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + }); +}); diff --git a/test/sortBy.js b/test/sortBy.js new file mode 100644 index 000000000..6cf7acc4f --- /dev/null +++ b/test/sortBy.js @@ -0,0 +1,75 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import sortBy from '../sortBy.js'; + +describe('sortBy', function() { + var objects = [ + { 'a': 'x', 'b': 3 }, + { 'a': 'y', 'b': 4 }, + { 'a': 'x', 'b': 1 }, + { 'a': 'y', 'b': 2 } + ]; + + it('should sort in ascending order by `iteratee`', function() { + var actual = lodashStable.map(sortBy(objects, function(object) { + return object.b; + }), 'b'); + + assert.deepStrictEqual(actual, [1, 2, 3, 4]); + }); + + it('should use `_.identity` when `iteratee` is nullish', function() { + var array = [3, 2, 1], + values = [, null, undefined], + expected = lodashStable.map(values, lodashStable.constant([1, 2, 3])); + + var actual = lodashStable.map(values, function(value, index) { + return index ? sortBy(array, value) : sortBy(array); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with `_.property` shorthands', function() { + var actual = lodashStable.map(sortBy(objects.concat(undefined), 'b'), 'b'); + assert.deepStrictEqual(actual, [1, 2, 3, 4, undefined]); + }); + + it('should work with an object for `collection`', function() { + var actual = sortBy({ 'a': 1, 'b': 2, 'c': 3 }, Math.sin); + assert.deepStrictEqual(actual, [3, 1, 2]); + }); + + it('should move `NaN`, nullish, and symbol values to the end', function() { + var symbol1 = Symbol ? Symbol('a') : null, + symbol2 = Symbol ? Symbol('b') : null, + array = [NaN, undefined, null, 4, symbol1, null, 1, symbol2, undefined, 3, NaN, 2], + expected = [1, 2, 3, 4, symbol1, symbol2, null, null, undefined, undefined, NaN, NaN]; + + assert.deepStrictEqual(sortBy(array), expected); + + array = [NaN, undefined, symbol1, null, 'd', null, 'a', symbol2, undefined, 'c', NaN, 'b']; + expected = ['a', 'b', 'c', 'd', symbol1, symbol2, null, null, undefined, undefined, NaN, NaN]; + + assert.deepStrictEqual(sortBy(array), expected); + }); + + it('should treat number values for `collection` as empty', function() { + assert.deepStrictEqual(sortBy(1), []); + }); + + it('should coerce arrays returned from `iteratee`', function() { + var actual = sortBy(objects, function(object) { + var result = [object.a, object.b]; + result.toString = function() { return String(this[0]); }; + return result; + }); + + assert.deepStrictEqual(actual, [objects[0], objects[2], objects[1], objects[3]]); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var actual = lodashStable.map([[2, 1, 3], [3, 2, 1]], sortBy); + assert.deepStrictEqual(actual, [[1, 2, 3], [1, 2, 3]]); + }); +}); diff --git a/test/sortedIndex-methods.js b/test/sortedIndex-methods.js new file mode 100644 index 000000000..056918bc7 --- /dev/null +++ b/test/sortedIndex-methods.js @@ -0,0 +1,84 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _ } from './utils.js'; +import sortBy from '../sortBy.js'; + +describe('sortedIndex methods', function() { + lodashStable.each(['sortedIndex', 'sortedLastIndex'], function(methodName) { + var func = _[methodName], + isSortedIndex = methodName == 'sortedIndex'; + + it('`_.' + methodName + '` should return the insert index', function() { + var array = [30, 50], + values = [30, 40, 50], + expected = isSortedIndex ? [0, 1, 1] : [1, 1, 2]; + + var actual = lodashStable.map(values, function(value) { + return func(array, value); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should work with an array of strings', function() { + var array = ['a', 'c'], + values = ['a', 'b', 'c'], + expected = isSortedIndex ? [0, 1, 1] : [1, 1, 2]; + + var actual = lodashStable.map(values, function(value) { + return func(array, value); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should accept a nullish `array` and a `value`', function() { + var values = [null, undefined], + expected = lodashStable.map(values, lodashStable.constant([0, 0, 0])); + + var actual = lodashStable.map(values, function(array) { + return [func(array, 1), func(array, undefined), func(array, NaN)]; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should align with `_.sortBy`', function() { + var symbol1 = Symbol ? Symbol('a') : null, + symbol2 = Symbol ? Symbol('b') : null, + symbol3 = Symbol ? Symbol('c') : null, + expected = [1, '2', {}, symbol1, symbol2, null, undefined, NaN, NaN]; + + lodashStable.each([ + [NaN, symbol1, null, 1, '2', {}, symbol2, NaN, undefined], + ['2', null, 1, symbol1, NaN, {}, NaN, symbol2, undefined] + ], function(array) { + assert.deepStrictEqual(sortBy(array), expected); + assert.strictEqual(func(expected, 3), 2); + assert.strictEqual(func(expected, symbol3), isSortedIndex ? 3 : (Symbol ? 5 : 6)); + assert.strictEqual(func(expected, null), isSortedIndex ? (Symbol ? 5 : 3) : 6); + assert.strictEqual(func(expected, undefined), isSortedIndex ? 6 : 7); + assert.strictEqual(func(expected, NaN), isSortedIndex ? 7 : 9); + }); + }); + + it('`_.' + methodName + '` should align with `_.sortBy` for nulls', function() { + var array = [null, null]; + + assert.strictEqual(func(array, null), isSortedIndex ? 0 : 2); + assert.strictEqual(func(array, 1), 0); + assert.strictEqual(func(array, 'a'), 0); + }); + + it('`_.' + methodName + '` should align with `_.sortBy` for symbols', function() { + var symbol1 = Symbol ? Symbol('a') : null, + symbol2 = Symbol ? Symbol('b') : null, + symbol3 = Symbol ? Symbol('c') : null, + array = [symbol1, symbol2]; + + assert.strictEqual(func(array, symbol3), isSortedIndex ? 0 : 2); + assert.strictEqual(func(array, 1), 0); + assert.strictEqual(func(array, 'a'), 0); + }); + }); +}); diff --git a/test/sortedIndexBy-methods.js b/test/sortedIndexBy-methods.js new file mode 100644 index 000000000..25f96f3b4 --- /dev/null +++ b/test/sortedIndexBy-methods.js @@ -0,0 +1,52 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, slice, MAX_ARRAY_LENGTH, MAX_ARRAY_INDEX } from './utils.js'; + +describe('sortedIndexBy methods', function() { + lodashStable.each(['sortedIndexBy', 'sortedLastIndexBy'], function(methodName) { + var func = _[methodName], + isSortedIndexBy = methodName == 'sortedIndexBy'; + + it('`_.' + methodName + '` should provide correct `iteratee` arguments', function() { + var args; + + func([30, 50], 40, function() { + args || (args = slice.call(arguments)); + }); + + assert.deepStrictEqual(args, [40]); + }); + + it('`_.' + methodName + '` should work with `_.property` shorthands', function() { + var objects = [{ 'x': 30 }, { 'x': 50 }], + actual = func(objects, { 'x': 40 }, 'x'); + + assert.strictEqual(actual, 1); + }); + + it('`_.' + methodName + '` should support arrays larger than `MAX_ARRAY_LENGTH / 2`', function() { + lodashStable.each([Math.ceil(MAX_ARRAY_LENGTH / 2), MAX_ARRAY_LENGTH], function(length) { + var array = [], + values = [MAX_ARRAY_LENGTH, NaN, undefined]; + + array.length = length; + + lodashStable.each(values, function(value) { + var steps = 0; + + var actual = func(array, value, function(value) { + steps++; + return value; + }); + + var expected = (isSortedIndexBy ? !lodashStable.isNaN(value) : lodashStable.isFinite(value)) + ? 0 + : Math.min(length, MAX_ARRAY_INDEX); + + assert.ok(steps == 32 || steps == 33); + assert.strictEqual(actual, expected); + }); + }); + }); + }); +}); diff --git a/test/sortedIndexOf-methods.js b/test/sortedIndexOf-methods.js new file mode 100644 index 000000000..14550c564 --- /dev/null +++ b/test/sortedIndexOf-methods.js @@ -0,0 +1,15 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _ } from './utils.js'; + +describe('sortedIndexOf methods', function() { + lodashStable.each(['sortedIndexOf', 'sortedLastIndexOf'], function(methodName) { + var func = _[methodName], + isSortedIndexOf = methodName == 'sortedIndexOf'; + + it('`_.' + methodName + '` should perform a binary search', function() { + var sorted = [4, 4, 5, 5, 6, 6]; + assert.deepStrictEqual(func(sorted, 5), isSortedIndexOf ? 2 : 3); + }); + }); +}); diff --git a/test/sortedUniq.test.js b/test/sortedUniq.test.js new file mode 100644 index 000000000..fec499a66 --- /dev/null +++ b/test/sortedUniq.test.js @@ -0,0 +1,13 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import sortedUniq from '../sortedUniq.js'; + +describe('sortedUniq', function() { + it('should return unique values of a sorted array', function() { + var expected = [1, 2, 3]; + + lodashStable.each([[1, 2, 3], [1, 1, 2, 2, 3], [1, 2, 3, 3, 3, 3, 3]], function(array) { + assert.deepStrictEqual(sortedUniq(array), expected); + }); + }); +}); diff --git a/test/split.js b/test/split.js new file mode 100644 index 000000000..cc8e05b7f --- /dev/null +++ b/test/split.js @@ -0,0 +1,35 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import split from '../split.js'; + +describe('split', function() { + it('should split a string by `separator`', function() { + var string = 'abcde'; + assert.deepStrictEqual(split(string, 'c'), ['ab', 'de']); + assert.deepStrictEqual(split(string, /[bd]/), ['a', 'c', 'e']); + assert.deepStrictEqual(split(string, '', 2), ['a', 'b']); + }); + + it('should return an array containing an empty string for empty values', function() { + var values = [, null, undefined, ''], + expected = lodashStable.map(values, lodashStable.constant([''])); + + var actual = lodashStable.map(values, function(value, index) { + return index ? split(value) : split(); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var strings = ['abc', 'def', 'ghi'], + actual = lodashStable.map(strings, split); + + assert.deepStrictEqual(actual, [['abc'], ['def'], ['ghi']]); + }); + + it('should allow mixed string and array prototype methods', function() { + var wrapped = _('abc'); + assert.strictEqual(wrapped.split('b').join(','), 'a,c'); + }); +}); diff --git a/test/spread.js b/test/spread.js new file mode 100644 index 000000000..862221b93 --- /dev/null +++ b/test/spread.js @@ -0,0 +1,58 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice, _, stubTrue, falsey } from './utils.js'; + +describe('spread', function() { + function fn(a, b, c) { + return slice.call(arguments); + } + + it('should spread arguments to `func`', function() { + var spread = _.spread(fn), + expected = [1, 2]; + + assert.deepStrictEqual(spread([1, 2]), expected); + assert.deepStrictEqual(spread([1, 2], 3), expected); + }); + + it('should accept a falsey `array`', function() { + var spread = _.spread(stubTrue), + expected = lodashStable.map(falsey, stubTrue); + + var actual = lodashStable.map(falsey, function(array, index) { + try { + return index ? spread(array) : spread(); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with `start`', function() { + var spread = _.spread(fn, 1), + expected = [1, 2, 3]; + + assert.deepStrictEqual(spread(1, [2, 3]), expected); + assert.deepStrictEqual(spread(1, [2, 3], 4), expected); + }); + + it('should treat `start` as `0` for negative or `NaN` values', function() { + var values = [-1, NaN, 'a'], + expected = lodashStable.map(values, lodashStable.constant([1, 2])); + + var actual = lodashStable.map(values, function(value) { + var spread = _.spread(fn, value); + return spread([1, 2]); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should coerce `start` to an integer', function() { + var spread = _.spread(fn, 1.6), + expected = [1, 2, 3]; + + assert.deepStrictEqual(spread(1, [2, 3]), expected); + assert.deepStrictEqual(spread(1, [2, 3], 4), expected); + }); +}); diff --git a/test/startCase.js b/test/startCase.js new file mode 100644 index 000000000..f61d329d0 --- /dev/null +++ b/test/startCase.js @@ -0,0 +1,10 @@ +import assert from 'assert'; +import startCase from '../startCase.js'; + +describe('startCase', function() { + it('should uppercase only the first character of each word', function() { + assert.strictEqual(startCase('--foo-bar--'), 'Foo Bar'); + assert.strictEqual(startCase('fooBar'), 'Foo Bar'); + assert.strictEqual(startCase('__FOO_BAR__'), 'FOO BAR'); + }); +}); diff --git a/test/startsWith-and-endsWith.js b/test/startsWith-and-endsWith.js new file mode 100644 index 000000000..58dabfe94 --- /dev/null +++ b/test/startsWith-and-endsWith.js @@ -0,0 +1,38 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, MAX_SAFE_INTEGER } from './utils.js'; + +describe('startsWith and endsWith', function() { + lodashStable.each(['startsWith', 'endsWith'], function(methodName) { + var func = _[methodName], + isStartsWith = methodName == 'startsWith'; + + var string = 'abc', + chr = isStartsWith ? 'a' : 'c'; + + it('`_.' + methodName + '` should coerce `string` to a string', function() { + assert.strictEqual(func(Object(string), chr), true); + assert.strictEqual(func({ 'toString': lodashStable.constant(string) }, chr), true); + }); + + it('`_.' + methodName + '` should coerce `target` to a string', function() { + assert.strictEqual(func(string, Object(chr)), true); + assert.strictEqual(func(string, { 'toString': lodashStable.constant(chr) }), true); + }); + + it('`_.' + methodName + '` should coerce `position` to a number', function() { + var position = isStartsWith ? 1 : 2; + + assert.strictEqual(func(string, 'b', Object(position)), true); + assert.strictEqual(func(string, 'b', { 'toString': lodashStable.constant(String(position)) }), true); + }); + + it('should return `true` when `target` is an empty string regardless of `position`', function() { + var positions = [-Infinity, NaN, -3, -1, 0, 1, 2, 3, 5, MAX_SAFE_INTEGER, Infinity]; + + assert.ok(lodashStable.every(positions, function(position) { + return func(string, '', position); + })); + }); + }); +}); diff --git a/test/startsWith.js b/test/startsWith.js new file mode 100644 index 000000000..d58905032 --- /dev/null +++ b/test/startsWith.js @@ -0,0 +1,47 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { MAX_SAFE_INTEGER, falsey, stubTrue } from './utils.js'; +import startsWith from '../startsWith.js'; + +describe('startsWith', function() { + var string = 'abc'; + + it('should return `true` if a string starts with `target`', function() { + assert.strictEqual(startsWith(string, 'a'), true); + }); + + it('should return `false` if a string does not start with `target`', function() { + assert.strictEqual(startsWith(string, 'b'), false); + }); + + it('should work with a `position`', function() { + assert.strictEqual(startsWith(string, 'b', 1), true); + }); + + it('should work with `position` >= `length`', function() { + lodashStable.each([3, 5, MAX_SAFE_INTEGER, Infinity], function(position) { + assert.strictEqual(startsWith(string, 'a', position), false); + }); + }); + + it('should treat falsey `position` values as `0`', function() { + var expected = lodashStable.map(falsey, stubTrue); + + var actual = lodashStable.map(falsey, function(position) { + return startsWith(string, 'a', position); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should treat a negative `position` as `0`', function() { + lodashStable.each([-1, -3, -Infinity], function(position) { + assert.strictEqual(startsWith(string, 'a', position), true); + assert.strictEqual(startsWith(string, 'b', position), false); + }); + }); + + it('should coerce `position` to an integer', function() { + assert.strictEqual(startsWith(string, 'bc', 1.2), true); + }); +}); diff --git a/test/strict-mode-checks.js b/test/strict-mode-checks.js new file mode 100644 index 000000000..3423fe1b8 --- /dev/null +++ b/test/strict-mode-checks.js @@ -0,0 +1,22 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, isStrict, freeze } from './utils.js'; + +describe('strict mode checks', function() { + lodashStable.each(['assign', 'assignIn', 'bindAll', 'defaults', 'defaultsDeep', 'merge'], function(methodName) { + var func = _[methodName], + isBindAll = methodName == 'bindAll'; + + it('`_.' + methodName + '` should ' + (isStrict ? '' : 'not ') + 'throw strict mode errors', function() { + var object = freeze({ 'a': undefined, 'b': function() {} }), + pass = !isStrict; + + try { + func(object, isBindAll ? 'b' : { 'a': 1 }); + } catch (e) { + pass = !pass; + } + assert.ok(pass); + }); + }); +}); diff --git a/test/stub-methods.js b/test/stub-methods.js new file mode 100644 index 000000000..1fb0e61b6 --- /dev/null +++ b/test/stub-methods.js @@ -0,0 +1,32 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, empties } from './utils.js'; + +describe('stub methods', function() { + lodashStable.each(['noop', 'stubTrue', 'stubFalse', 'stubArray', 'stubObject', 'stubString'], function(methodName) { + var func = _[methodName]; + + var pair = ({ + 'stubArray': [[], 'an empty array'], + 'stubFalse': [false, '`false`'], + 'stubObject': [{}, 'an empty object'], + 'stubString': ['', 'an empty string'], + 'stubTrue': [true, '`true`'], + 'noop': [undefined, '`undefined`'] + })[methodName]; + + var values = Array(2).concat(empties, true, 1, 'a'), + expected = lodashStable.map(values, lodashStable.constant(pair[0])); + + it('`_.' + methodName + '` should return ' + pair[1], function() { + var actual = lodashStable.map(values, function(value, index) { + if (index < 2) { + return index ? func.call({}) : func(); + } + return func(value); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); +}); diff --git a/test/subtract.test.js b/test/subtract.test.js new file mode 100644 index 000000000..b148411ad --- /dev/null +++ b/test/subtract.test.js @@ -0,0 +1,15 @@ +import assert from 'assert'; +import subtract from '../subtract.js'; + +describe('subtract', function() { + it('should subtract two numbers', function() { + assert.strictEqual(subtract(6, 4), 2); + assert.strictEqual(subtract(-6, 4), -10); + assert.strictEqual(subtract(-6, -4), -2); + }); + + it('should coerce arguments to numbers', function() { + assert.strictEqual(subtract('6', '4'), 2); + assert.deepStrictEqual(subtract('x', 'y'), NaN); + }); +}); diff --git a/test/sum-methods.js b/test/sum-methods.js new file mode 100644 index 000000000..d536b9c50 --- /dev/null +++ b/test/sum-methods.js @@ -0,0 +1,36 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, empties, stubZero } from './utils.js'; + +describe('sum methods', function() { + lodashStable.each(['sum', 'sumBy'], function(methodName) { + var array = [6, 4, 2], + func = _[methodName]; + + it('`_.' + methodName + '` should return the sum of an array of numbers', function() { + assert.strictEqual(func(array), 12); + }); + + it('`_.' + methodName + '` should return `0` when passing empty `array` values', function() { + var expected = lodashStable.map(empties, stubZero); + + var actual = lodashStable.map(empties, function(value) { + return func(value); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should skip `undefined` values', function() { + assert.strictEqual(func([1, undefined]), 1); + }); + + it('`_.' + methodName + '` should not skip `NaN` values', function() { + assert.deepStrictEqual(func([1, NaN]), NaN); + }); + + it('`_.' + methodName + '` should not coerce values to numbers', function() { + assert.strictEqual(func(['1', '2']), '12'); + }); + }); +}); diff --git a/test/sumBy.js b/test/sumBy.js new file mode 100644 index 000000000..62c780e58 --- /dev/null +++ b/test/sumBy.js @@ -0,0 +1,32 @@ +import assert from 'assert'; +import { slice } from './utils.js'; +import sumBy from '../sumBy.js'; + +describe('sumBy', function() { + var array = [6, 4, 2], + objects = [{ 'a': 2 }, { 'a': 3 }, { 'a': 1 }]; + + it('should work with an `iteratee`', function() { + var actual = sumBy(objects, function(object) { + return object.a; + }); + + assert.deepStrictEqual(actual, 6); + }); + + it('should provide correct `iteratee` arguments', function() { + var args; + + sumBy(array, function() { + args || (args = slice.call(arguments)); + }); + + assert.deepStrictEqual(args, [6]); + }); + + it('should work with `_.property` shorthands', function() { + var arrays = [[2], [3], [1]]; + assert.strictEqual(sumBy(arrays, 0), 6); + assert.strictEqual(sumBy(objects, 'a'), 6); + }); +}); diff --git a/test/tail.js b/test/tail.js new file mode 100644 index 000000000..6fd325796 --- /dev/null +++ b/test/tail.js @@ -0,0 +1,77 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, stubArray, LARGE_ARRAY_SIZE } from './utils.js'; +import tail from '../tail.js'; + +describe('tail', function() { + var array = [1, 2, 3]; + + it('should accept a falsey `array`', function() { + var expected = lodashStable.map(falsey, stubArray); + + var actual = lodashStable.map(falsey, function(array, index) { + try { + return index ? tail(array) : tail(); + } catch (e) {} + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should exclude the first element', function() { + assert.deepStrictEqual(tail(array), [2, 3]); + }); + + it('should return an empty when querying empty arrays', function() { + assert.deepStrictEqual(tail([]), []); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + actual = lodashStable.map(array, tail); + + assert.deepStrictEqual(actual, [[2, 3], [5, 6], [8, 9]]); + }); + + it('should work in a lazy sequence', function() { + var array = lodashStable.range(LARGE_ARRAY_SIZE), + values = []; + + var actual = _(array).tail().filter(function(value) { + values.push(value); + return false; + }) + .value(); + + assert.deepEqual(actual, []); + assert.deepEqual(values, array.slice(1)); + + values = []; + + actual = _(array).filter(function(value) { + values.push(value); + return isEven(value); + }) + .tail() + .value(); + + assert.deepEqual(actual, tail(_.filter(array, isEven))); + assert.deepEqual(values, array); + }); + + it('should not execute subsequent iteratees on an empty array in a lazy sequence', function() { + var array = lodashStable.range(LARGE_ARRAY_SIZE), + iteratee = function() { pass = false; }, + pass = true, + actual = _(array).slice(0, 1).tail().map(iteratee).value(); + + assert.ok(pass); + assert.deepEqual(actual, []); + + pass = true; + actual = _(array).filter().slice(0, 1).tail().map(iteratee).value(); + + assert.ok(pass); + assert.deepEqual(actual, []); + }); +}); diff --git a/test/take.js b/test/take.js new file mode 100644 index 000000000..76690a58b --- /dev/null +++ b/test/take.js @@ -0,0 +1,65 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, LARGE_ARRAY_SIZE, isEven } from './utils.js'; +import take from '../take.js'; + +describe('take', function() { + var array = [1, 2, 3]; + + it('should take the first two elements', function() { + assert.deepStrictEqual(take(array, 2), [1, 2]); + }); + + it('should treat falsey `n` values, except `undefined`, as `0`', function() { + var expected = lodashStable.map(falsey, function(value) { + return value === undefined ? [1] : []; + }); + + var actual = lodashStable.map(falsey, function(n) { + return take(array, n); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return an empty array when `n` < `1`', function() { + lodashStable.each([0, -1, -Infinity], function(n) { + assert.deepStrictEqual(take(array, n), []); + }); + }); + + it('should return all elements when `n` >= `length`', function() { + lodashStable.each([3, 4, Math.pow(2, 32), Infinity], function(n) { + assert.deepStrictEqual(take(array, n), array); + }); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + actual = lodashStable.map(array, take); + + assert.deepStrictEqual(actual, [[1], [4], [7]]); + }); + + it('should work in a lazy sequence', function() { + var array = lodashStable.range(1, LARGE_ARRAY_SIZE + 1), + predicate = function(value) { values.push(value); return isEven(value); }, + values = [], + actual = _(array).take(2).take().value(); + + assert.deepEqual(actual, take(take(array, 2))); + + actual = _(array).filter(predicate).take(2).take().value(); + assert.deepEqual(values, [1, 2]); + assert.deepEqual(actual, take(take(_.filter(array, predicate), 2))); + + actual = _(array).take(6).takeRight(4).take(2).takeRight().value(); + assert.deepEqual(actual, _.takeRight(take(_.takeRight(take(array, 6), 4), 2))); + + values = []; + + actual = _(array).take(array.length - 1).filter(predicate).take(6).takeRight(4).take(2).takeRight().value(); + assert.deepEqual(values, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + assert.deepEqual(actual, _.takeRight(take(_.takeRight(take(_.filter(take(array, array.length - 1), predicate), 6), 4), 2))); + }); +}); diff --git a/test/takeRight.js b/test/takeRight.js new file mode 100644 index 000000000..ee48abc80 --- /dev/null +++ b/test/takeRight.js @@ -0,0 +1,65 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { falsey, LARGE_ARRAY_SIZE, isEven } from './utils.js'; +import takeRight from '../takeRight.js'; + +describe('takeRight', function() { + var array = [1, 2, 3]; + + it('should take the last two elements', function() { + assert.deepStrictEqual(takeRight(array, 2), [2, 3]); + }); + + it('should treat falsey `n` values, except `undefined`, as `0`', function() { + var expected = lodashStable.map(falsey, function(value) { + return value === undefined ? [3] : []; + }); + + var actual = lodashStable.map(falsey, function(n) { + return takeRight(array, n); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return an empty array when `n` < `1`', function() { + lodashStable.each([0, -1, -Infinity], function(n) { + assert.deepStrictEqual(takeRight(array, n), []); + }); + }); + + it('should return all elements when `n` >= `length`', function() { + lodashStable.each([3, 4, Math.pow(2, 32), Infinity], function(n) { + assert.deepStrictEqual(takeRight(array, n), array); + }); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + actual = lodashStable.map(array, takeRight); + + assert.deepStrictEqual(actual, [[3], [6], [9]]); + }); + + it('should work in a lazy sequence', function() { + var array = lodashStable.range(LARGE_ARRAY_SIZE), + predicate = function(value) { values.push(value); return isEven(value); }, + values = [], + actual = _(array).takeRight(2).takeRight().value(); + + assert.deepEqual(actual, takeRight(takeRight(array))); + + actual = _(array).filter(predicate).takeRight(2).takeRight().value(); + assert.deepEqual(values, array); + assert.deepEqual(actual, takeRight(takeRight(_.filter(array, predicate), 2))); + + actual = _(array).takeRight(6).take(4).takeRight(2).take().value(); + assert.deepEqual(actual, _.take(takeRight(_.take(takeRight(array, 6), 4), 2))); + + values = []; + + actual = _(array).filter(predicate).takeRight(6).take(4).takeRight(2).take().value(); + assert.deepEqual(values, array); + assert.deepEqual(actual, _.take(takeRight(_.take(takeRight(_.filter(array, predicate), 6), 4), 2))); + }); +}); diff --git a/test/takeRightWhile.js b/test/takeRightWhile.js new file mode 100644 index 000000000..d080e0c60 --- /dev/null +++ b/test/takeRightWhile.js @@ -0,0 +1,96 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice, LARGE_ARRAY_SIZE } from './utils.js'; +import takeRightWhile from '../takeRightWhile.js'; + +describe('takeRightWhile', function() { + var array = [1, 2, 3, 4]; + + var objects = [ + { 'a': 0, 'b': 0 }, + { 'a': 1, 'b': 1 }, + { 'a': 2, 'b': 2 } + ]; + + it('should take elements while `predicate` returns truthy', function() { + var actual = takeRightWhile(array, function(n) { + return n > 2; + }); + + assert.deepStrictEqual(actual, [3, 4]); + }); + + it('should provide correct `predicate` arguments', function() { + var args; + + takeRightWhile(array, function() { + args = slice.call(arguments); + }); + + assert.deepStrictEqual(args, [4, 3, array]); + }); + + it('should work with `_.matches` shorthands', function() { + assert.deepStrictEqual(takeRightWhile(objects, { 'b': 2 }), objects.slice(2)); + }); + + it('should work with `_.matchesProperty` shorthands', function() { + assert.deepStrictEqual(takeRightWhile(objects, ['b', 2]), objects.slice(2)); + }); + + it('should work with `_.property` shorthands', function() { + assert.deepStrictEqual(takeRightWhile(objects, 'b'), objects.slice(1)); + }); + + it('should work in a lazy sequence', function() { + var array = lodashStable.range(LARGE_ARRAY_SIZE), + predicate = function(n) { return n > 2; }, + expected = takeRightWhile(array, predicate), + wrapped = _(array).takeRightWhile(predicate); + + assert.deepEqual(wrapped.value(), expected); + assert.deepEqual(wrapped.reverse().value(), expected.slice().reverse()); + assert.strictEqual(wrapped.last(), _.last(expected)); + }); + + it('should provide correct `predicate` arguments in a lazy sequence', function() { + var args, + array = lodashStable.range(LARGE_ARRAY_SIZE + 1); + + var expected = [ + square(LARGE_ARRAY_SIZE), + LARGE_ARRAY_SIZE - 1, + lodashStable.map(array.slice(1), square) + ]; + + _(array).slice(1).takeRightWhile(function(value, index, array) { + args = slice.call(arguments); + }).value(); + + assert.deepEqual(args, [LARGE_ARRAY_SIZE, LARGE_ARRAY_SIZE - 1, array.slice(1)]); + + _(array).slice(1).map(square).takeRightWhile(function(value, index, array) { + args = slice.call(arguments); + }).value(); + + assert.deepEqual(args, expected); + + _(array).slice(1).map(square).takeRightWhile(function(value, index) { + args = slice.call(arguments); + }).value(); + + assert.deepEqual(args, expected); + + _(array).slice(1).map(square).takeRightWhile(function(index) { + args = slice.call(arguments); + }).value(); + + assert.deepEqual(args, [square(LARGE_ARRAY_SIZE)]); + + _(array).slice(1).map(square).takeRightWhile(function() { + args = slice.call(arguments); + }).value(); + + assert.deepEqual(args, expected); + }); +}); diff --git a/test/takeWhile.js b/test/takeWhile.js new file mode 100644 index 000000000..43d1b10d9 --- /dev/null +++ b/test/takeWhile.js @@ -0,0 +1,102 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice, LARGE_ARRAY_SIZE, square } from './utils.js'; +import takeWhile from '../takeWhile.js'; + +describe('takeWhile', function() { + var array = [1, 2, 3, 4]; + + var objects = [ + { 'a': 2, 'b': 2 }, + { 'a': 1, 'b': 1 }, + { 'a': 0, 'b': 0 } + ]; + + it('should take elements while `predicate` returns truthy', function() { + var actual = takeWhile(array, function(n) { + return n < 3; + }); + + assert.deepStrictEqual(actual, [1, 2]); + }); + + it('should provide correct `predicate` arguments', function() { + var args; + + takeWhile(array, function() { + args = slice.call(arguments); + }); + + assert.deepStrictEqual(args, [1, 0, array]); + }); + + it('should work with `_.matches` shorthands', function() { + assert.deepStrictEqual(takeWhile(objects, { 'b': 2 }), objects.slice(0, 1)); + }); + + it('should work with `_.matchesProperty` shorthands', function() { + assert.deepStrictEqual(takeWhile(objects, ['b', 2]), objects.slice(0, 1)); + }); + it('should work with `_.property` shorthands', function() { + assert.deepStrictEqual(takeWhile(objects, 'b'), objects.slice(0, 2)); + }); + + it('should work in a lazy sequence', function() { + var array = lodashStable.range(LARGE_ARRAY_SIZE), + predicate = function(n) { return n < 3; }, + expected = takeWhile(array, predicate), + wrapped = _(array).takeWhile(predicate); + + assert.deepEqual(wrapped.value(), expected); + assert.deepEqual(wrapped.reverse().value(), expected.slice().reverse()); + assert.strictEqual(wrapped.last(), _.last(expected)); + }); + + it('should work in a lazy sequence with `take`', function() { + var array = lodashStable.range(LARGE_ARRAY_SIZE); + + var actual = _(array) + .takeWhile(function(n) { return n < 4; }) + .take(2) + .takeWhile(function(n) { return n == 0; }) + .value(); + + assert.deepEqual(actual, [0]); + }); + + it('should provide correct `predicate` arguments in a lazy sequence', function() { + var args, + array = lodashStable.range(LARGE_ARRAY_SIZE + 1), + expected = [1, 0, lodashStable.map(array.slice(1), square)]; + + _(array).slice(1).takeWhile(function(value, index, array) { + args = slice.call(arguments); + }).value(); + + assert.deepEqual(args, [1, 0, array.slice(1)]); + + _(array).slice(1).map(square).takeWhile(function(value, index, array) { + args = slice.call(arguments); + }).value(); + + assert.deepEqual(args, expected); + + _(array).slice(1).map(square).takeWhile(function(value, index) { + args = slice.call(arguments); + }).value(); + + assert.deepEqual(args, expected); + + _(array).slice(1).map(square).takeWhile(function(value) { + args = slice.call(arguments); + }).value(); + + assert.deepEqual(args, [1]); + + _(array).slice(1).map(square).takeWhile(function() { + args = slice.call(arguments); + }).value(); + + assert.deepEqual(args, expected); + }); +}); diff --git a/test/tap.js b/test/tap.js new file mode 100644 index 000000000..d50fa573c --- /dev/null +++ b/test/tap.js @@ -0,0 +1,30 @@ +import assert from 'assert'; + +describe('tap', function() { + it('should intercept and return the given value', function() { + var intercepted, + array = [1, 2, 3]; + + var actual = _.tap(array, function(value) { + intercepted = value; + }); + + assert.strictEqual(actual, array); + assert.strictEqual(intercepted, array); + }); + + it('should intercept unwrapped values and return wrapped values when chaining', function() { + var intercepted, + array = [1, 2, 3]; + + var wrapped = _(array).tap(function(value) { + intercepted = value; + value.pop(); + }); + + assert.ok(wrapped instanceof _); + + wrapped.value(); + assert.strictEqual(intercepted, array); + }); +}); diff --git a/test/template.js b/test/template.js new file mode 100644 index 000000000..01308dbce --- /dev/null +++ b/test/template.js @@ -0,0 +1,451 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { numberTag, stubString, stubTrue, stubFalse } from './utils.js'; +import template from '../template.js'; +import templateSettings from '../templateSettings.js'; + +describe('template', function() { + it('should escape values in "escape" delimiters', function() { + var strings = ['
<%- value %>
', '<%-value%>
', '<%-\nvalue\n%>
'], + expected = lodashStable.map(strings, lodashStable.constant('&<>"'/
')), + data = { 'value': '&<>"\'/' }; + + var actual = lodashStable.map(strings, function(string) { + return template(string)(data); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should not reference `_.escape` when "escape" delimiters are not used', function() { + var compiled = template('<%= typeof __e %>'); + assert.strictEqual(compiled({}), 'undefined'); + }); + + it('should evaluate JavaScript in "evaluate" delimiters', function() { + var compiled = template( + '<%= value %>
' + ); + + assert.strictEqual(compiled({ 'value': 3 }), '6
'); + }); + + it('should tokenize delimiters', function() { + var compiled = template(''), + data = { 'type': 1 }; + + assert.strictEqual(compiled(data), ''); + }); + + it('should evaluate delimiters once', function() { + var actual = [], + compiled = template('<%= func("a") %><%- func("b") %><% func("c") %>'), + data = { 'func': function(value) { actual.push(value); } }; + + compiled(data); + assert.deepStrictEqual(actual, ['a', 'b', 'c']); + }); + + it('should match delimiters before escaping text', function() { + var compiled = template('<<\n a \n>>', { 'evaluate': /<<(.*?)>>/g }); + assert.strictEqual(compiled(), '<<\n a \n>>'); + }); + + it('should resolve nullish values to an empty string', function() { + var compiled = template('<%= a %><%- a %>'), + data = { 'a': null }; + + assert.strictEqual(compiled(data), ''); + + data = { 'a': undefined }; + assert.strictEqual(compiled(data), ''); + + data = { 'a': {} }; + compiled = template('<%= a.b %><%- a.b %>'); + assert.strictEqual(compiled(data), ''); + }); + + it('should return an empty string for empty values', function() { + var values = [, null, undefined, ''], + expected = lodashStable.map(values, stubString), + data = { 'a': 1 }; + + var actual = lodashStable.map(values, function(value, index) { + var compiled = index ? template(value) : template(); + return compiled(data); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should parse delimiters without newlines', function() { + var expected = '<<\nprint("" + (value ? "yes" : "no") + "
")\n>>', + compiled = template(expected, { 'evaluate': /<<(.+?)>>/g }), + data = { 'value': true }; + + assert.strictEqual(compiled(data), expected); + }); + + it('should support recursive calls', function() { + var compiled = template('<%= a %><% a = _.template(c)(obj) %><%= a %>'), + data = { 'a': 'A', 'b': 'B', 'c': '<%= b %>' }; + + assert.strictEqual(compiled(data), 'AB'); + }); + + it('should coerce `text` to a string', function() { + var object = { 'toString': lodashStable.constant('<%= a %>') }, + data = { 'a': 1 }; + + assert.strictEqual(template(object)(data), '1'); + }); + + it('should not modify the `options` object', function() { + var options = {}; + template('', options); + assert.deepStrictEqual(options, {}); + }); + + it('should not modify `_.templateSettings` when `options` are given', function() { + var data = { 'a': 1 }; + + assert.ok(!('a' in templateSettings)); + template('', {}, data); + assert.ok(!('a' in templateSettings)); + + delete templateSettings.a; + }); + + it('should not error for non-object `data` and `options` values', function() { + template('')(1); + assert.ok(true, '`data` value'); + + template('', 1)(1); + assert.ok(true, '`options` value'); + }); + + it('should expose the source on compiled templates', function() { + var compiled = template('x'), + values = [String(compiled), compiled.source], + expected = lodashStable.map(values, stubTrue); + + var actual = lodashStable.map(values, function(value) { + return lodashStable.includes(value, '__p'); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should expose the source on SyntaxErrors', function() { + try { + template('<% if x %>'); + } catch (e) { + var source = e.source; + } + assert.ok(lodashStable.includes(source, '__p')); + }); + + it('should not include sourceURLs in the source', function() { + var options = { 'sourceURL': '/a/b/c' }, + compiled = template('x', options), + values = [compiled.source, undefined]; + + try { + template('<% if x %>', options); + } catch (e) { + values[1] = e.source; + } + var expected = lodashStable.map(values, stubFalse); + + var actual = lodashStable.map(values, function(value) { + return lodashStable.includes(value, 'sourceURL'); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var array = ['<%= a %>', '<%- b %>', '<% print(c) %>'], + compiles = lodashStable.map(array, template), + data = { 'a': 'one', 'b': '"two"', 'c': 'three' }; + + var actual = lodashStable.map(compiles, function(compiled) { + return compiled(data); + }); + + assert.deepStrictEqual(actual, ['one', '"two"', 'three']); + }); +}); diff --git a/test/throttle.js b/test/throttle.js new file mode 100644 index 000000000..c495b00eb --- /dev/null +++ b/test/throttle.js @@ -0,0 +1,227 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { identity, isModularize, argv, isPhantom } from './utils.js'; +import throttle from '../throttle.js'; +import runInContext from '../runInContext.js'; + +describe('throttle', function() { + it('should throttle a function', function(done) { + var callCount = 0, + throttled = throttle(function() { callCount++; }, 32); + + throttled(); + throttled(); + throttled(); + + var lastCount = callCount; + assert.ok(callCount); + + setTimeout(function() { + assert.ok(callCount > lastCount); + done(); + }, 64); + }); + + it('subsequent calls should return the result of the first call', function(done) { + var throttled = throttle(identity, 32), + results = [throttled('a'), throttled('b')]; + + assert.deepStrictEqual(results, ['a', 'a']); + + setTimeout(function() { + var results = [throttled('c'), throttled('d')]; + assert.notStrictEqual(results[0], 'a'); + assert.notStrictEqual(results[0], undefined); + + assert.notStrictEqual(results[1], 'd'); + assert.notStrictEqual(results[1], undefined); + done(); + }, 64); + }); + + it('should clear timeout when `func` is called', function(done) { + if (!isModularize) { + var callCount = 0, + dateCount = 0; + + var lodash = runInContext({ + 'Date': { + 'now': function() { + return ++dateCount == 5 ? Infinity : +new Date; + } + } + }); + + var throttled = lodash.throttle(function() { callCount++; }, 32); + + throttled(); + throttled(); + + setTimeout(function() { + assert.strictEqual(callCount, 2); + done(); + }, 64); + } + else { + done(); + } + }); + + it('should not trigger a trailing call when invoked once', function(done) { + var callCount = 0, + throttled = throttle(function() { callCount++; }, 32); + + throttled(); + assert.strictEqual(callCount, 1); + + setTimeout(function() { + assert.strictEqual(callCount, 1); + done(); + }, 64); + }); + + lodashStable.times(2, function(index) { + it('should trigger a call when invoked repeatedly' + (index ? ' and `leading` is `false`' : ''), function(done) { + var callCount = 0, + limit = (argv || isPhantom) ? 1000 : 320, + options = index ? { 'leading': false } : {}, + throttled = throttle(function() { callCount++; }, 32, options); + + var start = +new Date; + while ((new Date - start) < limit) { + throttled(); + } + var actual = callCount > 1; + setTimeout(function() { + assert.ok(actual); + done(); + }, 1); + }); + }); + + it('should trigger a second throttled call as soon as possible', function(done) { + var callCount = 0; + + var throttled = throttle(function() { + callCount++; + }, 128, { 'leading': false }); + + throttled(); + + setTimeout(function() { + assert.strictEqual(callCount, 1); + throttled(); + }, 192); + + setTimeout(function() { + assert.strictEqual(callCount, 1); + }, 254); + + setTimeout(function() { + assert.strictEqual(callCount, 2); + done(); + }, 384); + }); + + it('should apply default options', function(done) { + var callCount = 0, + throttled = throttle(function() { callCount++; }, 32, {}); + + throttled(); + throttled(); + assert.strictEqual(callCount, 1); + + setTimeout(function() { + assert.strictEqual(callCount, 2); + done(); + }, 128); + }); + + it('should support a `leading` option', function() { + var withLeading = throttle(identity, 32, { 'leading': true }); + assert.strictEqual(withLeading('a'), 'a'); + + var withoutLeading = throttle(identity, 32, { 'leading': false }); + assert.strictEqual(withoutLeading('a'), undefined); + }); + + it('should support a `trailing` option', function(done) { + var withCount = 0, + withoutCount = 0; + + var withTrailing = throttle(function(value) { + withCount++; + return value; + }, 64, { 'trailing': true }); + + var withoutTrailing = throttle(function(value) { + withoutCount++; + return value; + }, 64, { 'trailing': false }); + + assert.strictEqual(withTrailing('a'), 'a'); + assert.strictEqual(withTrailing('b'), 'a'); + + assert.strictEqual(withoutTrailing('a'), 'a'); + assert.strictEqual(withoutTrailing('b'), 'a'); + + setTimeout(function() { + assert.strictEqual(withCount, 2); + assert.strictEqual(withoutCount, 1); + done(); + }, 256); + }); + + it('should not update `lastCalled`, at the end of the timeout, when `trailing` is `false`', function(done) { + var callCount = 0; + + var throttled = throttle(function() { + callCount++; + }, 64, { 'trailing': false }); + + throttled(); + throttled(); + + setTimeout(function() { + throttled(); + throttled(); + }, 96); + + setTimeout(function() { + assert.ok(callCount > 1); + done(); + }, 192); + }); + + it('should work with a system time of `0`', function(done) { + if (!isModularize) { + var callCount = 0, + dateCount = 0; + + var lodash = runInContext({ + 'Date': { + 'now': function() { + return ++dateCount < 4 ? 0 : +new Date; + } + } + }); + + var throttled = lodash.throttle(function(value) { + callCount++; + return value; + }, 32); + + var results = [throttled('a'), throttled('b'), throttled('c')]; + assert.deepStrictEqual(results, ['a', 'a', 'a']); + assert.strictEqual(callCount, 1); + + setTimeout(function() { + assert.strictEqual(callCount, 2); + done(); + }, 64); + } + else { + done(); + } + }); +}); diff --git a/test/times.js b/test/times.js new file mode 100644 index 000000000..b9873f204 --- /dev/null +++ b/test/times.js @@ -0,0 +1,62 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice, doubled, falsey, stubArray } from './utils.js'; +import times from '../times.js'; +import identity from '../identity.js'; + +describe('times', function() { + it('should coerce non-finite `n` values to `0`', function() { + lodashStable.each([-Infinity, NaN, Infinity], function(n) { + assert.deepStrictEqual(times(n), []); + }); + }); + + it('should coerce `n` to an integer', function() { + var actual = times(2.6, identity); + assert.deepStrictEqual(actual, [0, 1]); + }); + + it('should provide correct `iteratee` arguments', function() { + var args; + + times(1, function() { + args || (args = slice.call(arguments)); + }); + + assert.deepStrictEqual(args, [0]); + }); + + it('should use `_.identity` when `iteratee` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, lodashStable.constant([0, 1, 2])); + + var actual = lodashStable.map(values, function(value, index) { + return index ? times(3, value) : times(3); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return an array of the results of each `iteratee` execution', function() { + assert.deepStrictEqual(times(3, doubled), [0, 2, 4]); + }); + + it('should return an empty array for falsey and negative `n` values', function() { + var values = falsey.concat(-1, -Infinity), + expected = lodashStable.map(values, stubArray); + + var actual = lodashStable.map(values, function(value, index) { + return index ? times(value) : times(); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return an unwrapped value when implicitly chaining', function() { + assert.deepStrictEqual(_(3).times(), [0, 1, 2]); + }); + + it('should return a wrapped value when explicitly chaining', function() { + assert.ok(_(3).chain().times() instanceof _); + }); +}); diff --git a/test/toArray.test.js b/test/toArray.test.js new file mode 100644 index 000000000..7baaf3c41 --- /dev/null +++ b/test/toArray.test.js @@ -0,0 +1,48 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { arrayProto, LARGE_ARRAY_SIZE } from './utils.js'; +import toArray from '../toArray.js'; + +describe('toArray', function() { + it('should convert objects to arrays', function() { + assert.deepStrictEqual(toArray({ 'a': 1, 'b': 2 }), [1, 2]); + }); + + it('should convert iterables to arrays', function() { + if (Symbol && Symbol.iterator) { + var object = { '0': 'a', 'length': 1 }; + object[Symbol.iterator] = arrayProto[Symbol.iterator]; + + assert.deepStrictEqual(toArray(object), ['a']); + } + }); + + it('should convert maps to arrays', function() { + if (Map) { + var map = new Map; + map.set('a', 1); + map.set('b', 2); + assert.deepStrictEqual(toArray(map), [['a', 1], ['b', 2]]); + } + }); + + it('should convert strings to arrays', function() { + assert.deepStrictEqual(toArray(''), []); + assert.deepStrictEqual(toArray('ab'), ['a', 'b']); + assert.deepStrictEqual(toArray(Object('ab')), ['a', 'b']); + }); + + it('should work in a lazy sequence', function() { + var array = lodashStable.range(LARGE_ARRAY_SIZE + 1); + + var object = lodashStable.zipObject(lodashStable.times(LARGE_ARRAY_SIZE, function(index) { + return ['key' + index, index]; + })); + + var actual = _(array).slice(1).map(String).toArray().value(); + assert.deepEqual(actual, lodashStable.map(array.slice(1), String)); + + actual = _(object).toArray().slice(1).map(String).value(); + assert.deepEqual(actual, _.map(toArray(object).slice(1), String)); + }); +}); diff --git a/test/toInteger-methods.js b/test/toInteger-methods.js new file mode 100644 index 000000000..c9d0c0349 --- /dev/null +++ b/test/toInteger-methods.js @@ -0,0 +1,25 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, MAX_SAFE_INTEGER, MAX_INTEGER } from './utils.js'; + +describe('toInteger methods', function() { + lodashStable.each(['toInteger', 'toSafeInteger'], function(methodName) { + var func = _[methodName], + isSafe = methodName == 'toSafeInteger'; + + it('`_.' + methodName + '` should convert values to integers', function() { + assert.strictEqual(func(-5.6), -5); + assert.strictEqual(func('5.6'), 5); + assert.strictEqual(func(), 0); + assert.strictEqual(func(NaN), 0); + + var expected = isSafe ? MAX_SAFE_INTEGER : MAX_INTEGER; + assert.strictEqual(func(Infinity), expected); + assert.strictEqual(func(-Infinity), -expected); + }); + + it('`_.' + methodName + '` should support `value` of `-0`', function() { + assert.strictEqual(1 / func(-0), -Infinity); + }); + }); +}); diff --git a/test/toLength.test.js b/test/toLength.test.js new file mode 100644 index 000000000..3abbdd227 --- /dev/null +++ b/test/toLength.test.js @@ -0,0 +1,22 @@ +import assert from 'assert'; +import { MAX_INTEGER, MAX_ARRAY_LENGTH } from './utils.js'; +import toLength from '../toLength.js'; + +describe('toLength', function() { + it('should return a valid length', function() { + assert.strictEqual(toLength(-1), 0); + assert.strictEqual(toLength('1'), 1); + assert.strictEqual(toLength(1.1), 1); + assert.strictEqual(toLength(MAX_INTEGER), MAX_ARRAY_LENGTH); + }); + + it('should return `value` if a valid length', function() { + assert.strictEqual(toLength(0), 0); + assert.strictEqual(toLength(3), 3); + assert.strictEqual(toLength(MAX_ARRAY_LENGTH), MAX_ARRAY_LENGTH); + }); + + it('should convert `-0` to `0`', function() { + assert.strictEqual(1 / toLength(-0), Infinity); + }); +}); diff --git a/test/toLower.js b/test/toLower.js new file mode 100644 index 000000000..03e7117d9 --- /dev/null +++ b/test/toLower.js @@ -0,0 +1,10 @@ +import assert from 'assert'; +import toLower from '../toLower.js'; + +describe('toLower', function() { + it('should convert whole string to lower case', function() { + assert.deepStrictEqual(toLower('--Foo-Bar--'), '--foo-bar--'); + assert.deepStrictEqual(toLower('fooBar'), 'foobar'); + assert.deepStrictEqual(toLower('__FOO_BAR__'), '__foo_bar__'); + }); +}); diff --git a/test/toPairs-methods.js b/test/toPairs-methods.js new file mode 100644 index 000000000..34f0ddba2 --- /dev/null +++ b/test/toPairs-methods.js @@ -0,0 +1,61 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _ } from './utils.js'; + +describe('toPairs methods', function() { + lodashStable.each(['toPairs', 'toPairsIn'], function(methodName) { + var func = _[methodName], + isToPairs = methodName == 'toPairs'; + + it('`_.' + methodName + '` should create an array of string keyed-value pairs', function() { + var object = { 'a': 1, 'b': 2 }, + actual = lodashStable.sortBy(func(object), 0); + + assert.deepStrictEqual(actual, [['a', 1], ['b', 2]]); + }); + + it('`_.' + methodName + '` should ' + (isToPairs ? 'not ' : '') + 'include inherited string keyed property values', function() { + function Foo() { + this.a = 1; + } + Foo.prototype.b = 2; + + var expected = isToPairs ? [['a', 1]] : [['a', 1], ['b', 2]], + actual = lodashStable.sortBy(func(new Foo), 0); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should convert objects with a `length` property', function() { + var object = { '0': 'a', '1': 'b', 'length': 2 }, + actual = lodashStable.sortBy(func(object), 0); + + assert.deepStrictEqual(actual, [['0', 'a'], ['1', 'b'], ['length', 2]]); + }); + + it('`_.' + methodName + '` should convert maps', function() { + if (Map) { + var map = new Map; + map.set('a', 1); + map.set('b', 2); + assert.deepStrictEqual(func(map), [['a', 1], ['b', 2]]); + } + }); + + it('`_.' + methodName + '` should convert sets', function() { + if (Set) { + var set = new Set; + set.add(1); + set.add(2); + assert.deepStrictEqual(func(set), [[1, 1], [2, 2]]); + } + }); + + it('`_.' + methodName + '` should convert strings', function() { + lodashStable.each(['xo', Object('xo')], function(string) { + var actual = lodashStable.sortBy(func(string), 0); + assert.deepStrictEqual(actual, [['0', 'x'], ['1', 'o']]); + }); + }); + }); +}); diff --git a/test/toPairs.js b/test/toPairs.js new file mode 100644 index 000000000..8605aa7bb --- /dev/null +++ b/test/toPairs.js @@ -0,0 +1,9 @@ +import assert from 'assert'; +import entries from '../entries.js'; +import toPairs from '../toPairs.js'; + +describe('toPairs', function() { + it('should be aliased', function() { + assert.strictEqual(entries, toPairs); + }); +}); diff --git a/test/toPairsIn.js b/test/toPairsIn.js new file mode 100644 index 000000000..f35de06d9 --- /dev/null +++ b/test/toPairsIn.js @@ -0,0 +1,9 @@ +import assert from 'assert'; +import entriesIn from '../entriesIn.js'; +import toPairsIn from '../toPairsIn.js'; + +describe('toPairsIn', function() { + it('should be aliased', function() { + assert.strictEqual(entriesIn, toPairsIn); + }); +}); diff --git a/test/toPath.js b/test/toPath.js new file mode 100644 index 000000000..361de4657 --- /dev/null +++ b/test/toPath.js @@ -0,0 +1,66 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { symbol } from './utils.js'; +import toPath from '../toPath.js'; + +describe('toPath', function() { + it('should convert a string to a path', function() { + assert.deepStrictEqual(toPath('a.b.c'), ['a', 'b', 'c']); + assert.deepStrictEqual(toPath('a[0].b.c'), ['a', '0', 'b', 'c']); + }); + + it('should coerce array elements to strings', function() { + var array = ['a', 'b', 'c']; + + lodashStable.each([array, lodashStable.map(array, Object)], function(value) { + var actual = toPath(value); + assert.deepStrictEqual(actual, array); + assert.notStrictEqual(actual, array); + }); + }); + + it('should return new path array', function() { + assert.notStrictEqual(toPath('a.b.c'), toPath('a.b.c')); + }); + + it('should not coerce symbols to strings', function() { + if (Symbol) { + var object = Object(symbol); + lodashStable.each([symbol, object, [symbol], [object]], function(value) { + var actual = toPath(value); + assert.ok(lodashStable.isSymbol(actual[0])); + }); + } + }); + + it('should handle complex paths', function() { + var actual = toPath('a[-1.23]["[\\"b\\"]"].c[\'[\\\'d\\\']\'][\ne\n][f].g'); + assert.deepStrictEqual(actual, ['a', '-1.23', '["b"]', 'c', "['d']", '\ne\n', 'f', 'g']); + }); + + it('should handle consecutive empty brackets and dots', function() { + var expected = ['', 'a']; + assert.deepStrictEqual(toPath('.a'), expected); + assert.deepStrictEqual(toPath('[].a'), expected); + + expected = ['', '', 'a']; + assert.deepStrictEqual(toPath('..a'), expected); + assert.deepStrictEqual(toPath('[][].a'), expected); + + expected = ['a', '', 'b']; + assert.deepStrictEqual(toPath('a..b'), expected); + assert.deepStrictEqual(toPath('a[].b'), expected); + + expected = ['a', '', '', 'b']; + assert.deepStrictEqual(toPath('a...b'), expected); + assert.deepStrictEqual(toPath('a[][].b'), expected); + + expected = ['a', '']; + assert.deepStrictEqual(toPath('a.'), expected); + assert.deepStrictEqual(toPath('a[]'), expected); + + expected = ['a', '', '']; + assert.deepStrictEqual(toPath('a..'), expected); + assert.deepStrictEqual(toPath('a[][]'), expected); + }); +}); diff --git a/test/toPlainObject.js b/test/toPlainObject.js new file mode 100644 index 000000000..78046d28f --- /dev/null +++ b/test/toPlainObject.js @@ -0,0 +1,30 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { args } from './utils.js'; +import toPlainObject from '../toPlainObject.js'; + +describe('toPlainObject', function() { + it('should flatten inherited string keyed properties', function() { + function Foo() { + this.b = 2; + } + Foo.prototype.c = 3; + + var actual = lodashStable.assign({ 'a': 1 }, toPlainObject(new Foo)); + assert.deepStrictEqual(actual, { 'a': 1, 'b': 2, 'c': 3 }); + }); + + it('should convert `arguments` objects to plain objects', function() { + var actual = toPlainObject(args), + expected = { '0': 1, '1': 2, '2': 3 }; + + assert.deepStrictEqual(actual, expected); + }); + + it('should convert arrays to plain objects', function() { + var actual = toPlainObject(['a', 'b', 'c']), + expected = { '0': 'a', '1': 'b', '2': 'c' }; + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/toString.test.js b/test/toString.test.js new file mode 100644 index 000000000..220b1bd14 --- /dev/null +++ b/test/toString.test.js @@ -0,0 +1,55 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { stubString, symbol } from './utils.js'; +import toString from '../toString.js'; + +describe('toString', function() { + it('should treat nullish values as empty strings', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, stubString); + + var actual = lodashStable.map(values, function(value, index) { + return index ? toString(value) : toString(); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should preserve the sign of `0`', function() { + var values = [-0, Object(-0), 0, Object(0)], + expected = ['-0', '-0', '0', '0'], + actual = lodashStable.map(values, toString); + + assert.deepStrictEqual(actual, expected); + }); + + it('should preserve the sign of `0` in an array', function() { + var values = [-0, Object(-0), 0, Object(0)]; + assert.deepStrictEqual(toString(values), '-0,-0,0,0'); + }); + + it('should not error on symbols', function() { + if (Symbol) { + try { + assert.strictEqual(toString(symbol), 'Symbol(a)'); + } catch (e) { + assert.ok(false, e.message); + } + } + }); + + it('should not error on an array of symbols', function() { + if (Symbol) { + try { + assert.strictEqual(toString([symbol]), 'Symbol(a)'); + } catch (e) { + assert.ok(false, e.message); + } + } + }); + + it('should return the `toString` result of the wrapped value', function() { + var wrapped = _([1, 2, 3]); + assert.strictEqual(wrapped.toString(), '1,2,3'); + }); +}); diff --git a/test/toUpper.js b/test/toUpper.js new file mode 100644 index 000000000..bb6b83729 --- /dev/null +++ b/test/toUpper.js @@ -0,0 +1,10 @@ +import assert from 'assert'; +import toUpper from '../toUpper.js'; + +describe('toUpper', function() { + it('should convert whole string to upper case', function() { + assert.deepStrictEqual(toUpper('--Foo-Bar'), '--FOO-BAR'); + assert.deepStrictEqual(toUpper('fooBar'), 'FOOBAR'); + assert.deepStrictEqual(toUpper('__FOO_BAR__'), '__FOO_BAR__'); + }); +}); diff --git a/test/transform.js b/test/transform.js new file mode 100644 index 000000000..c8121183a --- /dev/null +++ b/test/transform.js @@ -0,0 +1,205 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; + +import { + stubTrue, + square, + typedArrays, + noop, + stubObject, + stubFalse, + falsey, + slice, + realm, +} from './utils.js'; + +import transform from '../transform.js'; + +describe('transform', function() { + function Foo() { + this.a = 1; + this.b = 2; + this.c = 3; + } + + it('should create an object with the same `[[Prototype]]` as `object` when `accumulator` is nullish', function() { + var accumulators = [, null, undefined], + object = new Foo, + expected = lodashStable.map(accumulators, stubTrue); + + var iteratee = function(result, value, key) { + result[key] = square(value); + }; + + var mapper = function(accumulator, index) { + return index ? transform(object, iteratee, accumulator) : transform(object, iteratee); + }; + + var results = lodashStable.map(accumulators, mapper); + + var actual = lodashStable.map(results, function(result) { + return result instanceof Foo; + }); + + assert.deepStrictEqual(actual, expected); + + expected = lodashStable.map(accumulators, lodashStable.constant({ 'a': 1, 'b': 4, 'c': 9 })); + actual = lodashStable.map(results, lodashStable.toPlainObject); + + assert.deepStrictEqual(actual, expected); + + object = { 'a': 1, 'b': 2, 'c': 3 }; + actual = lodashStable.map(accumulators, mapper); + + assert.deepStrictEqual(actual, expected); + + object = [1, 2, 3]; + expected = lodashStable.map(accumulators, lodashStable.constant([1, 4, 9])); + actual = lodashStable.map(accumulators, mapper); + + assert.deepStrictEqual(actual, expected); + }); + + it('should create regular arrays from typed arrays', function() { + var expected = lodashStable.map(typedArrays, stubTrue); + + var actual = lodashStable.map(typedArrays, function(type) { + var Ctor = root[type], + array = Ctor ? new Ctor(new ArrayBuffer(24)) : []; + + return lodashStable.isArray(transform(array, noop)); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should support an `accumulator` value', function() { + var values = [new Foo, [1, 2, 3], { 'a': 1, 'b': 2, 'c': 3 }], + expected = lodashStable.map(values, lodashStable.constant([1, 4, 9])); + + var actual = lodashStable.map(values, function(value) { + return transform(value, function(result, value) { + result.push(square(value)); + }, []); + }); + + assert.deepStrictEqual(actual, expected); + + var object = { 'a': 1, 'b': 4, 'c': 9 }, + expected = [object, { '0': 1, '1': 4, '2': 9 }, object]; + + actual = lodashStable.map(values, function(value) { + return transform(value, function(result, value, key) { + result[key] = square(value); + }, {}); + }); + + assert.deepStrictEqual(actual, expected); + + lodashStable.each([[], {}], function(accumulator) { + var actual = lodashStable.map(values, function(value) { + return transform(value, noop, accumulator); + }); + + assert.ok(lodashStable.every(actual, function(result) { + return result === accumulator; + })); + + assert.strictEqual(transform(null, null, accumulator), accumulator); + }); + }); + + it('should treat sparse arrays as dense', function() { + var actual = transform(Array(1), function(result, value, index) { + result[index] = String(value); + }); + + assert.deepStrictEqual(actual, ['undefined']); + }); + + it('should work without an `iteratee`', function() { + assert.ok(transform(new Foo) instanceof Foo); + }); + + it('should ensure `object` is an object before using its `[[Prototype]]`', function() { + var Ctors = [Boolean, Boolean, Number, Number, Number, String, String], + values = [false, true, 0, 1, NaN, '', 'a'], + expected = lodashStable.map(values, stubObject); + + var results = lodashStable.map(values, function(value) { + return transform(value); + }); + + assert.deepStrictEqual(results, expected); + + expected = lodashStable.map(values, stubFalse); + + var actual = lodashStable.map(results, function(value, index) { + return value instanceof Ctors[index]; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should ensure `object` constructor is a function before using its `[[Prototype]]`', function() { + Foo.prototype.constructor = null; + assert.ok(!(transform(new Foo) instanceof Foo)); + Foo.prototype.constructor = Foo; + }); + + it('should create an empty object when given a falsey `object`', function() { + var expected = lodashStable.map(falsey, stubObject); + + var actual = lodashStable.map(falsey, function(object, index) { + return index ? transform(object) : transform(); + }); + + assert.deepStrictEqual(actual, expected); + }); + + lodashStable.each({ + 'array': [1, 2, 3], + 'object': { 'a': 1, 'b': 2, 'c': 3 } + }, + function(object, key) { + it('should provide correct `iteratee` arguments when transforming an ' + key, function() { + var args; + + transform(object, function() { + args || (args = slice.call(arguments)); + }); + + var first = args[0]; + if (key == 'array') { + assert.ok(first !== object && lodashStable.isArray(first)); + assert.deepStrictEqual(args, [first, 1, 0, object]); + } else { + assert.ok(first !== object && lodashStable.isPlainObject(first)); + assert.deepStrictEqual(args, [first, 1, 'a', object]); + } + }); + }); + + it('should create an object from the same realm as `object`', function() { + var objects = lodashStable.filter(realm, function(value) { + return lodashStable.isObject(value) && !lodashStable.isElement(value); + }); + + var expected = lodashStable.map(objects, stubTrue); + + var actual = lodashStable.map(objects, function(object) { + var Ctor = object.constructor, + result = transform(object); + + if (result === object) { + return false; + } + if (lodashStable.isTypedArray(object)) { + return result instanceof Array; + } + return result instanceof Ctor || !(new Ctor instanceof Ctor); + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/trim-methods.js b/test/trim-methods.js new file mode 100644 index 000000000..c3f8a845c --- /dev/null +++ b/test/trim-methods.js @@ -0,0 +1,83 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, whitespace } from './utils.js'; + +describe('trim methods', function() { + lodashStable.each(['trim', 'trimStart', 'trimEnd'], function(methodName, index) { + var func = _[methodName], + parts = []; + + if (index != 2) { + parts.push('leading'); + } + if (index != 1) { + parts.push('trailing'); + } + parts = parts.join(' and '); + + it('`_.' + methodName + '` should remove ' + parts + ' whitespace', function() { + var string = whitespace + 'a b c' + whitespace, + expected = (index == 2 ? whitespace : '') + 'a b c' + (index == 1 ? whitespace : ''); + + assert.strictEqual(func(string), expected); + }); + + it('`_.' + methodName + '` should coerce `string` to a string', function() { + var object = { 'toString': lodashStable.constant(whitespace + 'a b c' + whitespace) }, + expected = (index == 2 ? whitespace : '') + 'a b c' + (index == 1 ? whitespace : ''); + + assert.strictEqual(func(object), expected); + }); + + it('`_.' + methodName + '` should remove ' + parts + ' `chars`', function() { + var string = '-_-a-b-c-_-', + expected = (index == 2 ? '-_-' : '') + 'a-b-c' + (index == 1 ? '-_-' : ''); + + assert.strictEqual(func(string, '_-'), expected); + }); + + it('`_.' + methodName + '` should coerce `chars` to a string', function() { + var object = { 'toString': lodashStable.constant('_-') }, + string = '-_-a-b-c-_-', + expected = (index == 2 ? '-_-' : '') + 'a-b-c' + (index == 1 ? '-_-' : ''); + + assert.strictEqual(func(string, object), expected); + }); + + it('`_.' + methodName + '` should return an empty string for empty values and `chars`', function() { + lodashStable.each([null, '_-'], function(chars) { + assert.strictEqual(func(null, chars), ''); + assert.strictEqual(func(undefined, chars), ''); + assert.strictEqual(func('', chars), ''); + }); + }); + + it('`_.' + methodName + '` should work with `undefined` or empty string values for `chars`', function() { + var string = whitespace + 'a b c' + whitespace, + expected = (index == 2 ? whitespace : '') + 'a b c' + (index == 1 ? whitespace : ''); + + assert.strictEqual(func(string, undefined), expected); + assert.strictEqual(func(string, ''), string); + }); + + it('`_.' + methodName + '` should work as an iteratee for methods like `_.map`', function() { + var string = Object(whitespace + 'a b c' + whitespace), + trimmed = (index == 2 ? whitespace : '') + 'a b c' + (index == 1 ? whitespace : ''), + actual = lodashStable.map([string, string, string], func); + + assert.deepStrictEqual(actual, [trimmed, trimmed, trimmed]); + }); + + it('`_.' + methodName + '` should return an unwrapped value when implicitly chaining', function() { + var string = whitespace + 'a b c' + whitespace, + expected = (index == 2 ? whitespace : '') + 'a b c' + (index == 1 ? whitespace : ''); + + assert.strictEqual(_(string)[methodName](), expected); + }); + + it('`_.' + methodName + '` should return a wrapped value when explicitly chaining', function() { + var string = whitespace + 'a b c' + whitespace; + assert.ok(_(string).chain()[methodName]() instanceof _); + }); + }); +}); diff --git a/test/truncate.js b/test/truncate.js new file mode 100644 index 000000000..240eae0b9 --- /dev/null +++ b/test/truncate.js @@ -0,0 +1,64 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import truncate from '../truncate.js'; + +describe('truncate', function() { + var string = 'hi-diddly-ho there, neighborino'; + + it('should use a default `length` of `30`', function() { + assert.strictEqual(truncate(string), 'hi-diddly-ho there, neighbo...'); + }); + + it('should not truncate if `string` is <= `length`', function() { + assert.strictEqual(truncate(string, { 'length': string.length }), string); + assert.strictEqual(truncate(string, { 'length': string.length + 2 }), string); + }); + + it('should truncate string the given length', function() { + assert.strictEqual(truncate(string, { 'length': 24 }), 'hi-diddly-ho there, n...'); + }); + + it('should support a `omission` option', function() { + assert.strictEqual(truncate(string, { 'omission': ' [...]' }), 'hi-diddly-ho there, neig [...]'); + }); + + it('should coerce nullish `omission` values to strings', function() { + assert.strictEqual(truncate(string, { 'omission': null }), 'hi-diddly-ho there, neighbnull'); + assert.strictEqual(truncate(string, { 'omission': undefined }), 'hi-diddly-ho there, nundefined'); + }); + + it('should support a `length` option', function() { + assert.strictEqual(truncate(string, { 'length': 4 }), 'h...'); + }); + + it('should support a `separator` option', function() { + assert.strictEqual(truncate(string, { 'length': 24, 'separator': ' ' }), 'hi-diddly-ho there,...'); + assert.strictEqual(truncate(string, { 'length': 24, 'separator': /,? +/ }), 'hi-diddly-ho there...'); + assert.strictEqual(truncate(string, { 'length': 24, 'separator': /,? +/g }), 'hi-diddly-ho there...'); + }); + + it('should treat negative `length` as `0`', function() { + lodashStable.each([0, -2], function(length) { + assert.strictEqual(truncate(string, { 'length': length }), '...'); + }); + }); + + it('should coerce `length` to an integer', function() { + lodashStable.each(['', NaN, 4.6, '4'], function(length, index) { + var actual = index > 1 ? 'h...' : '...'; + assert.strictEqual(truncate(string, { 'length': { 'valueOf': lodashStable.constant(length) } }), actual); + }); + }); + + it('should coerce `string` to a string', function() { + assert.strictEqual(truncate(Object(string), { 'length': 4 }), 'h...'); + assert.strictEqual(truncate({ 'toString': lodashStable.constant(string) }, { 'length': 5 }), 'hi...'); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var actual = lodashStable.map([string, string, string], truncate), + truncated = 'hi-diddly-ho there, neighbo...'; + + assert.deepStrictEqual(actual, [truncated, truncated, truncated]); + }); +}); diff --git a/test/unary.js b/test/unary.js new file mode 100644 index 000000000..333c89091 --- /dev/null +++ b/test/unary.js @@ -0,0 +1,27 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice } from './utils.js'; +import unary from '../unary.js'; + +describe('unary', function() { + function fn() { + return slice.call(arguments); + } + + it('should cap the number of arguments provided to `func`', function() { + var actual = lodashStable.map(['6', '8', '10'], unary(parseInt)); + assert.deepStrictEqual(actual, [6, 8, 10]); + }); + + it('should not force a minimum argument count', function() { + var capped = unary(fn); + assert.deepStrictEqual(capped(), []); + }); + + it('should use `this` binding of function', function() { + var capped = unary(function(a, b) { return this; }), + object = { 'capped': capped }; + + assert.strictEqual(object.capped(), object); + }); +}); diff --git a/test/uncommon-symbols.js b/test/uncommon-symbols.js new file mode 100644 index 000000000..f108be793 --- /dev/null +++ b/test/uncommon-symbols.js @@ -0,0 +1,170 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { emojiVar, comboMarks, fitzModifiers } from './utils.js'; +import repeat from '../repeat.js'; +import camelCase from '../camelCase.js'; +import capitalize from '../capitalize.js'; +import pad from '../pad.js'; +import padStart from '../padStart.js'; +import padEnd from '../padEnd.js'; +import size from '../size.js'; +import split from '../split.js'; +import toArray from '../toArray.js'; +import trim from '../trim.js'; +import trimStart from '../trimStart.js'; +import trimEnd from '../trimEnd.js'; +import truncate from '../truncate.js'; +import words from '../words.js'; + +describe('uncommon symbols', function() { + var flag = '\ud83c\uddfa\ud83c\uddf8', + heart = '\u2764' + emojiVar, + hearts = '\ud83d\udc95', + comboGlyph = '\ud83d\udc68\u200d' + heart + '\u200d\ud83d\udc8B\u200d\ud83d\udc68', + hashKeycap = '#' + emojiVar + '\u20e3', + leafs = '\ud83c\udf42', + mic = '\ud83c\udf99', + noMic = mic + '\u20e0', + raisedHand = '\u270B' + emojiVar, + rocket = '\ud83d\ude80', + thumbsUp = '\ud83d\udc4d'; + + it('should account for astral symbols', function() { + var allHearts = repeat(hearts, 10), + chars = hearts + comboGlyph, + string = 'A ' + leafs + ', ' + comboGlyph + ', and ' + rocket, + trimChars = comboGlyph + hearts, + trimString = trimChars + string + trimChars; + + assert.strictEqual(camelCase(hearts + ' the ' + leafs), hearts + 'The' + leafs); + assert.strictEqual(camelCase(string), 'a' + leafs + comboGlyph + 'And' + rocket); + assert.strictEqual(capitalize(rocket), rocket); + + assert.strictEqual(pad(string, 16), ' ' + string + ' '); + assert.strictEqual(padStart(string, 16), ' ' + string); + assert.strictEqual(padEnd(string, 16), string + ' '); + + assert.strictEqual(pad(string, 16, chars), hearts + string + chars); + assert.strictEqual(padStart(string, 16, chars), chars + hearts + string); + assert.strictEqual(padEnd(string, 16, chars), string + chars + hearts); + + assert.strictEqual(size(string), 13); + assert.deepStrictEqual(split(string, ' '), ['A', leafs + ',', comboGlyph + ',', 'and', rocket]); + assert.deepStrictEqual(split(string, ' ', 3), ['A', leafs + ',', comboGlyph + ',']); + assert.deepStrictEqual(split(string, undefined), [string]); + assert.deepStrictEqual(split(string, undefined, -1), [string]); + assert.deepStrictEqual(split(string, undefined, 0), []); + + var expected = ['A', ' ', leafs, ',', ' ', comboGlyph, ',', ' ', 'a', 'n', 'd', ' ', rocket]; + + assert.deepStrictEqual(split(string, ''), expected); + assert.deepStrictEqual(split(string, '', 6), expected.slice(0, 6)); + assert.deepStrictEqual(toArray(string), expected); + + assert.strictEqual(trim(trimString, chars), string); + assert.strictEqual(trimStart(trimString, chars), string + trimChars); + assert.strictEqual(trimEnd(trimString, chars), trimChars + string); + + assert.strictEqual(truncate(string, { 'length': 13 }), string); + assert.strictEqual(truncate(string, { 'length': 6 }), 'A ' + leafs + '...'); + + assert.deepStrictEqual(words(string), ['A', leafs, comboGlyph, 'and', rocket]); + assert.deepStrictEqual(toArray(hashKeycap), [hashKeycap]); + assert.deepStrictEqual(toArray(noMic), [noMic]); + + lodashStable.times(2, function(index) { + var separator = index ? RegExp(hearts) : hearts, + options = { 'length': 4, 'separator': separator }, + actual = truncate(string, options); + + assert.strictEqual(actual, 'A...'); + assert.strictEqual(actual.length, 4); + + actual = truncate(allHearts, options); + assert.strictEqual(actual, hearts + '...'); + assert.strictEqual(actual.length, 5); + }); + }); + + it('should account for combining diacritical marks', function() { + var values = lodashStable.map(comboMarks, function(mark) { + return 'o' + mark; + }); + + var expected = lodashStable.map(values, function(value) { + return [1, [value], [value]]; + }); + + var actual = lodashStable.map(values, function(value) { + return [size(value), toArray(value), words(value)]; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should account for fitzpatrick modifiers', function() { + var values = lodashStable.map(fitzModifiers, function(modifier) { + return thumbsUp + modifier; + }); + + var expected = lodashStable.map(values, function(value) { + return [1, [value], [value]]; + }); + + var actual = lodashStable.map(values, function(value) { + return [size(value), toArray(value), words(value)]; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should account for regional symbols', function() { + var pair = flag.match(/\ud83c[\udde6-\uddff]/g), + regionals = pair.join(' '); + + assert.strictEqual(size(flag), 1); + assert.strictEqual(size(regionals), 3); + + assert.deepStrictEqual(toArray(flag), [flag]); + assert.deepStrictEqual(toArray(regionals), [pair[0], ' ', pair[1]]); + + assert.deepStrictEqual(words(flag), [flag]); + assert.deepStrictEqual(words(regionals), [pair[0], pair[1]]); + }); + + it('should account for variation selectors', function() { + assert.strictEqual(size(heart), 1); + assert.deepStrictEqual(toArray(heart), [heart]); + assert.deepStrictEqual(words(heart), [heart]); + }); + + it('should account for variation selectors with fitzpatrick modifiers', function() { + var values = lodashStable.map(fitzModifiers, function(modifier) { + return raisedHand + modifier; + }); + + var expected = lodashStable.map(values, function(value) { + return [1, [value], [value]]; + }); + + var actual = lodashStable.map(values, function(value) { + return [size(value), toArray(value), words(value)]; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should match lone surrogates', function() { + var pair = hearts.split(''), + surrogates = pair[0] + ' ' + pair[1]; + + assert.strictEqual(size(surrogates), 3); + assert.deepStrictEqual(toArray(surrogates), [pair[0], ' ', pair[1]]); + assert.deepStrictEqual(words(surrogates), []); + }); + + it('should match side by side fitzpatrick modifiers separately ', function() { + var string = fitzModifiers[0] + fitzModifiers[0]; + assert.deepStrictEqual(toArray(string), [fitzModifiers[0], fitzModifiers[0]]); + }); +}); diff --git a/test/unescape.js b/test/unescape.js new file mode 100644 index 000000000..418ebdff9 --- /dev/null +++ b/test/unescape.js @@ -0,0 +1,34 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import unescape from '../unescape.js'; +import escape from '../escape.js'; + +describe('unescape', function() { + var escaped = '&<>"'/', + unescaped = '&<>"\'/'; + + escaped += escaped; + unescaped += unescaped; + + it('should unescape entities in order', function() { + assert.strictEqual(unescape('<'), '<'); + }); + + it('should unescape the proper entities', function() { + assert.strictEqual(unescape(escaped), unescaped); + }); + + it('should handle strings with nothing to unescape', function() { + assert.strictEqual(unescape('abc'), 'abc'); + }); + + it('should unescape the same characters escaped by `_.escape`', function() { + assert.strictEqual(unescape(escape(unescaped)), unescaped); + }); + + lodashStable.each(['`', '/'], function(entity) { + it('should not unescape the "' + entity + '" entity', function() { + assert.strictEqual(unescape(entity), entity); + }); + }); +}); diff --git a/test/union-methods.js b/test/union-methods.js new file mode 100644 index 000000000..fc872e278 --- /dev/null +++ b/test/union-methods.js @@ -0,0 +1,31 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, args } from './utils.js'; + +describe('union methods', function() { + lodashStable.each(['union', 'unionBy', 'unionWith'], function(methodName) { + var func = _[methodName]; + + it('`_.' + methodName + '` should return the union of two arrays', function() { + var actual = func([2], [1, 2]); + assert.deepStrictEqual(actual, [2, 1]); + }); + + it('`_.' + methodName + '` should return the union of multiple arrays', function() { + var actual = func([2], [1, 2], [2, 3]); + assert.deepStrictEqual(actual, [2, 1, 3]); + }); + + it('`_.' + methodName + '` should not flatten nested arrays', function() { + var actual = func([1, 3, 2], [1, [5]], [2, [4]]); + assert.deepStrictEqual(actual, [1, 3, 2, [5], [4]]); + }); + + it('`_.' + methodName + '` should ignore values that are not arrays or `arguments` objects', function() { + var array = [0]; + assert.deepStrictEqual(func(array, 3, { '0': 1 }, null), array); + assert.deepStrictEqual(func(null, array, null, [2, 1]), [0, 2, 1]); + assert.deepStrictEqual(func(array, null, args, null), [0, 1, 2, 3]); + }); + }); +}); diff --git a/test/unionBy.js b/test/unionBy.js new file mode 100644 index 000000000..9dd5f8e55 --- /dev/null +++ b/test/unionBy.js @@ -0,0 +1,28 @@ +import assert from 'assert'; +import { slice } from './utils.js'; +import unionBy from '../unionBy.js'; + +describe('unionBy', function() { + it('should accept an `iteratee`', function() { + var actual = unionBy([2.1], [1.2, 2.3], Math.floor); + assert.deepStrictEqual(actual, [2.1, 1.2]); + + actual = unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); + assert.deepStrictEqual(actual, [{ 'x': 1 }, { 'x': 2 }]); + }); + + it('should provide correct `iteratee` arguments', function() { + var args; + + unionBy([2.1], [1.2, 2.3], function() { + args || (args = slice.call(arguments)); + }); + + assert.deepStrictEqual(args, [2.1]); + }); + + it('should output values from the first possible array', function() { + var actual = unionBy([{ 'x': 1, 'y': 1 }], [{ 'x': 1, 'y': 2 }], 'x'); + assert.deepStrictEqual(actual, [{ 'x': 1, 'y': 1 }]); + }); +}); diff --git a/test/unionWith.test.js b/test/unionWith.test.js new file mode 100644 index 000000000..e5f4d9664 --- /dev/null +++ b/test/unionWith.test.js @@ -0,0 +1,24 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import unionWith from '../unionWith.js'; + +describe('unionWith', function() { + it('should work with a `comparator`', function() { + var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }], + others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }], + actual = unionWith(objects, others, lodashStable.isEqual); + + assert.deepStrictEqual(actual, [objects[0], objects[1], others[0]]); + }); + + it('should output values from the first possible array', function() { + var objects = [{ 'x': 1, 'y': 1 }], + others = [{ 'x': 1, 'y': 2 }]; + + var actual = unionWith(objects, others, function(a, b) { + return a.x == b.x; + }); + + assert.deepStrictEqual(actual, [{ 'x': 1, 'y': 1 }]); + }); +}); diff --git a/test/uniq-methods.js b/test/uniq-methods.js new file mode 100644 index 000000000..6949a311f --- /dev/null +++ b/test/uniq-methods.js @@ -0,0 +1,123 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, LARGE_ARRAY_SIZE, isEven } from './utils.js'; +import sortBy from '../sortBy.js'; + +describe('uniq methods', function() { + lodashStable.each(['uniq', 'uniqBy', 'uniqWith', 'sortedUniq', 'sortedUniqBy'], function(methodName) { + var func = _[methodName], + isSorted = /^sorted/.test(methodName), + objects = [{ 'a': 2 }, { 'a': 3 }, { 'a': 1 }, { 'a': 2 }, { 'a': 3 }, { 'a': 1 }]; + + if (isSorted) { + objects = sortBy(objects, 'a'); + } + else { + it('`_.' + methodName + '` should return unique values of an unsorted array', function() { + var array = [2, 1, 2]; + assert.deepStrictEqual(func(array), [2, 1]); + }); + } + it('`_.' + methodName + '` should return unique values of a sorted array', function() { + var array = [1, 2, 2]; + assert.deepStrictEqual(func(array), [1, 2]); + }); + + it('`_.' + methodName + '` should treat object instances as unique', function() { + assert.deepStrictEqual(func(objects), objects); + }); + + it('`_.' + methodName + '` should treat `-0` as `0`', function() { + var actual = lodashStable.map(func([-0, 0]), lodashStable.toString); + assert.deepStrictEqual(actual, ['0']); + }); + + it('`_.' + methodName + '` should match `NaN`', function() { + assert.deepStrictEqual(func([NaN, NaN]), [NaN]); + }); + + it('`_.' + methodName + '` should work with large arrays', function() { + var largeArray = [], + expected = [0, {}, 'a'], + count = Math.ceil(LARGE_ARRAY_SIZE / expected.length); + + lodashStable.each(expected, function(value) { + lodashStable.times(count, function() { + largeArray.push(value); + }); + }); + + assert.deepStrictEqual(func(largeArray), expected); + }); + + it('`_.' + methodName + '` should work with large arrays of `-0` as `0`', function() { + var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, function(index) { + return isEven(index) ? -0 : 0; + }); + + var actual = lodashStable.map(func(largeArray), lodashStable.toString); + assert.deepStrictEqual(actual, ['0']); + }); + + it('`_.' + methodName + '` should work with large arrays of boolean, `NaN`, and nullish values', function() { + var largeArray = [], + expected = [null, undefined, false, true, NaN], + count = Math.ceil(LARGE_ARRAY_SIZE / expected.length); + + lodashStable.each(expected, function(value) { + lodashStable.times(count, function() { + largeArray.push(value); + }); + }); + + assert.deepStrictEqual(func(largeArray), expected); + }); + + it('`_.' + methodName + '` should work with large arrays of symbols', function() { + if (Symbol) { + var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, Symbol); + assert.deepStrictEqual(func(largeArray), largeArray); + } + }); + + it('`_.' + methodName + '` should work with large arrays of well-known symbols', function() { + // See http://www.ecma-international.org/ecma-262/6.0/#sec-well-known-symbols. + if (Symbol) { + var expected = [ + Symbol.hasInstance, Symbol.isConcatSpreadable, Symbol.iterator, + Symbol.match, Symbol.replace, Symbol.search, Symbol.species, + Symbol.split, Symbol.toPrimitive, Symbol.toStringTag, Symbol.unscopables + ]; + + var largeArray = [], + count = Math.ceil(LARGE_ARRAY_SIZE / expected.length); + + expected = lodashStable.map(expected, function(symbol) { + return symbol || {}; + }); + + lodashStable.each(expected, function(value) { + lodashStable.times(count, function() { + largeArray.push(value); + }); + }); + + assert.deepStrictEqual(func(largeArray), expected); + } + }); + + it('`_.' + methodName + '` should distinguish between numbers and numeric strings', function() { + var largeArray = [], + expected = ['2', 2, Object('2'), Object(2)], + count = Math.ceil(LARGE_ARRAY_SIZE / expected.length); + + lodashStable.each(expected, function(value) { + lodashStable.times(count, function() { + largeArray.push(value); + }); + }); + + assert.deepStrictEqual(func(largeArray), expected); + }); + }); +}); diff --git a/test/uniq.js b/test/uniq.js new file mode 100644 index 000000000..5eaf74c41 --- /dev/null +++ b/test/uniq.js @@ -0,0 +1,11 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; + +describe('uniq', function() { + it('should perform an unsorted uniq when used as an iteratee for methods like `_.map`', function() { + var array = [[2, 1, 2], [1, 2, 1]], + actual = lodashStable.map(array, lodashStable.uniq); + + assert.deepStrictEqual(actual, [[2, 1], [1, 2]]); + }); +}); diff --git a/test/uniqBy-methods.js b/test/uniqBy-methods.js new file mode 100644 index 000000000..046ae644d --- /dev/null +++ b/test/uniqBy-methods.js @@ -0,0 +1,74 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, LARGE_ARRAY_SIZE, slice } from './utils.js'; +import sortBy from '../sortBy.js'; + +describe('uniqBy methods', function() { + lodashStable.each(['uniqBy', 'sortedUniqBy'], function(methodName) { + var func = _[methodName], + isSorted = methodName == 'sortedUniqBy', + objects = [{ 'a': 2 }, { 'a': 3 }, { 'a': 1 }, { 'a': 2 }, { 'a': 3 }, { 'a': 1 }]; + + if (isSorted) { + objects = sortBy(objects, 'a'); + } + it('`_.' + methodName + '` should work with an `iteratee`', function() { + var expected = isSorted ? [{ 'a': 1 }, { 'a': 2 }, { 'a': 3 }] : objects.slice(0, 3); + + var actual = func(objects, function(object) { + return object.a; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work with large arrays', function() { + var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, function() { + return [1, 2]; + }); + + var actual = func(largeArray, String); + assert.strictEqual(actual[0], largeArray[0]); + assert.deepStrictEqual(actual, [[1, 2]]); + }); + + it('`_.' + methodName + '` should provide correct `iteratee` arguments', function() { + var args; + + func(objects, function() { + args || (args = slice.call(arguments)); + }); + + assert.deepStrictEqual(args, [objects[0]]); + }); + + it('`_.' + methodName + '` should work with `_.property` shorthands', function() { + var expected = isSorted ? [{ 'a': 1 }, { 'a': 2 }, { 'a': 3 }] : objects.slice(0, 3), + actual = func(objects, 'a'); + + assert.deepStrictEqual(actual, expected); + + var arrays = [[2], [3], [1], [2], [3], [1]]; + if (isSorted) { + arrays = lodashStable.sortBy(arrays, 0); + } + expected = isSorted ? [[1], [2], [3]] : arrays.slice(0, 3); + actual = func(arrays, 0); + + assert.deepStrictEqual(actual, expected); + }); + + lodashStable.each({ + 'an array': [0, 'a'], + 'an object': { '0': 'a' }, + 'a number': 0, + 'a string': '0' + }, + function(iteratee, key) { + it('`_.' + methodName + '` should work with ' + key + ' for `iteratee`', function() { + var actual = func([['a'], ['a'], ['b']], iteratee); + assert.deepStrictEqual(actual, [['a'], ['b']]); + }); + }); + }); +}); diff --git a/test/uniqWith.test.js b/test/uniqWith.test.js new file mode 100644 index 000000000..bb9e79873 --- /dev/null +++ b/test/uniqWith.test.js @@ -0,0 +1,28 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { LARGE_ARRAY_SIZE, isEven } from './utils.js'; +import uniqWith from '../uniqWith.js'; + +describe('uniqWith', function() { + it('should work with a `comparator`', function() { + var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }], + actual = uniqWith(objects, lodashStable.isEqual); + + assert.deepStrictEqual(actual, [objects[0], objects[1]]); + }); + + it('should preserve the sign of `0`', function() { + var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, function(index) { + return isEven(index) ? -0 : 0; + }); + + var arrays = [[-0, 0], largeArray], + expected = lodashStable.map(arrays, lodashStable.constant(['-0'])); + + var actual = lodashStable.map(arrays, function(array) { + return lodashStable.map(uniqWith(array, lodashStable.eq), lodashStable.toString); + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/uniqueId.js b/test/uniqueId.js new file mode 100644 index 000000000..4b0485b82 --- /dev/null +++ b/test/uniqueId.js @@ -0,0 +1,22 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import uniqueId from '../uniqueId.js'; + +describe('uniqueId', function() { + it('should generate unique ids', function() { + var actual = lodashStable.times(1000, function() { + return uniqueId(); + }); + + assert.strictEqual(lodashStable.uniq(actual).length, actual.length); + }); + + it('should return a string value when not providing a `prefix`', function() { + assert.strictEqual(typeof uniqueId(), 'string'); + }); + + it('should coerce the prefix argument to a string', function() { + var actual = [uniqueId(3), uniqueId(2), uniqueId(1)]; + assert.ok(/3\d+,2\d+,1\d+/.test(actual)); + }); +}); diff --git a/test/unset.js b/test/unset.js new file mode 100644 index 000000000..0aa2f5bd7 --- /dev/null +++ b/test/unset.js @@ -0,0 +1,119 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { symbol, numberProto, stringProto, defineProperty } from './utils.js'; +import unset from '../unset.js'; + +describe('unset', function() { + it('should unset property values', function() { + lodashStable.each(['a', ['a']], function(path) { + var object = { 'a': 1, 'c': 2 }; + assert.strictEqual(unset(object, path), true); + assert.deepStrictEqual(object, { 'c': 2 }); + }); + }); + + it('should preserve the sign of `0`', function() { + var props = [-0, Object(-0), 0, Object(0)], + expected = lodashStable.map(props, lodashStable.constant([true, false])); + + var actual = lodashStable.map(props, function(key) { + var object = { '-0': 'a', '0': 'b' }; + return [unset(object, key), lodashStable.toString(key) in object]; + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should unset symbol keyed property values', function() { + if (Symbol) { + var object = {}; + object[symbol] = 1; + + assert.strictEqual(unset(object, symbol), true); + assert.ok(!(symbol in object)); + } + }); + + it('should unset deep property values', function() { + lodashStable.each(['a.b', ['a', 'b']], function(path) { + var object = { 'a': { 'b': null } }; + assert.strictEqual(unset(object, path), true); + assert.deepStrictEqual(object, { 'a': {} }); + }); + }); + + it('should handle complex paths', function() { + var paths = [ + 'a[-1.23]["[\\"b\\"]"].c[\'[\\\'d\\\']\'][\ne\n][f].g', + ['a', '-1.23', '["b"]', 'c', "['d']", '\ne\n', 'f', 'g'] + ]; + + lodashStable.each(paths, function(path) { + var object = { 'a': { '-1.23': { '["b"]': { 'c': { "['d']": { '\ne\n': { 'f': { 'g': 8 } } } } } } } }; + assert.strictEqual(unset(object, path), true); + assert.ok(!('g' in object.a[-1.23]['["b"]'].c["['d']"]['\ne\n'].f)); + }); + }); + + it('should return `true` for nonexistent paths', function() { + var object = { 'a': { 'b': { 'c': null } } }; + + lodashStable.each(['z', 'a.z', 'a.b.z', 'a.b.c.z'], function(path) { + assert.strictEqual(unset(object, path), true); + }); + + assert.deepStrictEqual(object, { 'a': { 'b': { 'c': null } } }); + }); + + it('should not error when `object` is nullish', function() { + var values = [null, undefined], + expected = [[true, true], [true, true]]; + + var actual = lodashStable.map(values, function(value) { + try { + return [unset(value, 'a.b'), unset(value, ['a', 'b'])]; + } catch (e) { + return e.message; + } + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should follow `path` over non-plain objects', function() { + var object = { 'a': '' }, + paths = ['constructor.prototype.a', ['constructor', 'prototype', 'a']]; + + lodashStable.each(paths, function(path) { + numberProto.a = 1; + + var actual = unset(0, path); + assert.strictEqual(actual, true); + assert.ok(!('a' in numberProto)); + + delete numberProto.a; + }); + + lodashStable.each(['a.replace.b', ['a', 'replace', 'b']], function(path) { + stringProto.replace.b = 1; + + var actual = unset(object, path); + assert.strictEqual(actual, true); + assert.ok(!('a' in stringProto.replace)); + + delete stringProto.replace.b; + }); + }); + + it('should return `false` for non-configurable properties', function() { + var object = {}; + + defineProperty(object, 'a', { + 'configurable': false, + 'enumerable': true, + 'writable': true, + 'value': 1, + }); + assert.strictEqual(unset(object, 'a'), false); + }); +}); diff --git a/test/unzip-and-zip.js b/test/unzip-and-zip.js new file mode 100644 index 000000000..57cdfd6ef --- /dev/null +++ b/test/unzip-and-zip.js @@ -0,0 +1,72 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, falsey, stubArray } from './utils.js'; + +describe('unzip and zip', function() { + lodashStable.each(['unzip', 'zip'], function(methodName, index) { + var func = _[methodName]; + func = lodashStable.bind(index ? func.apply : func.call, func, null); + + var object = { + 'an empty array': [ + [], + [] + ], + '0-tuples': [ + [[], []], + [] + ], + '2-tuples': [ + [['barney', 'fred'], [36, 40]], + [['barney', 36], ['fred', 40]] + ], + '3-tuples': [ + [['barney', 'fred'], [36, 40], [false, true]], + [['barney', 36, false], ['fred', 40, true]] + ] + }; + + lodashStable.forOwn(object, function(pair, key) { + it('`_.' + methodName + '` should work with ' + key, function() { + var actual = func(pair[0]); + assert.deepStrictEqual(actual, pair[1]); + assert.deepStrictEqual(func(actual), actual.length ? pair[0] : []); + }); + }); + + it('`_.' + methodName + '` should work with tuples of different lengths', function() { + var pair = [ + [['barney', 36], ['fred', 40, false]], + [['barney', 'fred'], [36, 40], [undefined, false]] + ]; + + var actual = func(pair[0]); + assert.ok('0' in actual[2]); + assert.deepStrictEqual(actual, pair[1]); + + actual = func(actual); + assert.ok('2' in actual[0]); + assert.deepStrictEqual(actual, [['barney', 36, undefined], ['fred', 40, false]]); + }); + + it('`_.' + methodName + '` should treat falsey values as empty arrays', function() { + var expected = lodashStable.map(falsey, stubArray); + + var actual = lodashStable.map(falsey, function(value) { + return func([value, value, value]); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should ignore values that are not arrays or `arguments` objects', function() { + var array = [[1, 2], [3, 4], null, undefined, { '0': 1 }]; + assert.deepStrictEqual(func(array), [[1, 3], [2, 4]]); + }); + + it('`_.' + methodName + '` should support consuming its return value', function() { + var expected = [['barney', 'fred'], [36, 40]]; + assert.deepStrictEqual(func(func(func(func(expected)))), expected); + }); + }); +}); diff --git a/test/unzipWith.js b/test/unzipWith.js new file mode 100644 index 000000000..d02d1a76a --- /dev/null +++ b/test/unzipWith.js @@ -0,0 +1,39 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice } from './utils.js'; +import unzipWith from '../unzipWith.js'; +import unzip from '../unzip.js'; + +describe('unzipWith', function() { + it('should unzip arrays combining regrouped elements with `iteratee`', function() { + var array = [[1, 4], [2, 5], [3, 6]]; + + var actual = unzipWith(array, function(a, b, c) { + return a + b + c; + }); + + assert.deepStrictEqual(actual, [6, 15]); + }); + + it('should provide correct `iteratee` arguments', function() { + var args; + + unzipWith([[1, 3, 5], [2, 4, 6]], function() { + args || (args = slice.call(arguments)); + }); + + assert.deepStrictEqual(args, [1, 2]); + }); + + it('should perform a basic unzip when `iteratee` is nullish', function() { + var array = [[1, 3], [2, 4]], + values = [, null, undefined], + expected = lodashStable.map(values, lodashStable.constant(unzip(array))); + + var actual = lodashStable.map(values, function(value, index) { + return index ? unzipWith(array, value) : unzipWith(array); + }); + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/test/update-methods.js b/test/update-methods.js new file mode 100644 index 000000000..159f4008a --- /dev/null +++ b/test/update-methods.js @@ -0,0 +1,25 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _ } from './utils.js'; + +describe('update methods', function() { + lodashStable.each(['update', 'updateWith'], function(methodName) { + var func = _[methodName], + oldValue = 1; + + it('`_.' + methodName + '` should invoke `updater` with the value on `path` of `object`', function() { + var object = { 'a': [{ 'b': { 'c': oldValue } }] }, + expected = oldValue + 1; + + lodashStable.each(['a[0].b.c', ['a', '0', 'b', 'c']], function(path) { + func(object, path, function(n) { + assert.strictEqual(n, oldValue); + return ++n; + }); + + assert.strictEqual(object.a[0].b.c, expected); + object.a[0].b.c = oldValue; + }); + }); + }); +}); diff --git a/test/updateWith.js b/test/updateWith.js new file mode 100644 index 000000000..7c709f91d --- /dev/null +++ b/test/updateWith.js @@ -0,0 +1,19 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { stubThree, stubFour, noop } from './utils.js'; +import updateWith from '../updateWith.js'; + +describe('updateWith', function() { + it('should work with a `customizer` callback', function() { + var actual = updateWith({ '0': {} }, '[0][1][2]', stubThree, function(value) { + return lodashStable.isObject(value) ? undefined : {}; + }); + + assert.deepStrictEqual(actual, { '0': { '1': { '2': 3 } } }); + }); + + it('should work with a `customizer` that returns `undefined`', function() { + var actual = updateWith({}, 'a[0].b.c', stubFour, noop); + assert.deepStrictEqual(actual, { 'a': [{ 'b': { 'c': 4 } }] }); + }); +}); diff --git a/test/upperCase.js b/test/upperCase.js new file mode 100644 index 000000000..08c362a0a --- /dev/null +++ b/test/upperCase.js @@ -0,0 +1,10 @@ +import assert from 'assert'; +import upperCase from '../upperCase.js'; + +describe('upperCase', function() { + it('should uppercase as space-separated words', function() { + assert.strictEqual(upperCase('--foo-bar--'), 'FOO BAR'); + assert.strictEqual(upperCase('fooBar'), 'FOO BAR'); + assert.strictEqual(upperCase('__foo_bar__'), 'FOO BAR'); + }); +}); diff --git a/test/upperFirst.test.js b/test/upperFirst.test.js new file mode 100644 index 000000000..b3833310a --- /dev/null +++ b/test/upperFirst.test.js @@ -0,0 +1,10 @@ +import assert from 'assert'; +import upperFirst from '../upperFirst.js'; + +describe('upperFirst', function() { + it('should uppercase only the first character', function() { + assert.strictEqual(upperFirst('fred'), 'Fred'); + assert.strictEqual(upperFirst('Fred'), 'Fred'); + assert.strictEqual(upperFirst('FRED'), 'FRED'); + }); +}); diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 000000000..af88e1580 --- /dev/null +++ b/test/utils.js @@ -0,0 +1,798 @@ +/** Used to detect when a function becomes hot. */ +var HOT_COUNT = 150; + +/** Used as the size to cover large array optimizations. */ +var LARGE_ARRAY_SIZE = 200; + +/** Used as the `TypeError` message for "Functions" methods. */ +var FUNC_ERROR_TEXT = 'Expected a function'; + +/** Used as the maximum memoize cache size. */ +var MAX_MEMOIZE_SIZE = 500; + +/** Used as references for various `Number` constants. */ +var MAX_SAFE_INTEGER = 9007199254740991, + MAX_INTEGER = 1.7976931348623157e+308; + +/** Used as references for the maximum length and index of an array. */ +var MAX_ARRAY_LENGTH = 4294967295, + MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1; + +/** `Object#toString` result references. */ +var funcTag = '[object Function]', + numberTag = '[object Number]', + objectTag = '[object Object]'; + +/** Used as a reference to the global object. */ +var root = (typeof global == 'object' && global) || this; + +/** Used to store lodash to test for bad extensions/shims. */ +var lodashBizarro = root.lodashBizarro; + +/** Used for native method references. */ +var arrayProto = Array.prototype, + funcProto = Function.prototype, + objectProto = Object.prototype, + numberProto = Number.prototype, + stringProto = String.prototype; + +/** Method and object shortcuts. */ +var phantom = root.phantom, + process = root.process, + amd = root.define ? define.amd : undefined, + args = toArgs([1, 2, 3]), + argv = process ? process.argv : undefined, + defineProperty = Object.defineProperty, + document = phantom ? undefined : root.document, + body = root.document ? root.document.body : undefined, + create = Object.create, + fnToString = funcProto.toString, + freeze = Object.freeze, + getSymbols = Object.getOwnPropertySymbols, + identity = function(value) { return value; }, + noop = function() {}, + objToString = objectProto.toString, + params = argv, + push = arrayProto.push, + realm = {}, + slice = arrayProto.slice, + strictArgs = (function() { 'use strict'; return arguments; }(1, 2, 3)); + +var ArrayBuffer = root.ArrayBuffer, + Buffer = root.Buffer, + Map = root.Map, + Promise = root.Promise, + Proxy = root.Proxy, + Set = root.Set, + Symbol = root.Symbol, + Uint8Array = root.Uint8Array, + WeakMap = root.WeakMap, + WeakSet = root.WeakSet; + +var arrayBuffer = ArrayBuffer ? new ArrayBuffer(2) : undefined, + map = Map ? new Map : undefined, + promise = Promise ? Promise.resolve(1) : undefined, + set = Set ? new Set : undefined, + symbol = Symbol ? Symbol('a') : undefined, + weakMap = WeakMap ? new WeakMap : undefined, + weakSet = WeakSet ? new WeakSet : undefined; + +/** Math helpers. */ +var add = function(x, y) { return x + y; }, + doubled = function(n) { return n * 2; }, + isEven = function(n) { return n % 2 == 0; }, + square = function(n) { return n * n; }; + +/** Stub functions. */ +var stubA = function() { return 'a'; }, + stubB = function() { return 'b'; }, + stubC = function() { return 'c'; }; + +var stubTrue = function() { return true; }, + stubFalse = function() { return false; }; + +var stubNaN = function() { return NaN; }, + stubNull = function() { return null; }; + +var stubZero = function() { return 0; }, + stubOne = function() { return 1; }, + stubTwo = function() { return 2; }, + stubThree = function() { return 3; }, + stubFour = function() { return 4; }; + +var stubArray = function() { return []; }, + stubObject = function() { return {}; }, + stubString = function() { return ''; }; + +/** List of Latin Unicode letters. */ +var burredLetters = [ + // Latin-1 Supplement letters. + '\xc0', '\xc1', '\xc2', '\xc3', '\xc4', '\xc5', '\xc6', '\xc7', '\xc8', '\xc9', '\xca', '\xcb', '\xcc', '\xcd', '\xce', '\xcf', + '\xd0', '\xd1', '\xd2', '\xd3', '\xd4', '\xd5', '\xd6', '\xd8', '\xd9', '\xda', '\xdb', '\xdc', '\xdd', '\xde', '\xdf', + '\xe0', '\xe1', '\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7', '\xe8', '\xe9', '\xea', '\xeb', '\xec', '\xed', '\xee', '\xef', + '\xf0', '\xf1', '\xf2', '\xf3', '\xf4', '\xf5', '\xf6', '\xf8', '\xf9', '\xfa', '\xfb', '\xfc', '\xfd', '\xfe', '\xff', + // Latin Extended-A letters. + '\u0100', '\u0101', '\u0102', '\u0103', '\u0104', '\u0105', '\u0106', '\u0107', '\u0108', '\u0109', '\u010a', '\u010b', '\u010c', '\u010d', '\u010e', '\u010f', + '\u0110', '\u0111', '\u0112', '\u0113', '\u0114', '\u0115', '\u0116', '\u0117', '\u0118', '\u0119', '\u011a', '\u011b', '\u011c', '\u011d', '\u011e', '\u011f', + '\u0120', '\u0121', '\u0122', '\u0123', '\u0124', '\u0125', '\u0126', '\u0127', '\u0128', '\u0129', '\u012a', '\u012b', '\u012c', '\u012d', '\u012e', '\u012f', + '\u0130', '\u0131', '\u0132', '\u0133', '\u0134', '\u0135', '\u0136', '\u0137', '\u0138', '\u0139', '\u013a', '\u013b', '\u013c', '\u013d', '\u013e', '\u013f', + '\u0140', '\u0141', '\u0142', '\u0143', '\u0144', '\u0145', '\u0146', '\u0147', '\u0148', '\u0149', '\u014a', '\u014b', '\u014c', '\u014d', '\u014e', '\u014f', + '\u0150', '\u0151', '\u0152', '\u0153', '\u0154', '\u0155', '\u0156', '\u0157', '\u0158', '\u0159', '\u015a', '\u015b', '\u015c', '\u015d', '\u015e', '\u015f', + '\u0160', '\u0161', '\u0162', '\u0163', '\u0164', '\u0165', '\u0166', '\u0167', '\u0168', '\u0169', '\u016a', '\u016b', '\u016c', '\u016d', '\u016e', '\u016f', + '\u0170', '\u0171', '\u0172', '\u0173', '\u0174', '\u0175', '\u0176', '\u0177', '\u0178', '\u0179', '\u017a', '\u017b', '\u017c', '\u017d', '\u017e', '\u017f' +]; + +/** List of combining diacritical marks. */ +var comboMarks = [ + '\u0300', '\u0301', '\u0302', '\u0303', '\u0304', '\u0305', '\u0306', '\u0307', '\u0308', '\u0309', '\u030a', '\u030b', '\u030c', '\u030d', '\u030e', '\u030f', + '\u0310', '\u0311', '\u0312', '\u0313', '\u0314', '\u0315', '\u0316', '\u0317', '\u0318', '\u0319', '\u031a', '\u031b', '\u031c', '\u031d', '\u031e', '\u031f', + '\u0320', '\u0321', '\u0322', '\u0323', '\u0324', '\u0325', '\u0326', '\u0327', '\u0328', '\u0329', '\u032a', '\u032b', '\u032c', '\u032d', '\u032e', '\u032f', + '\u0330', '\u0331', '\u0332', '\u0333', '\u0334', '\u0335', '\u0336', '\u0337', '\u0338', '\u0339', '\u033a', '\u033b', '\u033c', '\u033d', '\u033e', '\u033f', + '\u0340', '\u0341', '\u0342', '\u0343', '\u0344', '\u0345', '\u0346', '\u0347', '\u0348', '\u0349', '\u034a', '\u034b', '\u034c', '\u034d', '\u034e', '\u034f', + '\u0350', '\u0351', '\u0352', '\u0353', '\u0354', '\u0355', '\u0356', '\u0357', '\u0358', '\u0359', '\u035a', '\u035b', '\u035c', '\u035d', '\u035e', '\u035f', + '\u0360', '\u0361', '\u0362', '\u0363', '\u0364', '\u0365', '\u0366', '\u0367', '\u0368', '\u0369', '\u036a', '\u036b', '\u036c', '\u036d', '\u036e', '\u036f', + '\ufe20', '\ufe21', '\ufe22', '\ufe23' +]; + +/** List of converted Latin Unicode letters. */ +var deburredLetters = [ + // Converted Latin-1 Supplement letters. + 'A', 'A', 'A', 'A', 'A', 'A', 'Ae', 'C', 'E', 'E', 'E', 'E', 'I', 'I', 'I', + 'I', 'D', 'N', 'O', 'O', 'O', 'O', 'O', 'O', 'U', 'U', 'U', 'U', 'Y', 'Th', + 'ss', 'a', 'a', 'a', 'a', 'a', 'a', 'ae', 'c', 'e', 'e', 'e', 'e', 'i', 'i', 'i', + 'i', 'd', 'n', 'o', 'o', 'o', 'o', 'o', 'o', 'u', 'u', 'u', 'u', 'y', 'th', 'y', + // Converted Latin Extended-A letters. + 'A', 'a', 'A', 'a', 'A', 'a', 'C', 'c', 'C', 'c', 'C', 'c', 'C', 'c', + 'D', 'd', 'D', 'd', 'E', 'e', 'E', 'e', 'E', 'e', 'E', 'e', 'E', 'e', + 'G', 'g', 'G', 'g', 'G', 'g', 'G', 'g', 'H', 'h', 'H', 'h', + 'I', 'i', 'I', 'i', 'I', 'i', 'I', 'i', 'I', 'i', 'IJ', 'ij', 'J', 'j', + 'K', 'k', 'k', 'L', 'l', 'L', 'l', 'L', 'l', 'L', 'l', 'L', 'l', + 'N', 'n', 'N', 'n', 'N', 'n', "'n", 'N', 'n', + 'O', 'o', 'O', 'o', 'O', 'o', 'Oe', 'oe', + 'R', 'r', 'R', 'r', 'R', 'r', 'S', 's', 'S', 's', 'S', 's', 'S', 's', + 'T', 't', 'T', 't', 'T', 't', + 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', + 'W', 'w', 'Y', 'y', 'Y', 'Z', 'z', 'Z', 'z', 'Z', 'z', 's' +]; + +/** Used to provide falsey values to methods. */ +var falsey = [, null, undefined, false, 0, NaN, '']; + +/** Used to specify the emoji style glyph variant of characters. */ +var emojiVar = '\ufe0f'; + +/** Used to provide empty values to methods. */ +var empties = [[], {}].concat(falsey.slice(1)); + +/** Used to test error objects. */ +var errors = [ + new Error, + new EvalError, + new RangeError, + new ReferenceError, + new SyntaxError, + new TypeError, + new URIError +]; + +/** List of fitzpatrick modifiers. */ +var fitzModifiers = [ + '\ud83c\udffb', + '\ud83c\udffc', + '\ud83c\udffd', + '\ud83c\udffe', + '\ud83c\udfff' +]; + +/** Used to provide primitive values to methods. */ +var primitives = [null, undefined, false, true, 1, NaN, 'a']; + +/** Used to check whether methods support typed arrays. */ +var typedArrays = [ + 'Float32Array', + 'Float64Array', + 'Int8Array', + 'Int16Array', + 'Int32Array', + 'Uint8Array', + 'Uint8ClampedArray', + 'Uint16Array', + 'Uint32Array' +]; + +/** Used to check whether methods support array views. */ +var arrayViews = typedArrays.concat('DataView'); + +/** The file path of the lodash file to test. */ +var filePath = (function() { + var min = 2, + result = params || []; + + if (phantom) { + min = 0; + result = params = phantom.args || require('system').args; + } + var last = result[result.length - 1]; + result = (result.length > min && !/test(?:\.js)?$/.test(last)) ? last : '../node_modules/lodash/lodash.js'; + + if (!amd) { + try { + result = require('fs').realpathSync(result); + } catch (e) {} + + try { + result = require.resolve(result); + } catch (e) {} + } + return result; +}()); + +/** The `ui` object. */ +var ui = root.ui || (root.ui = { + 'buildPath': filePath, + 'loaderPath': '', + 'isModularize': /\b(?:amd|commonjs|es|node|npm|(index|main)\.js)\b/.test(filePath), + 'isStrict': /\bes\b/.test(filePath) || 'default' in require(filePath), + 'urlParams': {} +}); + +/** The basename of the lodash file to test. */ +var basename = /[\w.-]+$/.exec(filePath)[0]; + +/** Used to indicate testing a modularized build. */ +var isModularize = ui.isModularize; + +/** Detect if testing `npm` modules. */ +var isNpm = isModularize && /\bnpm\b/.test([ui.buildPath, ui.urlParams.build]); + +/** Detect if running in PhantomJS. */ +var isPhantom = phantom || (typeof callPhantom == 'function'); + +/** Detect if lodash is in strict mode. */ +var isStrict = ui.isStrict; + +/*--------------------------------------------------------------------------*/ + +// Leak to avoid sporadic `noglobals` fails on Edge in Sauce Labs. +root.msWDfn = undefined; + +// Assign `setTimeout` to itself to avoid being flagged as a leak. +setProperty(root, 'setTimeout', setTimeout); + +/*--------------------------------------------------------------------------*/ + +/** Used to test Web Workers. */ +var Worker = !(ui.isForeign || ui.isSauceLabs || isModularize) && + (document && document.origin != 'null') && root.Worker; + +/** Poison the free variable `root` in Node.js */ +try { + defineProperty(global.root, 'root', { + 'configurable': false, + 'enumerable': false, + 'get': function() { throw new ReferenceError; } + }); +} catch (e) {} + +/** Load stable Lodash. */ +var lodashStable = root.lodashStable; +if (!lodashStable) { + try { + lodashStable = interopRequire('../node_modules/lodash/lodash.js'); + } catch (e) { + console.log('Error: The stable lodash dev dependency should be at least a version behind master branch.'); + } + lodashStable = lodashStable.noConflict(); +} + +/** The `lodash` function to test. */ +var _ = root._ || (root._ = interopRequire(filePath)); + +/** Used to test pseudo private map caches. */ +var mapCaches = (function() { + var MapCache = _.memoize.Cache; + var result = { + 'Hash': new MapCache().__data__.hash.constructor, + 'MapCache': MapCache + }; + _.isMatchWith({ 'a': 1 }, { 'a': 1 }, function() { + var stack = lodashStable.last(arguments); + result.ListCache = stack.__data__.constructor; + result.Stack = stack.constructor; + }); + return result; +}()); + +/** Used to detect instrumented istanbul code coverage runs. */ +var coverage = root.__coverage__ || root[lodashStable.find(lodashStable.keys(root), function(key) { + return /^(?:\$\$cov_\d+\$\$)$/.test(key); +})]; + +/** Used to test async functions. */ +var asyncFunc = lodashStable.attempt(function() { + return Function('return async () => {}'); +}); + +/** Used to test generator functions. */ +var genFunc = lodashStable.attempt(function() { + return Function('return function*(){}'); +}); + +/** Used to restore the `_` reference. */ +var oldDash = root._; + +/** + * Used to check for problems removing whitespace. For a whitespace reference, + * see [V8's unit test](https://code.google.com/p/v8/source/browse/branches/bleeding_edge/test/mjsunit/whitespaces.js). + */ +var whitespace = lodashStable.filter([ + // Basic whitespace characters. + ' ', '\t', '\x0b', '\f', '\xa0', '\ufeff', + + // Line terminators. + '\n', '\r', '\u2028', '\u2029', + + // Unicode category "Zs" space separators. + '\u1680', '\u180e', '\u2000', '\u2001', '\u2002', '\u2003', '\u2004', '\u2005', + '\u2006', '\u2007', '\u2008', '\u2009', '\u200a', '\u202f', '\u205f', '\u3000' +], +function(chr) { return /\s/.exec(chr); }) +.join(''); + +/** + * Creates a custom error object. + * + * @private + * @constructor + * @param {string} message The error message. + */ +function CustomError(message) { + this.name = 'CustomError'; + this.message = message; +} + +CustomError.prototype = lodashStable.create(Error.prototype, { + 'constructor': CustomError +}); + +/** + * Removes all own enumerable string keyed properties from a given object. + * + * @private + * @param {Object} object The object to empty. + */ +function emptyObject(object) { + lodashStable.forOwn(object, function(value, key, object) { + delete object[key]; + }); +} + +/** + * Extracts the unwrapped value from its wrapper. + * + * @private + * @param {Object} wrapper The wrapper to unwrap. + * @returns {*} Returns the unwrapped value. + */ +function getUnwrappedValue(wrapper) { + var index = -1, + actions = wrapper.__actions__, + length = actions.length, + result = wrapper.__wrapped__; + + while (++index < length) { + var args = [result], + action = actions[index]; + + push.apply(args, action.args); + result = action.func.apply(action.thisArg, args); + } + return result; +} + +/** + * Loads the module of `id`. If the module has an `exports.default`, the + * exported default value is returned as the resolved module. + * + * @private + * @param {string} id The identifier of the module to resolve. + * @returns {*} Returns the resolved module. + */ +function interopRequire(id) { + var result = require(id); + return 'default' in result ? result['default'] : result; +} + +/** + * Sets a non-enumerable property value on `object`. + * + * Note: This function is used to avoid a bug in older versions of V8 where + * overwriting non-enumerable built-ins makes them enumerable. + * See https://code.google.com/p/v8/issues/detail?id=1623 + * + * @private + * @param {Object} object The object modify. + * @param {string} key The name of the property to set. + * @param {*} value The property value. + */ +function setProperty(object, key, value) { + try { + defineProperty(object, key, { + 'configurable': true, + 'enumerable': false, + 'writable': true, + 'value': value + }); + } catch (e) { + object[key] = value; + } + return object; +} + +/** + * Skips a given number of tests with a passing result. + * + * @private + * @param {Object} assert The QUnit assert object. + * @param {number} [count=1] The number of tests to skip. + */ +function skipAssert(assert, count) { + count || (count = 1); + while (count--) { + assert.ok(true, 'test skipped'); + } +} + +/** + * Converts `array` to an `arguments` object. + * + * @private + * @param {Array} array The array to convert. + * @returns {Object} Returns the converted `arguments` object. + */ +function toArgs(array) { + return (function() { return arguments; }.apply(undefined, array)); +} + +/*--------------------------------------------------------------------------*/ + +// Add bizarro values. +(function() { + return; // fixme + if (document || (typeof require != 'function')) { + return; + } + var nativeString = fnToString.call(toString), + reToString = /toString/g; + + function createToString(funcName) { + return lodashStable.constant(nativeString.replace(reToString, funcName)); + } + + // Allow bypassing native checks. + setProperty(funcProto, 'toString', function wrapper() { + setProperty(funcProto, 'toString', fnToString); + var result = lodashStable.has(this, 'toString') ? this.toString() : fnToString.call(this); + setProperty(funcProto, 'toString', wrapper); + return result; + }); + + // Add prototype extensions. + funcProto._method = noop; + + // Set bad shims. + setProperty(Object, 'create', undefined); + setProperty(Object, 'getOwnPropertySymbols', undefined); + + var _propertyIsEnumerable = objectProto.propertyIsEnumerable; + setProperty(objectProto, 'propertyIsEnumerable', function(key) { + return !(key == 'valueOf' && this && this.valueOf === 1) && _propertyIsEnumerable.call(this, key); + }); + + if (Buffer) { + defineProperty(root, 'Buffer', { + 'configurable': true, + 'enumerable': true, + 'get': function get() { + var caller = get.caller, + name = caller ? caller.name : ''; + + if (!(name == 'runInContext' || name.length == 1 || /\b_\.isBuffer\b/.test(caller))) { + return Buffer; + } + } + }); + } + if (Map) { + setProperty(root, 'Map', (function() { + var count = 0; + return function() { + if (count++) { + return new Map; + } + setProperty(root, 'Map', Map); + return {}; + }; + }())); + + setProperty(root.Map, 'toString', createToString('Map')); + } + setProperty(root, 'Promise', noop); + setProperty(root, 'Set', noop); + setProperty(root, 'Symbol', undefined); + setProperty(root, 'WeakMap', noop); + + // Fake `WinRTError`. + setProperty(root, 'WinRTError', Error); + + // Clear cache so lodash can be reloaded. + emptyObject(require.cache); + + // Load lodash and expose it to the bad extensions/shims. + lodashBizarro = interopRequire(filePath); + root._ = oldDash; + + // Restore built-in methods. + setProperty(Object, 'create', create); + setProperty(objectProto, 'propertyIsEnumerable', _propertyIsEnumerable); + setProperty(root, 'Buffer', Buffer); + + if (getSymbols) { + Object.getOwnPropertySymbols = getSymbols; + } else { + delete Object.getOwnPropertySymbols; + } + if (Map) { + setProperty(root, 'Map', Map); + } else { + delete root.Map; + } + if (Promise) { + setProperty(root, 'Promise', Promise); + } else { + delete root.Promise; + } + if (Set) { + setProperty(root, 'Set', Set); + } else { + delete root.Set; + } + if (Symbol) { + setProperty(root, 'Symbol', Symbol); + } else { + delete root.Symbol; + } + if (WeakMap) { + setProperty(root, 'WeakMap', WeakMap); + } else { + delete root.WeakMap; + } + delete root.WinRTError; + delete funcProto._method; +}()); + +// Add other realm values from the `vm` module. +lodashStable.attempt(function() { + lodashStable.assign(realm, require('vm').runInNewContext([ + '(function() {', + ' var noop = function() {},', + ' root = this;', + '', + ' var object = {', + " 'ArrayBuffer': root.ArrayBuffer,", + " 'arguments': (function() { return arguments; }(1, 2, 3)),", + " 'array': [1],", + " 'arrayBuffer': root.ArrayBuffer ? new root.ArrayBuffer : undefined,", + " 'boolean': Object(false),", + " 'date': new Date,", + " 'errors': [new Error, new EvalError, new RangeError, new ReferenceError, new SyntaxError, new TypeError, new URIError],", + " 'function': noop,", + " 'map': root.Map ? new root.Map : undefined,", + " 'nan': NaN,", + " 'null': null,", + " 'number': Object(0),", + " 'object': { 'a': 1 },", + " 'promise': root.Promise ? Promise.resolve(1) : undefined,", + " 'regexp': /x/,", + " 'set': root.Set ? new root.Set : undefined,", + " 'string': Object('a'),", + " 'symbol': root.Symbol ? root.Symbol() : undefined,", + " 'undefined': undefined,", + " 'weakMap': root.WeakMap ? new root.WeakMap : undefined,", + " 'weakSet': root.WeakSet ? new root.WeakSet : undefined", + ' };', + '', + " ['" + arrayViews.join("', '") + "'].forEach(function(type) {", + ' var Ctor = root[type]', + ' object[type] = Ctor;', + ' object[type.toLowerCase()] = Ctor ? new Ctor(new ArrayBuffer(24)) : undefined;', + ' });', + '', + ' return object;', + '}());' + ].join('\n'))); +}); + +// Add other realm values from an iframe. +lodashStable.attempt(function() { + _._realm = realm; + + var iframe = document.createElement('iframe'); + iframe.frameBorder = iframe.height = iframe.width = 0; + body.appendChild(iframe); + + var idoc = (idoc = iframe.contentDocument || iframe.contentWindow).document || idoc; + idoc.write([ + '', + '', + '', + '', + '' + ].join('\n')); + + idoc.close(); + delete _._realm; +}); + +// Add a web worker. +lodashStable.attempt(function() { + var worker = new Worker('./asset/worker.js?t=' + (+new Date)); + worker.addEventListener('message', function(e) { + _._VERSION = e.data || ''; + }, false); + + worker.postMessage(ui.buildPath); +}); + +// Expose internal modules for better code coverage. +lodashStable.attempt(function() { + var path = require('path'), + basePath = path.dirname(filePath); + + if (isModularize && !(amd || isNpm)) { + lodashStable.each([ + 'baseEach', + 'isIndex', + 'isIterateeCall', + 'memoizeCapped' + ], function(funcName) { + _['_' + funcName] = interopRequire(path.join(basePath, '_' + funcName)); + }); + } +}); + +export { + HOT_COUNT, + LARGE_ARRAY_SIZE, + FUNC_ERROR_TEXT, + MAX_MEMOIZE_SIZE, + MAX_SAFE_INTEGER, + MAX_INTEGER, + MAX_ARRAY_LENGTH, + MAX_ARRAY_INDEX, + funcTag, + numberTag, + objectTag, + lodashBizarro, + arrayProto, + funcProto, + objectProto, + numberProto, + stringProto, + phantom, + amd, + args, + argv, + defineProperty, + document, + body, + create, + fnToString, + freeze, + getSymbols, + identity, + noop, + objToString, + params, + push, + realm, + slice, + strictArgs, + arrayBuffer, + map, + promise, + set, + symbol, + weakMap, + weakSet, + add, + doubled, + isEven, + square, + stubA, + stubB, + stubC, + stubTrue, + stubFalse, + stubNaN, + stubNull, + stubZero, + stubOne, + stubTwo, + stubThree, + stubFour, + stubArray, + stubObject, + stubString, + burredLetters, + comboMarks, + deburredLetters, + falsey, + emojiVar, + empties, + errors, + fitzModifiers, + primitives, + typedArrays, + arrayViews, + filePath, + ui, + basename, + isModularize, + isNpm, + isPhantom, + isStrict, + Worker, + lodashStable, + _, + mapCaches, + coverage, + asyncFunc, + genFunc, + oldDash, + whitespace, + CustomError, + emptyObject, + getUnwrappedValue, + interopRequire, + setProperty, + skipAssert, + toArgs +}; diff --git a/test/values-methods.js b/test/values-methods.js new file mode 100644 index 000000000..b17f3c48b --- /dev/null +++ b/test/values-methods.js @@ -0,0 +1,47 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, args, strictArgs } from './utils.js'; + +describe('values methods', function() { + lodashStable.each(['values', 'valuesIn'], function(methodName) { + var func = _[methodName], + isValues = methodName == 'values'; + + it('`_.' + methodName + '` should get string keyed values of `object`', function() { + var object = { 'a': 1, 'b': 2 }, + actual = func(object).sort(); + + assert.deepStrictEqual(actual, [1, 2]); + }); + + it('`_.' + methodName + '` should work with an object that has a `length` property', function() { + var object = { '0': 'a', '1': 'b', 'length': 2 }, + actual = func(object).sort(); + + assert.deepStrictEqual(actual, [2, 'a', 'b']); + }); + + it('`_.' + methodName + '` should ' + (isValues ? 'not ' : '') + 'include inherited string keyed property values', function() { + function Foo() { + this.a = 1; + } + Foo.prototype.b = 2; + + var expected = isValues ? [1] : [1, 2], + actual = func(new Foo).sort(); + + assert.deepStrictEqual(actual, expected); + }); + + it('`_.' + methodName + '` should work with `arguments` objects', function() { + var values = [args, strictArgs], + expected = lodashStable.map(values, lodashStable.constant([1, 2, 3])); + + var actual = lodashStable.map(values, function(value) { + return func(value).sort(); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); +}); diff --git a/test/without.test.js b/test/without.test.js new file mode 100644 index 000000000..bad332941 --- /dev/null +++ b/test/without.test.js @@ -0,0 +1,23 @@ +import assert from 'assert'; +import without from '../without.js'; + +describe('without', function() { + it('should return the difference of values', function() { + var actual = without([2, 1, 2, 3], 1, 2); + assert.deepStrictEqual(actual, [3]); + }); + + it('should use strict equality to determine the values to reject', function() { + var object1 = { 'a': 1 }, + object2 = { 'b': 2 }, + array = [object1, object2]; + + assert.deepStrictEqual(without(array, { 'a': 1 }), array); + assert.deepStrictEqual(without(array, object1), [object2]); + }); + + it('should remove all occurrences of each value from an array', function() { + var array = [1, 2, 3, 1, 2, 3]; + assert.deepStrictEqual(without(array, 1, 2), [3, 3]); + }); +}); diff --git a/test/words.js b/test/words.js new file mode 100644 index 000000000..450d9a191 --- /dev/null +++ b/test/words.js @@ -0,0 +1,123 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { burredLetters, _, stubArray } from './utils.js'; + +describe('words', function() { + it('should match words containing Latin Unicode letters', function() { + var expected = lodashStable.map(burredLetters, function(letter) { + return [letter]; + }); + + var actual = lodashStable.map(burredLetters, function(letter) { + return _.words(letter); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should support a `pattern`', function() { + assert.deepStrictEqual(_.words('abcd', /ab|cd/g), ['ab', 'cd']); + assert.deepStrictEqual(_.words('abcd', 'ab|cd'), ['ab']); + }); + + it('should work with compound words', function() { + assert.deepStrictEqual(_.words('12ft'), ['12', 'ft']); + assert.deepStrictEqual(_.words('aeiouAreVowels'), ['aeiou', 'Are', 'Vowels']); + assert.deepStrictEqual(_.words('enable 6h format'), ['enable', '6', 'h', 'format']); + assert.deepStrictEqual(_.words('enable 24H format'), ['enable', '24', 'H', 'format']); + assert.deepStrictEqual(_.words('isISO8601'), ['is', 'ISO', '8601']); + assert.deepStrictEqual(_.words('LETTERSAeiouAreVowels'), ['LETTERS', 'Aeiou', 'Are', 'Vowels']); + assert.deepStrictEqual(_.words('tooLegit2Quit'), ['too', 'Legit', '2', 'Quit']); + assert.deepStrictEqual(_.words('walk500Miles'), ['walk', '500', 'Miles']); + assert.deepStrictEqual(_.words('xhr2Request'), ['xhr', '2', 'Request']); + assert.deepStrictEqual(_.words('XMLHttp'), ['XML', 'Http']); + assert.deepStrictEqual(_.words('XmlHTTP'), ['Xml', 'HTTP']); + assert.deepStrictEqual(_.words('XmlHttp'), ['Xml', 'Http']); + }); + + it('should work with compound words containing diacritical marks', function() { + assert.deepStrictEqual(_.words('LETTERSÆiouAreVowels'), ['LETTERS', 'Æiou', 'Are', 'Vowels']); + assert.deepStrictEqual(_.words('æiouAreVowels'), ['æiou', 'Are', 'Vowels']); + assert.deepStrictEqual(_.words('æiou2Consonants'), ['æiou', '2', 'Consonants']); + }); + + it('should not treat contractions as separate words', function() { + var postfixes = ['d', 'll', 'm', 're', 's', 't', 've']; + + lodashStable.each(["'", '\u2019'], function(apos) { + lodashStable.times(2, function(index) { + var actual = lodashStable.map(postfixes, function(postfix) { + var string = 'a b' + apos + postfix + ' c'; + return _.words(string[index ? 'toUpperCase' : 'toLowerCase']()); + }); + + var expected = lodashStable.map(postfixes, function(postfix) { + var words = ['a', 'b' + apos + postfix, 'c']; + return lodashStable.map(words, function(word) { + return word[index ? 'toUpperCase' : 'toLowerCase'](); + }); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + }); + + it('should not treat ordinal numbers as separate words', function() { + var ordinals = ['1st', '2nd', '3rd', '4th']; + + lodashStable.times(2, function(index) { + var expected = lodashStable.map(ordinals, function(ordinal) { + return [ordinal[index ? 'toUpperCase' : 'toLowerCase']()]; + }); + + var actual = lodashStable.map(expected, function(words) { + return _.words(words[0]); + }); + + assert.deepStrictEqual(actual, expected); + }); + }); + + it('should not treat mathematical operators as words', function() { + var operators = ['\xac', '\xb1', '\xd7', '\xf7'], + expected = lodashStable.map(operators, stubArray), + actual = lodashStable.map(operators, _.words); + + assert.deepStrictEqual(actual, expected); + }); + + it('should not treat punctuation as words', function() { + var marks = [ + '\u2012', '\u2013', '\u2014', '\u2015', + '\u2024', '\u2025', '\u2026', + '\u205d', '\u205e' + ]; + + var expected = lodashStable.map(marks, stubArray), + actual = lodashStable.map(marks, _.words); + + assert.deepStrictEqual(actual, expected); + }); + + it('should work as an iteratee for methods like `_.map`', function() { + var strings = lodashStable.map(['a', 'b', 'c'], Object), + actual = lodashStable.map(strings, _.words); + + assert.deepStrictEqual(actual, [['a'], ['b'], ['c']]); + }); + + it('should prevent ReDoS', function() { + var largeWordLen = 50000, + largeWord = 'A'.repeat(largeWordLen), + maxMs = 1000, + startTime = lodashStable.now(); + + assert.deepStrictEqual(_.words(largeWord + 'ÆiouAreVowels'), [largeWord, 'Æiou', 'Are', 'Vowels']); + + var endTime = lodashStable.now(), + timeSpent = endTime - startTime; + + assert.ok(timeSpent < maxMs, 'operation took ' + timeSpent + 'ms'); + }); +}); diff --git a/test/wrap.js b/test/wrap.js new file mode 100644 index 000000000..7a6bd5a45 --- /dev/null +++ b/test/wrap.js @@ -0,0 +1,46 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { noop, slice, stubA } from './utils.js'; +import wrap from '../wrap.js'; + +describe('wrap', function() { + it('should create a wrapped function', function() { + var p = wrap(lodashStable.escape, function(func, text) { + return '' + func(text) + '
'; + }); + + assert.strictEqual(p('fred, barney, & pebbles'), 'fred, barney, & pebbles
'); + }); + + it('should provide correct `wrapper` arguments', function() { + var args; + + var wrapped = wrap(noop, function() { + args || (args = slice.call(arguments)); + }); + + wrapped(1, 2, 3); + assert.deepStrictEqual(args, [noop, 1, 2, 3]); + }); + + it('should use `_.identity` when `wrapper` is nullish', function() { + var values = [, null, undefined], + expected = lodashStable.map(values, stubA); + + var actual = lodashStable.map(values, function(value, index) { + var wrapped = index ? wrap('a', value) : wrap('a'); + return wrapped('b', 'c'); + }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should use `this` binding of function', function() { + var p = wrap(lodashStable.escape, function(func) { + return '' + func(this.text) + '
'; + }); + + var object = { 'p': p, 'text': 'fred, barney, & pebbles' }; + assert.strictEqual(object.p(), 'fred, barney, & pebbles
'); + }); +}); diff --git a/test/xor-methods.js b/test/xor-methods.js new file mode 100644 index 000000000..11c31b07a --- /dev/null +++ b/test/xor-methods.js @@ -0,0 +1,70 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, args, LARGE_ARRAY_SIZE } from './utils.js'; + +describe('xor methods', function() { + lodashStable.each(['xor', 'xorBy', 'xorWith'], function(methodName) { + var func = _[methodName]; + + it('`_.' + methodName + '` should return the symmetric difference of two arrays', function() { + var actual = func([2, 1], [2, 3]); + assert.deepStrictEqual(actual, [1, 3]); + }); + + it('`_.' + methodName + '` should return the symmetric difference of multiple arrays', function() { + var actual = func([2, 1], [2, 3], [3, 4]); + assert.deepStrictEqual(actual, [1, 4]); + + actual = func([1, 2], [2, 1], [1, 2]); + assert.deepStrictEqual(actual, []); + }); + + it('`_.' + methodName + '` should return an empty array when comparing the same array', function() { + var array = [1], + actual = func(array, array, array); + + assert.deepStrictEqual(actual, []); + }); + + it('`_.' + methodName + '` should return an array of unique values', function() { + var actual = func([1, 1, 2, 5], [2, 2, 3, 5], [3, 4, 5, 5]); + assert.deepStrictEqual(actual, [1, 4]); + + actual = func([1, 1]); + assert.deepStrictEqual(actual, [1]); + }); + + it('`_.' + methodName + '` should return a new array when a single array is given', function() { + var array = [1]; + assert.notStrictEqual(func(array), array); + }); + + it('`_.' + methodName + '` should ignore individual secondary arguments', function() { + var array = [0]; + assert.deepStrictEqual(func(array, 3, null, { '0': 1 }), array); + }); + + it('`_.' + methodName + '` should ignore values that are not arrays or `arguments` objects', function() { + var array = [1, 2]; + assert.deepStrictEqual(func(array, 3, { '0': 1 }, null), array); + assert.deepStrictEqual(func(null, array, null, [2, 3]), [1, 3]); + assert.deepStrictEqual(func(array, null, args, null), [3]); + }); + + it('`_.' + methodName + '` should return a wrapped value when chaining', function() { + var wrapped = _([1, 2, 3])[methodName]([5, 2, 1, 4]); + assert.ok(wrapped instanceof _); + }); + + it('`_.' + methodName + '` should work when in a lazy sequence before `head` or `last`', function() { + var array = lodashStable.range(LARGE_ARRAY_SIZE + 1), + wrapped = _(array).slice(1)[methodName]([LARGE_ARRAY_SIZE, LARGE_ARRAY_SIZE + 1]); + + var actual = lodashStable.map(['head', 'last'], function(methodName) { + return wrapped[methodName](); + }); + + assert.deepEqual(actual, [1, LARGE_ARRAY_SIZE + 1]); + }); + }); +}); diff --git a/test/xorBy.js b/test/xorBy.js new file mode 100644 index 000000000..02b9250c6 --- /dev/null +++ b/test/xorBy.js @@ -0,0 +1,23 @@ +import assert from 'assert'; +import { slice } from './utils.js'; +import xorBy from '../xorBy.js'; + +describe('xorBy', function() { + it('should accept an `iteratee`', function() { + var actual = xorBy([2.1, 1.2], [2.3, 3.4], Math.floor); + assert.deepStrictEqual(actual, [1.2, 3.4]); + + actual = xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); + assert.deepStrictEqual(actual, [{ 'x': 2 }]); + }); + + it('should provide correct `iteratee` arguments', function() { + var args; + + xorBy([2.1, 1.2], [2.3, 3.4], function() { + args || (args = slice.call(arguments)); + }); + + assert.deepStrictEqual(args, [2.3]); + }); +}); diff --git a/test/xorWith.test.js b/test/xorWith.test.js new file mode 100644 index 000000000..83551e2cd --- /dev/null +++ b/test/xorWith.test.js @@ -0,0 +1,13 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import xorWith from '../xorWith.js'; + +describe('xorWith', function() { + it('should work with a `comparator`', function() { + var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }], + others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }], + actual = xorWith(objects, others, lodashStable.isEqual); + + assert.deepStrictEqual(actual, [objects[1], others[0]]); + }); +}); diff --git a/test/zipObject-methods.js b/test/zipObject-methods.js new file mode 100644 index 000000000..02c4fbaa7 --- /dev/null +++ b/test/zipObject-methods.js @@ -0,0 +1,39 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { _, LARGE_ARRAY_SIZE, square, isEven } from './utils.js'; + +describe('zipObject methods', function() { + lodashStable.each(['zipObject', 'zipObjectDeep'], function(methodName) { + var func = _[methodName], + object = { 'barney': 36, 'fred': 40 }, + isDeep = methodName == 'zipObjectDeep'; + + it('`_.' + methodName + '` should zip together key/value arrays into an object', function() { + var actual = func(['barney', 'fred'], [36, 40]); + assert.deepStrictEqual(actual, object); + }); + + it('`_.' + methodName + '` should ignore extra `values`', function() { + assert.deepStrictEqual(func(['a'], [1, 2]), { 'a': 1 }); + }); + + it('`_.' + methodName + '` should assign `undefined` values for extra `keys`', function() { + assert.deepStrictEqual(func(['a', 'b'], [1]), { 'a': 1, 'b': undefined }); + }); + + it('`_.' + methodName + '` should ' + (isDeep ? '' : 'not ') + 'support deep paths', function() { + lodashStable.each(['a.b.c', ['a', 'b', 'c']], function(path, index) { + var expected = isDeep ? ({ 'a': { 'b': { 'c': 1 } } }) : (index ? { 'a,b,c': 1 } : { 'a.b.c': 1 }); + assert.deepStrictEqual(func([path], [1]), expected); + }); + }); + + it('`_.' + methodName + '` should work in a lazy sequence', function() { + var values = lodashStable.range(LARGE_ARRAY_SIZE), + props = lodashStable.map(values, function(value) { return 'key' + value; }), + actual = _(props)[methodName](values).map(square).filter(isEven).take().value(); + + assert.deepEqual(actual, _.take(_.filter(_.map(func(props, values), square), isEven))); + }); + }); +}); diff --git a/test/zipWith.js b/test/zipWith.js new file mode 100644 index 000000000..f6faaa460 --- /dev/null +++ b/test/zipWith.js @@ -0,0 +1,48 @@ +import assert from 'assert'; +import lodashStable from 'lodash'; +import { slice } from './utils.js'; +import zipWith from '../zipWith.js'; +import zip from '../zip.js'; + +describe('zipWith', function() { + it('should zip arrays combining grouped elements with `iteratee`', function() { + var array1 = [1, 2, 3], + array2 = [4, 5, 6], + array3 = [7, 8, 9]; + + var actual = zipWith(array1, array2, array3, function(a, b, c) { + return a + b + c; + }); + + assert.deepStrictEqual(actual, [12, 15, 18]); + + var actual = zipWith(array1, [], function(a, b) { + return a + (b || 0); + }); + + assert.deepStrictEqual(actual, [1, 2, 3]); + }); + + it('should provide correct `iteratee` arguments', function() { + var args; + + zipWith([1, 2], [3, 4], [5, 6], function() { + args || (args = slice.call(arguments)); + }); + + assert.deepStrictEqual(args, [1, 3, 5]); + }); + + it('should perform a basic zip when `iteratee` is nullish', function() { + var array1 = [1, 2], + array2 = [3, 4], + values = [, null, undefined], + expected = lodashStable.map(values, lodashStable.constant(zip(array1, array2))); + + var actual = lodashStable.map(values, function(value, index) { + return index ? zipWith(array1, array2, value) : zipWith(array1, array2); + }); + + assert.deepStrictEqual(actual, expected); + }); +});