From bd97f6ce794da837bdc2ccca8571163a399a1d30 Mon Sep 17 00:00:00 2001 From: megawac Date: Tue, 15 Apr 2014 13:45:03 -0400 Subject: [PATCH 1/5] Abstract iterator binding to createIterator Long coming optimization discussed in 1525, 1561 and elsewhere. Follows lodash's lead. --- underscore.js | 81 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 25 deletions(-) diff --git a/underscore.js b/underscore.js index 62beac1ba..2470925e2 100644 --- a/underscore.js +++ b/underscore.js @@ -58,6 +58,35 @@ // Current version. _.VERSION = '1.6.0'; + // Internal function: creates a callback bound to its context if supplied + var createCallback = function(func, context, argCount) { + if (!context) return func; + switch (argCount == null ? 3 : argCount) { + case 1: return function(value) { + return func.call(context, value); + }; + case 2: return function(value, other) { + return func.call(context, value, other); + }; + case 3: return function(value, index, collection) { + return func.call(context, value, index, collection); + }; + case 4: return function(accumulator, value, index, collection) { + return func.call(context, accumulator, value, index, collection); + }; + } + return function() { + return func.apply(this, arguments); + }; + }; + + // An internal function to generate lookup iterators. + var lookupIterator = function(value, context, argCount) { + if (value == null) return _.identity; + if (!_.isFunction(value)) return _.property(value); + return createCallback(value, context, argCount); + }; + // Collection Functions // -------------------- @@ -66,14 +95,15 @@ // sparse array-likes as if they were dense. _.each = _.forEach = function(obj, iterator, context) { if (obj == null) return obj; + iterator = createCallback(iterator, context); if (obj.length === +obj.length) { for (var i = 0, length = obj.length; i < length; i++) { - if (iterator.call(context, obj[i], i, obj) === breaker) return; + if (iterator(obj[i], i, obj) === breaker) return; } } else { var keys = _.keys(obj); for (var i = 0, length = keys.length; i < length; i++) { - if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return; + if (iterator(obj[keys[i]], keys[i], obj) === breaker) return; } } return obj; @@ -83,8 +113,9 @@ _.map = _.collect = function(obj, iterator, context) { var results = []; if (obj == null) return results; + iterator = createCallback(iterator, context); _.each(obj, function(value, index, list) { - results.push(iterator.call(context, value, index, list)); + results.push(iterator(value, index, list)); }); return results; }; @@ -96,12 +127,13 @@ _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { var initial = arguments.length > 2; if (obj == null) obj = []; + iterator = createCallback(iterator, context, 4); _.each(obj, function(value, index, list) { if (!initial) { memo = value; initial = true; } else { - memo = iterator.call(context, memo, value, index, list); + memo = iterator(memo, value, index, list); } }); if (!initial) throw new TypeError(reduceError); @@ -113,6 +145,7 @@ var initial = arguments.length > 2; if (obj == null) obj = []; var length = obj.length; + iterator = createCallback(iterator, context, 4); if (length !== +length) { var keys = _.keys(obj); length = keys.length; @@ -123,7 +156,7 @@ memo = obj[index]; initial = true; } else { - memo = iterator.call(context, memo, obj[index], index, list); + memo = iterator(memo, obj[index], index, list); } }); if (!initial) throw new TypeError(reduceError); @@ -133,8 +166,9 @@ // Return the first value which passes a truth test. Aliased as `detect`. _.find = _.detect = function(obj, predicate, context) { var result; + predicate = createCallback(predicate, context); _.some(obj, function(value, index, list) { - if (predicate.call(context, value, index, list)) { + if (predicate(value, index, list)) { result = value; return true; } @@ -147,8 +181,9 @@ _.filter = _.select = function(obj, predicate, context) { var results = []; if (obj == null) return results; + predicate = createCallback(predicate, context); _.each(obj, function(value, index, list) { - if (predicate.call(context, value, index, list)) results.push(value); + if (predicate(value, index, list)) results.push(value); }); return results; }; @@ -164,8 +199,9 @@ predicate || (predicate = _.identity); var result = true; if (obj == null) return result; + predicate = createCallback(predicate, context); _.each(obj, function(value, index, list) { - if (!(result = result && predicate.call(context, value, index, list))) return breaker; + if (!(result = result && predicate(value, index, list))) return breaker; }); return !!result; }; @@ -176,8 +212,9 @@ predicate || (predicate = _.identity); var result = false; if (obj == null) return result; + predicate = createCallback(predicate, context); _.each(obj, function(value, index, list) { - if (result || (result = predicate.call(context, value, index, list))) return breaker; + if (result || (result = predicate(value, index, list))) return breaker; }); return !!result; }; @@ -230,8 +267,9 @@ } } } else { + iterator = createCallback(iterator, context); _.each(obj, function(value, index, list) { - computed = iterator ? iterator.call(context, value, index, list) : value; + computed = iterator ? iterator(value, index, list) : value; if (computed > lastComputed || (computed === -Infinity && result === -Infinity)) { result = value; lastComputed = computed; @@ -253,8 +291,9 @@ } } } else { + iterator = createCallback(iterator); _.each(obj, function(value, index, list) { - computed = iterator ? iterator.call(context, value, index, list) : value; + computed = iterator ? iterator(value, index, list) : value; if (computed < lastComputed || (computed === Infinity && result === Infinity)) { result = value; lastComputed = computed; @@ -289,16 +328,6 @@ return _.shuffle(obj).slice(0, Math.max(0, n)); }; - // An internal function to generate lookup iterators. - var lookupIterator = function(value, context) { - if (value == null) return _.identity; - if (!_.isFunction(value)) return _.property(value); - if (!context) return value; - return function() { - return value.apply(context, arguments); - }; - }; - // Sort the object's values by a criterion produced by an iterator. _.sortBy = function(obj, iterator, context) { iterator = lookupIterator(iterator, context); @@ -354,7 +383,7 @@ // Use a comparator function to figure out the smallest index at which // an object should be inserted so as to maintain order. Uses binary search. _.sortedIndex = function(array, obj, iterator, context) { - iterator = lookupIterator(iterator, context); + iterator = lookupIterator(iterator, context, 1); var value = iterator(obj); var low = 0, high = array.length; while (low < high) { @@ -451,7 +480,7 @@ // Split an array into two arrays: one whose elements all satisfy the given // predicate, and one whose elements all do not satisfy the predicate. _.partition = function(obj, predicate, context) { - predicate = lookupIterator(predicate, context); + predicate = lookupIterator(predicate, context, 1); var pass = [], fail = []; _.each(obj, function(value, key, obj) { (predicate(value, key, obj) ? pass : fail).push(value); @@ -469,11 +498,12 @@ iterator = isSorted; isSorted = false; } + if (iterator) iterator = createCallback(iterator, context); var result = []; var seen = []; for (var i = 0, length = array.length; i < length; i++) { var value = array[i]; - if (iterator) value = iterator.call(context, value, i, array); + if (iterator) value = iterator(value, i, array); if (isSorted ? (!i || seen !== value) : !_.contains(seen, value)) { if (isSorted) seen = value; else seen.push(value); @@ -1119,7 +1149,8 @@ // Run a function **n** times. _.times = function(n, iterator, context) { var accum = Array(Math.max(0, n)); - for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); + iterator = createCallback(iterator, context, 1); + for (var i = 0; i < n; i++) accum[i] = iterator(i); return accum; }; From 4b1096c69b0136f3d3e587dd7e1cd002d555b413 Mon Sep 17 00:00:00 2001 From: megawac Date: Tue, 15 Apr 2014 14:57:57 -0400 Subject: [PATCH 2/5] Closes #1561; add lookupIterators as appropriate See included tests and lodash docs for some examples LookupIterators maps string properties (see #1557) and objects to _.matches. As a result several of the helper functions such as _.where, _.pluck, _.findWhere are handled by their parent implementations. --- test/collections.js | 85 ++++++++++++++++++++++++++++++++++++++++++--- underscore.js | 32 ++++++++--------- 2 files changed, 95 insertions(+), 22 deletions(-) diff --git a/test/collections.js b/test/collections.js index ad9fa4bc2..e3116bb3d 100644 --- a/test/collections.js +++ b/test/collections.js @@ -68,6 +68,10 @@ var ifnull = _.map(null, function(){}); ok(_.isArray(ifnull) && ifnull.length === 0, 'handles a null properly'); + + //can be used like _.pluck + var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}]; + deepEqual(_.map(people, 'name'), ['moe', 'curly'], 'predicate string map to object properties'); }); test('reduce', function() { @@ -167,6 +171,17 @@ var array = [1, 2, 3, 4]; strictEqual(_.find(array, function(n) { return n > 2; }), 3, 'should return first found `value`'); strictEqual(_.find(array, function() { return false; }), void 0, 'should return `undefined` if `value` is not found'); + + //can be used like findwhere + var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}, {a: 2, b: 4}]; + var result = _.find(list, {a: 1}, 'can be used as findWhere'); + deepEqual(result, {a: 1, b: 2}); + result = _.find(list, {b: 4}); + deepEqual(result, {a: 1, b: 4}); + result = _.find(list, {c:1}); + ok(_.isUndefined(result), 'undefined when not found'); + result = _.find([], {c:1}); + ok(_.isUndefined(result), 'undefined when searching empty list'); }); test('detect', function() { @@ -177,9 +192,27 @@ test('select', function() { var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); deepEqual(evens, [2, 4, 6], 'selected each even number'); + }); - evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); - deepEqual(evens, [2, 4, 6], 'aliased as "filter"'); + test('filter', function() { + var evenArray = [1, 2, 3, 4, 5, 6]; + var evenObject = {one: 1, two: 2, three: 3}; + var isEven = function(num){ return num % 2 === 0; }; + + deepEqual(_.filter(evenArray, isEven), [2, 4, 6], 'aliased as "filter"'); + deepEqual(_.filter(evenObject, isEven), [2]); + deepEqual(_.filter([{}, evenObject, []], 'two'), [evenObject], 'predicate string map to object properties'); + + //can be used like where + var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]; + var result = _.filter(list, {a: 1}, 'can be used like _.where'); + equal(result.length, 3); + equal(result[result.length - 1].b, 4); + result = _.filter(list, {b: 2}); + equal(result.length, 2); + equal(result[0].a, 1); + result = _.filter(list, {}); + equal(result.length, list.length); }); test('reject', function() { @@ -193,6 +226,22 @@ return num % 2 != 0; }, context); deepEqual(evens, [2, 4, 6], 'rejected each odd number'); + + var evenObject = {one: 1, two: 2, three: 3}; + deepEqual(_.reject([odds, evenObject], 'two'), [odds], 'predicate string map to object properties'); + }); + + test('reject given object (ala rejectWhere)', function() { + //can be used like where + var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]; + var result = _.reject(list, {a: 1}); + equal(result.length, 1); + equal(result[result.length - 1].b, 2); + result = _.reject(list, {b: 2}); + equal(result.length, 2); + equal(result[0].a, 1); + result = _.reject(list, {}); + equal(result.length, list.length); }); test('all', function() { @@ -205,6 +254,13 @@ ok(_.all([0], _.identity) === false, 'cast to boolean - false'); ok(_.every([true, true, true], _.identity), 'aliased as "every"'); ok(!_.all([undefined, undefined, undefined], _.identity), 'works with arrays of undefined'); + + var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]; + var list2 = [{a: 1, b: 2}, {a: 2, b: 2, c: true}]; + ok(!_.all(list, {a: 1, b: 2}), 'Can be called with object'); + ok(_.all(list, 'a'), 'String mapped to object property'); + ok(_.all(list2, {b: 2}), 'Can be called with object'); + ok(!_.all(list2, 'c'), 'String mapped to object property'); }); test('any', function() { @@ -218,6 +274,20 @@ ok(_.any([1], _.identity) === true, 'cast to boolean - true'); ok(_.any([0], _.identity) === false, 'cast to boolean - false'); ok(_.some([false, false, true]), 'aliased as "some"'); + + var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]; + var list2 = [{a: 1, b: 2}, {a: 2, b: 2, c: true}]; + ok(!_.all(list, {a: 1, b: 2}), 'Can be called with object'); + ok(_.all(list, 'a'), 'String mapped to object property'); + ok(_.all(list2, {b: 2}), 'Can be called with object'); + ok(!_.all(list2, 'c'), 'String mapped to object property'); + + var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]; + var list2 = [{a: 1, b: 2}, {a: 2, b: 2, c: true}]; + ok(!_.any(list, {a: 5, b: 2}), 'Can be called with object'); + ok(_.any(list, 'a'), 'String mapped to object property'); + ok(_.any(list2, {b: 2}), 'Can be called with object'); + ok(!_.any(list2, 'd'), 'String mapped to object property'); }); test('include', function() { @@ -280,7 +350,7 @@ result = _.findWhere(list, {b: 4}); deepEqual(result, {a: 1, b: 4}); - result = _.findWhere(list, {c: 1}) + result = _.findWhere(list, {c: 1}); ok(_.isUndefined(result), 'undefined when not found'); result = _.findWhere([], {c: 1}); @@ -368,6 +438,9 @@ deepEqual(actual, collection, 'sortBy should be stable'); + var actual2 = _.sortBy(collection, 'x'); + deepEqual(actual, collection, 'sortBy should be stable'); + var list = ['q', 'w', 'e', 'r', 't', 'y']; deepEqual(_.sortBy(list), ['e', 'q', 'r', 't', 'w', 'y'], 'uses _.identity if iterator is not specified'); }); @@ -405,8 +478,8 @@ [1, 3], [2, 3] ]; - deepEqual(_.groupBy(matrix, 0), {1: [[1, 2], [1, 3]], 2: [[2, 3]]}) - deepEqual(_.groupBy(matrix, 1), {2: [[1, 2]], 3: [[1, 3], [2, 3]]}) + deepEqual(_.groupBy(matrix, 0), {1: [[1, 2], [1, 3]], 2: [[2, 3]]}); + deepEqual(_.groupBy(matrix, 1), {2: [[1, 2]], 3: [[1, 3], [2, 3]]}); }); test('indexBy', function() { @@ -549,6 +622,8 @@ // Context var predicate = function(x){ return x === this.x }; deepEqual(_.partition([1, 2, 3], predicate, {x: 2}), [[2], [1, 3]], 'partition takes a context argument'); + + deepEqual(_.partition([{a: 1}, {b: 2}, {a: 1, b: 2}], {a: 1}), [[{a: 1}, {a: 1, b: 2}], [{b: 2}]], 'predicate can be object'); }); })(); diff --git a/underscore.js b/underscore.js index 2470925e2..13456915b 100644 --- a/underscore.js +++ b/underscore.js @@ -83,8 +83,9 @@ // An internal function to generate lookup iterators. var lookupIterator = function(value, context, argCount) { if (value == null) return _.identity; - if (!_.isFunction(value)) return _.property(value); - return createCallback(value, context, argCount); + if (_.isFunction(value)) return createCallback(value, context, argCount); + if (_.isObject(value)) return _.matches(value); + return _.property(value); }; // Collection Functions @@ -113,7 +114,7 @@ _.map = _.collect = function(obj, iterator, context) { var results = []; if (obj == null) return results; - iterator = createCallback(iterator, context); + iterator = lookupIterator(iterator, context); _.each(obj, function(value, index, list) { results.push(iterator(value, index, list)); }); @@ -166,7 +167,7 @@ // Return the first value which passes a truth test. Aliased as `detect`. _.find = _.detect = function(obj, predicate, context) { var result; - predicate = createCallback(predicate, context); + predicate = lookupIterator(predicate, context); _.some(obj, function(value, index, list) { if (predicate(value, index, list)) { result = value; @@ -181,7 +182,7 @@ _.filter = _.select = function(obj, predicate, context) { var results = []; if (obj == null) return results; - predicate = createCallback(predicate, context); + predicate = lookupIterator(predicate, context); _.each(obj, function(value, index, list) { if (predicate(value, index, list)) results.push(value); }); @@ -190,7 +191,8 @@ // Return all the elements for which a truth test fails. _.reject = function(obj, predicate, context) { - return _.filter(obj, _.negate(predicate), context); + if (typeof predicate == 'object' && _.isEmpty(predicate)) return _.filter(obj, predicate); + return _.filter(obj, _.negate(lookupIterator(predicate)), context); }; // Determine whether all of the elements match a truth test. @@ -199,7 +201,7 @@ predicate || (predicate = _.identity); var result = true; if (obj == null) return result; - predicate = createCallback(predicate, context); + predicate = lookupIterator(predicate, context); _.each(obj, function(value, index, list) { if (!(result = result && predicate(value, index, list))) return breaker; }); @@ -212,7 +214,7 @@ predicate || (predicate = _.identity); var result = false; if (obj == null) return result; - predicate = createCallback(predicate, context); + predicate = lookupIterator(predicate, context); _.each(obj, function(value, index, list) { if (result || (result = predicate(value, index, list))) return breaker; }); @@ -239,9 +241,7 @@ }; // Convenience version of a common use case of `map`: fetching a property. - _.pluck = function(obj, key) { - return _.map(obj, _.property(key)); - }; + _.pluck = _.map; // Convenience version of a common use case of `filter`: selecting only objects // containing specific `key:value` pairs. @@ -251,9 +251,7 @@ // Convenience version of a common use case of `find`: getting the first object // containing specific `key:value` pairs. - _.findWhere = function(obj, attrs) { - return _.find(obj, _.matches(attrs)); - }; + _.findWhere = _.find; // Return the maximum element or (element-based computation). _.max = function(obj, iterator, context) { @@ -267,7 +265,7 @@ } } } else { - iterator = createCallback(iterator, context); + iterator = lookupIterator(iterator, context); _.each(obj, function(value, index, list) { computed = iterator ? iterator(value, index, list) : value; if (computed > lastComputed || (computed === -Infinity && result === -Infinity)) { @@ -291,7 +289,7 @@ } } } else { - iterator = createCallback(iterator); + iterator = lookupIterator(iterator); _.each(obj, function(value, index, list) { computed = iterator ? iterator(value, index, list) : value; if (computed < lastComputed || (computed === Infinity && result === Infinity)) { @@ -498,7 +496,7 @@ iterator = isSorted; isSorted = false; } - if (iterator) iterator = createCallback(iterator, context); + if (iterator) iterator = lookupIterator(iterator, context); var result = []; var seen = []; for (var i = 0, length = array.length; i < length; i++) { From e52fd55e264394f11622522b6cc1acc061a372d6 Mon Sep 17 00:00:00 2001 From: megawac Date: Thu, 17 Apr 2014 15:58:31 -0400 Subject: [PATCH 3/5] Edge case compat for findWhere, where, pluck As discussed in PR with jdalton and @bradunbar --- test/collections.js | 44 +++++++++++++++++++++++++++++--------------- underscore.js | 9 ++++++--- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/test/collections.js b/test/collections.js index e3116bb3d..112e189ec 100644 --- a/test/collections.js +++ b/test/collections.js @@ -174,13 +174,13 @@ //can be used like findwhere var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}, {a: 2, b: 4}]; - var result = _.find(list, {a: 1}, 'can be used as findWhere'); - deepEqual(result, {a: 1, b: 2}); + var result = _.find(list, {a: 1}); + deepEqual(result, {a: 1, b: 2}, 'can be used as findWhere'); result = _.find(list, {b: 4}); deepEqual(result, {a: 1, b: 4}); - result = _.find(list, {c:1}); + result = _.find(list, {c: 1}); ok(_.isUndefined(result), 'undefined when not found'); - result = _.find([], {c:1}); + result = _.find([], {c: 1}); ok(_.isUndefined(result), 'undefined when searching empty list'); }); @@ -205,14 +205,12 @@ //can be used like where var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]; - var result = _.filter(list, {a: 1}, 'can be used like _.where'); - equal(result.length, 3); + var result = _.filter(list, {a: 1}); + equal(result.length, 3, 'can be used like _.where'); equal(result[result.length - 1].b, 4); - result = _.filter(list, {b: 2}); - equal(result.length, 2); - equal(result[0].a, 1); + deepEqual(_.filter(list, {b: 2}), [{a: 1, b: 2}, {a: 2, b: 2}]); result = _.filter(list, {}); - equal(result.length, list.length); + deepEqual(result, list, 'Empty object accepts all items'); }); test('reject', function() { @@ -240,8 +238,8 @@ result = _.reject(list, {b: 2}); equal(result.length, 2); equal(result[0].a, 1); - result = _.reject(list, {}); - equal(result.length, list.length); + deepEqual(_.reject(list, {}), [], 'Returns empty list given empty object'); + deepEqual(_.reject(list, []), [], 'Returns empty list given empty object'); }); test('all', function() { @@ -327,8 +325,10 @@ }); test('pluck', function() { - var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}]; + var people = [{name: 'moe', age: 30}, {name: 'curly', age: 50}]; deepEqual(_.pluck(people, 'name'), ['moe', 'curly'], 'pulls names out of objects'); + //compat: most flexible handling of edge cases + deepEqual(_.pluck([{'[object Window]': 1}], window), [1]); }); test('where', function() { @@ -341,6 +341,10 @@ equal(result[0].a, 1); result = _.where(list, {}); equal(result.length, list.length); + + function test() {} + test.map = _.map; + deepEqual(_.where([_, {a: 1, b: 2}, _], test), [_, _], 'checks properties given function'); }); test('findWhere', function() { @@ -355,6 +359,17 @@ result = _.findWhere([], {c: 1}); ok(_.isUndefined(result), 'undefined when searching empty list'); + + function test() {} + test.map = _.map; + equal(_.findWhere([_, {a: 1, b: 2}, _], test), _, 'checks properties given function'); + + function TestClass() { + this.y = 5; + this.x = 'foo'; + } + var expect = {c: 1, x: 'foo', y: 5}; + deepEqual(_.findWhere([{y: 5, b: 6}, expect], new TestClass()), expect, 'uses class instance properties'); }); test('max', function() { @@ -438,8 +453,7 @@ deepEqual(actual, collection, 'sortBy should be stable'); - var actual2 = _.sortBy(collection, 'x'); - deepEqual(actual, collection, 'sortBy should be stable'); + deepEqual(_.sortBy(collection, 'x'), collection, 'sortBy accepts property string'); var list = ['q', 'w', 'e', 'r', 't', 'y']; deepEqual(_.sortBy(list), ['e', 'q', 'r', 't', 'w', 'y'], 'uses _.identity if iterator is not specified'); diff --git a/underscore.js b/underscore.js index 13456915b..92cfb966b 100644 --- a/underscore.js +++ b/underscore.js @@ -191,7 +191,6 @@ // Return all the elements for which a truth test fails. _.reject = function(obj, predicate, context) { - if (typeof predicate == 'object' && _.isEmpty(predicate)) return _.filter(obj, predicate); return _.filter(obj, _.negate(lookupIterator(predicate)), context); }; @@ -241,7 +240,9 @@ }; // Convenience version of a common use case of `map`: fetching a property. - _.pluck = _.map; + _.pluck = function(obj, key) { + return _.map(obj, _.property(key)); + }; // Convenience version of a common use case of `filter`: selecting only objects // containing specific `key:value` pairs. @@ -251,7 +252,9 @@ // Convenience version of a common use case of `find`: getting the first object // containing specific `key:value` pairs. - _.findWhere = _.find; + _.findWhere = function(obj, attrs) { + return _.find(obj, _.matches(attrs)); + }; // Return the maximum element or (element-based computation). _.max = function(obj, iterator, context) { From 42d1abb9813bc12b110cc8ab2de2f78667681d30 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Fri, 18 Apr 2014 14:51:53 -0400 Subject: [PATCH 4/5] Tweak tests. --- test/collections.js | 89 +++++++++++++-------------------------------- 1 file changed, 25 insertions(+), 64 deletions(-) diff --git a/test/collections.js b/test/collections.js index 112e189ec..13957df9e 100644 --- a/test/collections.js +++ b/test/collections.js @@ -69,7 +69,7 @@ var ifnull = _.map(null, function(){}); ok(_.isArray(ifnull) && ifnull.length === 0, 'handles a null properly'); - //can be used like _.pluck + // Passing a property name like _.pluck. var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}]; deepEqual(_.map(people, 'name'), ['moe', 'curly'], 'predicate string map to object properties'); }); @@ -172,16 +172,12 @@ strictEqual(_.find(array, function(n) { return n > 2; }), 3, 'should return first found `value`'); strictEqual(_.find(array, function() { return false; }), void 0, 'should return `undefined` if `value` is not found'); - //can be used like findwhere + // Matching an object like _.findWhere. var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}, {a: 2, b: 4}]; - var result = _.find(list, {a: 1}); - deepEqual(result, {a: 1, b: 2}, 'can be used as findWhere'); - result = _.find(list, {b: 4}); - deepEqual(result, {a: 1, b: 4}); - result = _.find(list, {c: 1}); - ok(_.isUndefined(result), 'undefined when not found'); - result = _.find([], {c: 1}); - ok(_.isUndefined(result), 'undefined when searching empty list'); + deepEqual(_.find(list, {a: 1}), {a: 1, b: 2}, 'can be used as findWhere'); + deepEqual(_.find(list, {b: 4}), {a: 1, b: 4}); + ok(!_.find(list, {c: 1}), 'undefined when not found'); + ok(!_.find([], {c: 1}), 'undefined when searching empty list'); }); test('detect', function() { @@ -189,28 +185,22 @@ equal(result, 2, 'found the first "2" and broke the loop'); }); - test('select', function() { - var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); - deepEqual(evens, [2, 4, 6], 'selected each even number'); - }); - test('filter', function() { var evenArray = [1, 2, 3, 4, 5, 6]; var evenObject = {one: 1, two: 2, three: 3}; var isEven = function(num){ return num % 2 === 0; }; - deepEqual(_.filter(evenArray, isEven), [2, 4, 6], 'aliased as "filter"'); + strictEqual(_.select, _.filter); + + deepEqual(_.filter(evenArray, isEven), [2, 4, 6]); deepEqual(_.filter(evenObject, isEven), [2]); deepEqual(_.filter([{}, evenObject, []], 'two'), [evenObject], 'predicate string map to object properties'); - //can be used like where + // Can be used like _.where. var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]; - var result = _.filter(list, {a: 1}); - equal(result.length, 3, 'can be used like _.where'); - equal(result[result.length - 1].b, 4); + deepEqual(_.filter(list, {a: 1}), [{a: 1, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]); deepEqual(_.filter(list, {b: 2}), [{a: 1, b: 2}, {a: 2, b: 2}]); - result = _.filter(list, {}); - deepEqual(result, list, 'Empty object accepts all items'); + deepEqual(_.filter(list, {}), list, 'Empty object accepts all items'); }); test('reject', function() { @@ -225,21 +215,14 @@ }, context); deepEqual(evens, [2, 4, 6], 'rejected each odd number'); - var evenObject = {one: 1, two: 2, three: 3}; - deepEqual(_.reject([odds, evenObject], 'two'), [odds], 'predicate string map to object properties'); - }); + deepEqual(_.reject([odds, {one: 1, two: 2, three: 3}], 'two'), [odds], 'predicate string map to object properties'); - test('reject given object (ala rejectWhere)', function() { - //can be used like where + // Can be used like _.where. var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]; - var result = _.reject(list, {a: 1}); - equal(result.length, 1); - equal(result[result.length - 1].b, 2); - result = _.reject(list, {b: 2}); - equal(result.length, 2); - equal(result[0].a, 1); + deepEqual(_.reject(list, {a: 1}), [{a: 2, b: 2}]); + deepEqual(_.reject(list, {b: 2}), [{a: 1, b: 3}, {a: 1, b: 4}]); deepEqual(_.reject(list, {}), [], 'Returns empty list given empty object'); - deepEqual(_.reject(list, []), [], 'Returns empty list given empty object'); + deepEqual(_.reject(list, []), [], 'Returns empty list given empty array'); }); test('all', function() { @@ -254,11 +237,12 @@ ok(!_.all([undefined, undefined, undefined], _.identity), 'works with arrays of undefined'); var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]; - var list2 = [{a: 1, b: 2}, {a: 2, b: 2, c: true}]; ok(!_.all(list, {a: 1, b: 2}), 'Can be called with object'); ok(_.all(list, 'a'), 'String mapped to object property'); - ok(_.all(list2, {b: 2}), 'Can be called with object'); - ok(!_.all(list2, 'c'), 'String mapped to object property'); + + list = [{a: 1, b: 2}, {a: 2, b: 2, c: true}]; + ok(_.all(list, {b: 2}), 'Can be called with object'); + ok(!_.all(list, 'c'), 'String mapped to object property'); }); test('any', function() { @@ -274,18 +258,12 @@ ok(_.some([false, false, true]), 'aliased as "some"'); var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]; - var list2 = [{a: 1, b: 2}, {a: 2, b: 2, c: true}]; - ok(!_.all(list, {a: 1, b: 2}), 'Can be called with object'); - ok(_.all(list, 'a'), 'String mapped to object property'); - ok(_.all(list2, {b: 2}), 'Can be called with object'); - ok(!_.all(list2, 'c'), 'String mapped to object property'); - - var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]; - var list2 = [{a: 1, b: 2}, {a: 2, b: 2, c: true}]; ok(!_.any(list, {a: 5, b: 2}), 'Can be called with object'); ok(_.any(list, 'a'), 'String mapped to object property'); - ok(_.any(list2, {b: 2}), 'Can be called with object'); - ok(!_.any(list2, 'd'), 'String mapped to object property'); + + list = [{a: 1, b: 2}, {a: 2, b: 2, c: true}]; + ok(_.any(list, {b: 2}), 'Can be called with object'); + ok(!_.any(list, 'd'), 'String mapped to object property'); }); test('include', function() { @@ -327,8 +305,6 @@ test('pluck', function() { var people = [{name: 'moe', age: 30}, {name: 'curly', age: 50}]; deepEqual(_.pluck(people, 'name'), ['moe', 'curly'], 'pulls names out of objects'); - //compat: most flexible handling of edge cases - deepEqual(_.pluck([{'[object Window]': 1}], window), [1]); }); test('where', function() { @@ -341,10 +317,6 @@ equal(result[0].a, 1); result = _.where(list, {}); equal(result.length, list.length); - - function test() {} - test.map = _.map; - deepEqual(_.where([_, {a: 1, b: 2}, _], test), [_, _], 'checks properties given function'); }); test('findWhere', function() { @@ -359,17 +331,6 @@ result = _.findWhere([], {c: 1}); ok(_.isUndefined(result), 'undefined when searching empty list'); - - function test() {} - test.map = _.map; - equal(_.findWhere([_, {a: 1, b: 2}, _], test), _, 'checks properties given function'); - - function TestClass() { - this.y = 5; - this.x = 'foo'; - } - var expect = {c: 1, x: 'foo', y: 5}; - deepEqual(_.findWhere([{y: 5, b: 6}, expect], new TestClass()), expect, 'uses class instance properties'); }); test('max', function() { From 787290caee3a0e2c07b239e369b6aff22714f214 Mon Sep 17 00:00:00 2001 From: Graeme Yeates Date: Sun, 27 Apr 2014 21:25:38 -0400 Subject: [PATCH 5/5] Merge bradunbar's test tweaks+min/max test --- test/collections.js | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/test/collections.js b/test/collections.js index 13957df9e..528d113e5 100644 --- a/test/collections.js +++ b/test/collections.js @@ -185,13 +185,16 @@ equal(result, 2, 'found the first "2" and broke the loop'); }); + test('select', function() { + var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); + deepEqual(evens, [2, 4, 6], 'selected each even number'); + }); + test('filter', function() { var evenArray = [1, 2, 3, 4, 5, 6]; var evenObject = {one: 1, two: 2, three: 3}; var isEven = function(num){ return num % 2 === 0; }; - strictEqual(_.select, _.filter); - deepEqual(_.filter(evenArray, isEven), [2, 4, 6]); deepEqual(_.filter(evenObject, isEven), [2]); deepEqual(_.filter([{}, evenObject, []], 'two'), [evenObject], 'predicate string map to object properties'); @@ -305,6 +308,8 @@ test('pluck', function() { var people = [{name: 'moe', age: 30}, {name: 'curly', age: 50}]; deepEqual(_.pluck(people, 'name'), ['moe', 'curly'], 'pulls names out of objects'); + //compat: most flexible handling of edge cases + deepEqual(_.pluck([{'[object Object]': 1}], {}), [1]); }); test('where', function() { @@ -317,6 +322,10 @@ equal(result[0].a, 1); result = _.where(list, {}); equal(result.length, list.length); + + function test() {} + test.map = _.map; + deepEqual(_.where([_, {a: 1, b: 2}, _], test), [_, _], 'checks properties given function'); }); test('findWhere', function() { @@ -331,6 +340,17 @@ result = _.findWhere([], {c: 1}); ok(_.isUndefined(result), 'undefined when searching empty list'); + + function test() {} + test.map = _.map; + equal(_.findWhere([_, {a: 1, b: 2}, _], test), _, 'checks properties given function'); + + function TestClass() { + this.y = 5; + this.x = 'foo'; + } + var expect = {c: 1, x: 'foo', y: 5}; + deepEqual(_.findWhere([{y: 5, b: 6}, expect], new TestClass()), expect, 'uses class instance properties'); }); test('max', function() { @@ -352,6 +372,8 @@ var b = {x: -Infinity}; var iterator = function(o){ return o.x; }; equal(_.max([a, b], iterator), a, 'Respects iterator return value of -Infinity'); + + deepEqual(_.max([{'a': 1}, {'a': 0, 'b': 3}, {'a': 4}, {'a': 2}], 'a'), {'a': 4}, 'String keys use property iterator'); }); test('min', function() { @@ -377,6 +399,8 @@ var b = {x: Infinity}; var iterator = function(o){ return o.x; }; equal(_.min([a, b], iterator), a, 'Respects iterator return value of Infinity'); + + deepEqual(_.min([{'a': 1}, {'a': 0, 'b': 3}, {'a': 4}, {'a': 2}], 'a'), {'a': 0, 'b': 3}, 'String keys use property iterator'); }); test('sortBy', function() {