From 43037c0ff98b06003289d108912a45bbfb7d2821 Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Sun, 7 Apr 2013 15:10:03 -0700 Subject: [PATCH] Ensure "Arrays" and "Objects" methods work with `arguments` objects and arrays respectively. Former-commit-id: aebb7a0004d804b7fd43d73e24d1da28c67f4059 --- build.js | 14 ++++----- lodash.js | 34 +++++++++++---------- test/test.js | 84 ++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 100 insertions(+), 32 deletions(-) diff --git a/build.js b/build.js index 1911a1578..9bffd1162 100755 --- a/build.js +++ b/build.js @@ -168,7 +168,7 @@ 'times': ['createCallback'], 'toArray': ['isString', 'values'], 'unescape': [], - 'union': ['uniq'], + 'union': ['isArray', 'uniq'], 'uniq': ['createCallback', 'indexOf'], 'uniqueId': [], 'unzip': ['max', 'pluck'], @@ -2030,12 +2030,12 @@ 'function difference(array) {', ' var index = -1,', ' length = array.length,', - ' flattened = concat.apply(arrayRef, arguments),', + ' flattened = concat.apply(arrayRef, nativeSlice.call(arguments, 1)),', ' result = [];', '', ' while (++index < length) {', ' var value = array[index];', - ' if (indexOf(flattened, value, length) < 0) {', + ' if (indexOf(flattened, value) < 0) {', ' result.push(value);', ' }', ' }', @@ -2219,11 +2219,11 @@ // replace `_.omit` source = replaceFunction(source, 'omit', [ 'function omit(object) {', - ' var props = concat.apply(arrayRef, arguments),', + ' var props = concat.apply(arrayRef, nativeSlice.call(arguments, 1)),', ' result = {};', '', ' forIn(object, function(value, key) {', - ' if (indexOf(props, key, 1) < 0) {', + ' if (indexOf(props, key) < 0) {', ' result[key] = value;', ' }', ' });', @@ -2234,8 +2234,8 @@ // replace `_.pick` source = replaceFunction(source, 'pick', [ 'function pick(object) {', - ' var index = 0,', - ' props = concat.apply(arrayRef, arguments),', + ' var index = -1,', + ' props = concat.apply(arrayRef, nativeSlice.call(arguments, 1)),', ' length = props.length,', ' result = {};', '', diff --git a/lodash.js b/lodash.js index a2ee82fdc..30114e7e2 100644 --- a/lodash.js +++ b/lodash.js @@ -580,16 +580,15 @@ * @private * @param {Array} array The array to search. * @param {Mixed} value The value to search for. - * @param {Number} fromIndex The index to search from. * @returns {Boolean} Returns `true`, if `value` is found, else `false`. */ - function cachedContains(array, fromIndex) { + function cachedContains(array) { var length = array.length, - isLarge = (length - fromIndex) >= largeArraySize; + isLarge = length >= largeArraySize; if (isLarge) { var cache = {}, - index = fromIndex - 1; + index = -1; while (++index < length) { var key = keyPrefix + array[index]; @@ -601,7 +600,7 @@ var key = keyPrefix + value; return cache[key] && indexOf(cache[key], value) > -1; } - return indexOf(array, value, fromIndex) > -1; + return indexOf(array, value) > -1; } } @@ -2111,12 +2110,12 @@ if (isFunc) { callback = lodash.createCallback(callback, thisArg); } else { - var props = concat.apply(arrayRef, arguments); + var props = concat.apply(arrayRef, nativeSlice.call(arguments, 1)); } forIn(object, function(value, key, object) { if (isFunc ? !callback(value, key, object) - : indexOf(props, key, 1) < 0 + : indexOf(props, key) < 0 ) { result[key] = value; } @@ -2179,8 +2178,8 @@ function pick(object, callback, thisArg) { var result = {}; if (typeof callback != 'function') { - var index = 0, - props = concat.apply(arrayRef, arguments), + var index = -1, + props = concat.apply(arrayRef, nativeSlice.call(arguments, 1)), length = isObject(object) ? props.length : 0; while (++index < length) { @@ -3292,8 +3291,8 @@ function difference(array) { var index = -1, length = array ? array.length : 0, - flattened = concat.apply(arrayRef, arguments), - contains = cachedContains(flattened, length), + flattened = concat.apply(arrayRef, nativeSlice.call(arguments, 1)), + contains = cachedContains(flattened), result = []; while (++index < length) { @@ -3643,7 +3642,7 @@ } var argsIndex = argsLength; while (--argsIndex) { - if (!(cache[argsIndex] || (cache[argsIndex] = cachedContains(args[argsIndex], 0)))(value)) { + if (!(cache[argsIndex] || (cache[argsIndex] = cachedContains(args[argsIndex])))(value)) { continue outer; } } @@ -3967,8 +3966,11 @@ * _.union([1, 2, 3], [101, 2, 1, 10], [2, 1]); * // => [1, 2, 3, 101, 10] */ - function union() { - return uniq(concat.apply(arrayRef, arguments)); + function union(array) { + return uniq(isArray(array) + ? concat.apply(arrayRef, arguments) + : concat.apply(array ? nativeSlice.call(array) : arrayRef, nativeSlice.call(arguments, 1)) + ); } /** @@ -4258,8 +4260,8 @@ * // => alerts 'clicked docs', when the button is clicked */ function bindAll(object) { - var funcs = concat.apply(arrayRef, arguments), - index = funcs.length > 1 ? 0 : (funcs = functions(object), -1), + var funcs = arguments.length > 1 ? concat.apply(arrayRef, nativeSlice.call(arguments, 1)) : functions(object), + index = -1, length = funcs.length; while (++index < length) { diff --git a/test/test.js b/test/test.js index c6d768dc9..a32bb527c 100644 --- a/test/test.js +++ b/test/test.js @@ -196,6 +196,8 @@ QUnit.module('lodash.at'); (function() { + var args = arguments; + test('should return `undefined` for nonexistent keys', function() { var actual = _.at(['a', 'b', 'c'], [0, 2, 4]); deepEqual(actual, ['a', 'c', undefined]); @@ -210,6 +212,11 @@ deepEqual(actual, ['a', 'c', 'd']); }); + test('should work with an `arguments` object for `collection`', function() { + var actual = _.at(args, [0, 2]); + deepEqual(actual, ['a', 'c']); + }); + test('should work with an object for `collection`', function() { var actual = _.at({ 'a': 1, 'b': 2, 'c': 3 }, ['a', 'c']); deepEqual(actual, [1, 3]); @@ -224,7 +231,7 @@ deepEqual(_.at(collection, [0, 2]), ['a', 'c']); }); }); - }()); + }('a', 'b', 'c')); /*--------------------------------------------------------------------------*/ @@ -295,6 +302,12 @@ deepEqual(actual, [1, 2, 3, undefined]); }); + + test('should work with an array `object` argument', function() { + var array = ['push', 'pop']; + _.bindAll(array); + equal(array.pop, Array.prototype.pop); + }); }()); /*--------------------------------------------------------------------------*/ @@ -334,7 +347,7 @@ }; var objects = { - 'an arguments object': arguments, + 'an `arguments` object': arguments, 'an array': ['a', 'b', 'c', ''], 'an array-like-object': { '0': 'a', '1': 'b', '2': 'c', '3': '', 'length': 5 }, 'boolean': false, @@ -1900,6 +1913,10 @@ deepEqual(_.omit(new Foo, 'a'), expected); }); + test('should work with an array `object` argument', function() { + deepEqual(_.omit([1, 2, 3], '0', '2'), { '1': 2 }); + }); + test('should work with a `callback` argument', function() { var actual = _.omit(object, function(value) { return value == 1; @@ -2024,6 +2041,10 @@ deepEqual(_.pick(new Foo, 'b'), { 'b': 2 }); }); + test('should work with an array `object` argument', function() { + deepEqual(_.pick([1, 2, 3], '1'), { '1': 2 }); + }); + test('should work with a `callback` argument', function() { var actual = _.pick(object, function(value) { return value == 2; @@ -2855,6 +2876,19 @@ /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.union'); + + (function() { + test('should produce correct results when passed a falsey `array` argument', function() { + var expected = [1, 2, 3], + actual = _.union(null, expected); + + deepEqual(actual, expected); + }); + }()); + + /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.uniq'); (function() { @@ -2867,10 +2901,14 @@ }); test('should distinguish between numbers and numeric strings', function() { - var expected = ['2', 2, Object('2'), Object(2)], - actual = _.uniq(expected); + var array = [], + expected = ['2', 2, Object('2'), Object(2)]; - deepEqual(actual, expected); + _.times(50, function() { + array.push.apply(array, expected); + }); + + deepEqual(_.uniq(expected), expected); }); _.each({ @@ -3167,6 +3205,37 @@ /*--------------------------------------------------------------------------*/ + QUnit.module('"Arrays" methods'); + + (function() { + var args = arguments; + + test('should work with `arguments` objects', function() { + function message(methodName) { + return '_.' + methodName + ' should work with `arguments` objects'; + } + + deepEqual(_.compact(args), [1, [3], 5], message('compact')); + deepEqual(_.difference(args, [null]), [1, [3], 5], message('difference')); + deepEqual(_.findIndex(args, _.identity), 0, message('findIndex')); + deepEqual(_.first(args), 1, message('first')); + deepEqual(_.flatten(args), [1, null, 3, null, 5], message('flatten')); + deepEqual(_.indexOf(args, 5), 4, message('indexOf')); + deepEqual(_.initial(args, 4), [1], message('initial')); + deepEqual(_.intersection(args, [1]), [1], message('intersection')); + deepEqual(_.last(args), 5, message('last')); + deepEqual(_.lastIndexOf(args, 1), 0, message('lastIndexOf')); + deepEqual(_.rest(args, 4), [5], message('rest')); + deepEqual(_.sortedIndex(args, 6), 5, message('sortedIndex')); + deepEqual(_.union(args, [null, 6]), [1, null, [3], 5, 6], message('union')); + deepEqual(_.uniq(args), [1, null, [3], 5], message('uniq')); + deepEqual(_.without(args, null), [1, [3], 5], message('without')); + deepEqual(_.zip(args, args), [[1, 1], [null, null], [[3], [3]], [null, null], [5, 5]], message('zip')); + }); + }(1, null, [3], null, 5)); + + /*--------------------------------------------------------------------------*/ + QUnit.module('lodash methods'); (function() { @@ -3227,13 +3296,10 @@ _.each(funcs, function(methodName) { var actual = [], + expected = _.map(falsey, function() { return []; }), func = _[methodName], pass = true; - var expected = (methodName == 'union') - ? _.map(falsey, function(value, index) { return index ? [value] : []; }) - : _.map(falsey, function() { return []; }); - _.each(falsey, function(value, index) { try { actual.push(index ? func(value) : func());