Update vendor/underscore and underscore tests.

This commit is contained in:
John-David Dalton
2014-04-15 01:01:18 -07:00
parent 900c0eafac
commit d653b951e3
8 changed files with 446 additions and 285 deletions

View File

@@ -31,8 +31,15 @@
// excuse tests we intentionally fail or those with problems
QUnit.config.excused = {
'Arrays': {
'union': [
'[null,1,2,3]'
'initial': [
'initial works on arguments object'
],
'intersection': [
'can perform an OO-style intersection'
],
'rest': [
'aliased as drop and works on arguments object',
'aliased as tail and works on arguments object',
]
},
'Chaining': {
@@ -43,10 +50,13 @@
'Died on test #1'
],
'reverse/concat/unshift/pop/map': [
'"34, 10, 8, 6, 4, 2, 10, 10"'
'can chain together array functions.'
]
},
'Collections': {
'map': [
'OO-style doubled numbers'
],
'reduce': [
'handles a null (without initial value) properly',
'throws an error for empty arrays with no initial value'
@@ -54,9 +64,6 @@
'reduceRight': [
'handles a null (without initial value) properly',
'throws an error for empty arrays with no initial value'
],
'where': [
'4'
]
},
'Functions': {
@@ -67,6 +74,7 @@
'bindAll': [
'throws an error for bindAll with no functions named'
],
'negate': true,
'partial': [
'can partially apply with placeholders',
'accepts more arguments than the number of placeholders',
@@ -85,6 +93,12 @@
],
'keys': [
'is not fooled by sparse arrays; see issue #95'
],
'omit': [
'can accept a predicate'
],
'pick': [
'can accept a predicate and context'
]
},
'Utility': {
@@ -94,6 +108,7 @@
'_.unescape': [
'"<a href=\\"http://moe.com\\">Curly & Moe&#039;s</a>"'
],
'noop': true,
'now': [
'Produces the correct time in milliseconds'
],
@@ -123,12 +138,21 @@
if (!ui.isModularize) {
delete QUnit.config.excused.Functions.partial;
}
delete QUnit.config.excused.Arrays.initial;
delete QUnit.config.excused.Arrays.intersection;
delete QUnit.config.excused.Arrays.rest;
delete QUnit.config.excused.Chaining;
delete QUnit.config.excused.Collections.where;
delete QUnit.config.excused.Collections.map;
delete QUnit.config.excused.Objects.keys;
delete QUnit.config.excused.Utility['_.escape'];
delete QUnit.config.excused.Utility['_.unescape'];
}
else {
delete QUnit.config.excused.Functions.negate;
delete QUnit.config.excused.Objects.omit;
delete QUnit.config.excused.Objects.pick;
delete QUnit.config.excused.Utility.noop;
}
// load test scripts
document.write(ui.urlParams.loader != 'none'
? '<script data-dojo-config="async:1" src="' + ui.loaderPath + '"><\/script>'

View File

@@ -5,15 +5,15 @@
test('first', function() {
equal(_.first([1,2,3]), 1, 'can pull out the first element of an array');
equal(_([1, 2, 3]).first(), 1, 'can perform OO-style "first()"');
equal(_.first([1,2,3], 0).join(', '), '', 'can pass an index to first');
equal(_.first([1,2,3], 2).join(', '), '1, 2', 'can pass an index to first');
equal(_.first([1,2,3], 5).join(', '), '1, 2, 3', 'can pass an index to first');
deepEqual(_.first([1, 2, 3], 0), [], 'can pass an index to first');
deepEqual(_.first([1, 2, 3], 2), [1, 2], 'can pass an index to first');
deepEqual(_.first([1, 2, 3], 5), [1, 2, 3], 'can pass an index to first');
var result = (function(){ return _.first(arguments); })(4, 3, 2, 1);
equal(result, 4, 'works on an arguments object.');
result = _.map([[1,2,3],[1,2,3]], _.first);
equal(result.join(','), '1,1', 'works well with _.map');
deepEqual(result, [1, 1], 'works well with _.map');
result = (function() { return _.take([1,2,3], 2); })();
equal(result.join(','), '1,2', 'aliased as take');
deepEqual(result, [1, 2], 'aliased as take');
equal(_.first(null), undefined, 'handles nulls');
strictEqual(_.first([1, 2, 3], -1).length, 0);
@@ -21,35 +21,36 @@
test('rest', function() {
var numbers = [1, 2, 3, 4];
equal(_.rest(numbers).join(', '), '2, 3, 4', 'working rest()');
equal(_.rest(numbers, 0).join(', '), '1, 2, 3, 4', 'working rest(0)');
equal(_.rest(numbers, 2).join(', '), '3, 4', 'rest can take an index');
deepEqual(_.rest(numbers), [2, 3, 4], 'working rest()');
deepEqual(_.rest(numbers, 0), [1, 2, 3, 4], 'working rest(0)');
deepEqual(_.rest(numbers, 2), [3, 4], 'rest can take an index');
var result = (function(){ return _(arguments).tail(); })(1, 2, 3, 4);
equal(result.join(', '), '2, 3, 4', 'aliased as tail and works on arguments object');
deepEqual(result, [2, 3, 4], 'aliased as tail and works on arguments object');
result = _.map([[1,2,3],[1,2,3]], _.rest);
equal(_.flatten(result).join(','), '2,3,2,3', 'works well with _.map');
deepEqual(_.flatten(result), [2, 3, 2, 3], 'works well with _.map');
result = (function(){ return _(arguments).drop(); })(1, 2, 3, 4);
equal(result.join(', '), '2, 3, 4', 'aliased as drop and works on arguments object');
deepEqual(result, [2, 3, 4], 'aliased as drop and works on arguments object');
});
test('initial', function() {
equal(_.initial([1,2,3,4,5]).join(', '), '1, 2, 3, 4', 'working initial()');
equal(_.initial([1,2,3,4],2).join(', '), '1, 2', 'initial can take an index');
deepEqual(_.initial([1, 2, 3, 4, 5]), [1, 2, 3, 4], 'working initial()');
deepEqual(_.initial([1, 2, 3, 4], 2), [1, 2], 'initial can take an index');
deepEqual(_.initial([1, 2, 3, 4], 6), [], 'initial can take a large index');
var result = (function(){ return _(arguments).initial(); })(1, 2, 3, 4);
equal(result.join(', '), '1, 2, 3', 'initial works on arguments object');
deepEqual(result, [1, 2, 3], 'initial works on arguments object');
result = _.map([[1,2,3],[1,2,3]], _.initial);
equal(_.flatten(result).join(','), '1,2,1,2', 'initial works with _.map');
deepEqual(_.flatten(result), [1, 2, 1, 2], 'initial works with _.map');
});
test('last', function() {
equal(_.last([1,2,3]), 3, 'can pull out the last element of an array');
equal(_.last([1,2,3], 0).join(', '), '', 'can pass an index to last');
equal(_.last([1,2,3], 2).join(', '), '2, 3', 'can pass an index to last');
equal(_.last([1,2,3], 5).join(', '), '1, 2, 3', 'can pass an index to last');
deepEqual(_.last([1, 2, 3], 0), [], 'can pass an index to last');
deepEqual(_.last([1, 2, 3], 2), [2, 3], 'can pass an index to last');
deepEqual(_.last([1, 2, 3], 5), [1, 2, 3], 'can pass an index to last');
var result = (function(){ return _(arguments).last(); })(1, 2, 3, 4);
equal(result, 4, 'works on an arguments object');
result = _.map([[1,2,3],[1,2,3]], _.last);
equal(result.join(','), '3,3', 'works well with _.map');
deepEqual(result, [3, 3], 'works well with _.map');
equal(_.last(null), undefined, 'handles nulls');
strictEqual(_.last([1, 2, 3], -1).length, 0);
@@ -73,78 +74,89 @@
test('without', function() {
var list = [1, 2, 1, 0, 3, 1, 4];
equal(_.without(list, 0, 1).join(', '), '2, 3, 4', 'can remove all instances of an object');
deepEqual(_.without(list, 0, 1), [2, 3, 4], 'can remove all instances of an object');
var result = (function(){ return _.without(arguments, 0, 1); })(1, 2, 1, 0, 3, 1, 4);
equal(result.join(', '), '2, 3, 4', 'works on an arguments object');
deepEqual(result, [2, 3, 4], 'works on an arguments object');
list = [{one : 1}, {two : 2}];
ok(_.without(list, {one : 1}).length == 2, 'uses real object identity for comparisons.');
ok(_.without(list, list[0]).length == 1, 'ditto.');
});
test('partition', function() {
var list = [0, 1, 2, 3, 4, 5];
function inspect(x) { return x instanceof Array ? '[' + _.map(x, inspect) + ']' : '' + x; }
equal(inspect(_.partition(list, function(x) { return x < 4; })), '[[0,1,2,3],[4,5]]', 'handles bool return values');
equal(inspect(_.partition(list, function(x) { return x & 1; })), '[[1,3,5],[0,2,4]]', 'handles 0 and 1 return values');
equal(inspect(_.partition(list, function(x) { return x - 3; })), '[[0,1,2,4,5],[3]]', 'handles other numeric return values');
equal(inspect(_.partition(list, function(x) { return x > 1 ? null : true; })), '[[0,1],[2,3,4,5]]', 'handles null return values');
equal(inspect(_.partition(list, function(x) { if(x < 2) return true; })), '[[0,1],[2,3,4,5]]', 'handles undefined return values');
});
test('uniq', function() {
var list = [1, 2, 1, 3, 1, 4];
equal(_.uniq(list).join(', '), '1, 2, 3, 4', 'can find the unique values of an unsorted array');
deepEqual(_.uniq(list), [1, 2, 3, 4], 'can find the unique values of an unsorted array');
list = [1, 1, 1, 2, 2, 3];
equal(_.uniq(list, true).join(', '), '1, 2, 3', 'can find the unique values of a sorted array faster');
deepEqual(_.uniq(list, true), [1, 2, 3], 'can find the unique values of a sorted array faster');
list = [{name:'moe'}, {name:'curly'}, {name:'larry'}, {name:'curly'}];
var iterator = function(value) { return value.name; };
equal(_.map(_.uniq(list, false, iterator), iterator).join(', '), 'moe, curly, larry', 'can find the unique values of an array using a custom iterator');
deepEqual(_.map(_.uniq(list, false, iterator), iterator), ['moe', 'curly', 'larry'], 'can find the unique values of an array using a custom iterator');
equal(_.map(_.uniq(list, iterator), iterator).join(', '), 'moe, curly, larry', 'can find the unique values of an array using a custom iterator without specifying whether array is sorted');
deepEqual(_.map(_.uniq(list, iterator), iterator), ['moe', 'curly', 'larry'], 'can find the unique values of an array using a custom iterator without specifying whether array is sorted');
iterator = function(value) { return value +1; };
list = [1, 2, 2, 3, 4, 4];
equal(_.uniq(list, true, iterator).join(', '), '1, 2, 3, 4', 'iterator works with sorted array');
deepEqual(_.uniq(list, true, iterator), [1, 2, 3, 4], 'iterator works with sorted array');
var result = (function(){ return _.uniq(arguments); })(1, 2, 1, 3, 1, 4);
equal(result.join(', '), '1, 2, 3, 4', 'works on an arguments object');
deepEqual(result, [1, 2, 3, 4], 'works on an arguments object');
deepEqual(_.uniq(null), []);
var context = {};
list = [3];
_.uniq(list, function(value, index, array) {
strictEqual(this, context);
strictEqual(value, 3);
strictEqual(index, 0);
}, context);
});
test('intersection', function() {
var stooges = ['moe', 'curly', 'larry'], leaders = ['moe', 'groucho'];
equal(_.intersection(stooges, leaders).join(''), 'moe', 'can take the set intersection of two arrays');
equal(_(stooges).intersection(leaders).join(''), 'moe', 'can perform an OO-style intersection');
deepEqual(_.intersection(stooges, leaders), ['moe'], 'can take the set intersection of two arrays');
deepEqual(_(stooges).intersection(leaders), ['moe'], 'can perform an OO-style intersection');
var result = (function(){ return _.intersection(arguments, leaders); })('moe', 'curly', 'larry');
equal(result.join(''), 'moe', 'works on an arguments object');
deepEqual(result, ['moe'], 'works on an arguments object');
var theSixStooges = ['moe', 'moe', 'curly', 'curly', 'larry', 'larry'];
equal(_.intersection(theSixStooges, leaders).join(''), 'moe', 'returns a duplicate-free array');
deepEqual(_.intersection(theSixStooges, leaders), ['moe'], 'returns a duplicate-free array');
result = _.intersection([2, 4, 3, 1], [1, 2, 3]);
deepEqual(result, [2, 3, 1], 'preserves order of first array');
result = _.intersection(null, [1, 2, 3]);
equal(Object.prototype.toString.call(result), '[object Array]', 'returns an empty array when passed null as first argument');
equal(result.length, 0, 'returns an empty array when passed null as first argument');
result = _.intersection([1, 2, 3], null);
equal(Object.prototype.toString.call(result), '[object Array]', 'returns an empty array when passed null as argument beyond the first');
equal(result.length, 0, 'returns an empty array when passed null as argument beyond the first');
});
test('union', function() {
var result = _.union([1, 2, 3], [2, 30, 1], [1, 40]);
equal(result.join(' '), '1 2 3 30 40', 'takes the union of a list of arrays');
deepEqual(result, [1, 2, 3, 30, 40], 'takes the union of a list of arrays');
result = _.union([1, 2, 3], [2, 30, 1], [1, 40, [1]]);
equal(result.join(' '), '1 2 3 30 40 1', 'takes the union of a list of nested arrays');
deepEqual(result, [1, 2, 3, 30, 40, [1]], 'takes the union of a list of nested arrays');
var args = null;
(function(){ args = arguments; })(1, 2, 3);
result = _.union(args, [2, 30, 1], [1, 40]);
equal(result.join(' '), '1 2 3 30 40', 'takes the union of a list of arrays');
deepEqual(result, [1, 2, 3, 30, 40], 'takes the union of a list of arrays');
result = _.union(null, [1, 2, 3]);
deepEqual(result, [null, 1, 2, 3]);
result = _.union([1, 2, 3], 4);
deepEqual(result, [1, 2, 3], 'restrict the union to arrays only');
});
test('difference', function() {
var result = _.difference([1, 2, 3], [2, 30, 40]);
equal(result.join(' '), '1 3', 'takes the difference of two arrays');
deepEqual(result, [1, 3], 'takes the difference of two arrays');
result = _.difference([1, 2, 3, 4], [2, 30, 40], [1, 11, 111]);
equal(result.join(' '), '3 4', 'takes the difference of three arrays');
deepEqual(result, [3, 4], 'takes the difference of three arrays');
result = _.difference([1, 2, 3], 1);
deepEqual(result, [1, 2, 3], 'restrict the difference to arrays only');
});
test('zip', function() {
@@ -203,6 +215,9 @@
numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3];
index = _.indexOf(numbers, 2, 5);
equal(index, 7, 'supports the fromIndex argument');
index = _.indexOf([,,,], undefined);
equal(index, 0, 'treats sparse arrays as if they were dense');
});
test('lastIndexOf', function() {
@@ -223,14 +238,14 @@
});
test('range', function() {
equal(_.range(0).join(''), '', 'range with 0 as a first argument generates an empty array');
equal(_.range(4).join(' '), '0 1 2 3', 'range with a single positive argument generates an array of elements 0,1,2,...,n-1');
equal(_.range(5, 8).join(' '), '5 6 7', 'range with two arguments a &amp; b, a&lt;b generates an array of elements a,a+1,a+2,...,b-2,b-1');
equal(_.range(8, 5).join(''), '', 'range with two arguments a &amp; b, b&lt;a generates an empty array');
equal(_.range(3, 10, 3).join(' '), '3 6 9', 'range with three arguments a &amp; b &amp; c, c &lt; b-a, a &lt; b generates an array of elements a,a+c,a+2c,...,b - (multiplier of a) &lt; c');
equal(_.range(3, 10, 15).join(''), '3', 'range with three arguments a &amp; b &amp; c, c &gt; b-a, a &lt; b generates an array with a single element, equal to a');
equal(_.range(12, 7, -2).join(' '), '12 10 8', 'range with three arguments a &amp; b &amp; c, a &gt; b, c &lt; 0 generates an array of elements a,a-c,a-2c and ends with the number not less than b');
equal(_.range(0, -10, -1).join(' '), '0 -1 -2 -3 -4 -5 -6 -7 -8 -9', 'final example in the Python docs');
deepEqual(_.range(0), [], 'range with 0 as a first argument generates an empty array');
deepEqual(_.range(4), [0, 1, 2, 3], 'range with a single positive argument generates an array of elements 0,1,2,...,n-1');
deepEqual(_.range(5, 8), [5, 6, 7], 'range with two arguments a &amp; b, a&lt;b generates an array of elements a,a+1,a+2,...,b-2,b-1');
deepEqual(_.range(8, 5), [], 'range with two arguments a &amp; b, b&lt;a generates an empty array');
deepEqual(_.range(3, 10, 3), [3, 6, 9], 'range with three arguments a &amp; b &amp; c, c &lt; b-a, a &lt; b generates an array of elements a,a+c,a+2c,...,b - (multiplier of a) &lt; c');
deepEqual(_.range(3, 10, 15), [3], 'range with three arguments a &amp; b &amp; c, c &gt; b-a, a &lt; b generates an array with a single element, equal to a');
deepEqual(_.range(12, 7, -2), [12, 10, 8], 'range with three arguments a &amp; b &amp; c, a &gt; b, c &lt; 0 generates an array of elements a,a-c,a-2c and ends with the number not less than b');
deepEqual(_.range(0, -10, -1), [0, -1, -2, -3, -4, -5, -6, -7, -8, -9], 'final example in the Python docs');
});
})();

View File

@@ -29,7 +29,7 @@
}).sortBy(function(n) {
return -n;
}).value();
equal(numbers.join(', '), '10, 6, 2', 'filtered and reversed the numbers');
deepEqual(numbers, [10, 6, 2], 'filtered and reversed the numbers');
});
test('select/reject/sortBy in functional style', function() {
@@ -41,7 +41,7 @@
}).sortBy(function(n) {
return -n;
}).value();
equal(numbers.join(', '), '10, 6, 2', 'filtered and reversed the numbers');
deepEqual(numbers, [10, 6, 2], 'filtered and reversed the numbers');
});
test('reverse/concat/unshift/pop/map', function() {
@@ -53,7 +53,7 @@
.pop()
.map(function(n){ return n * 2; })
.value();
equal(numbers.join(', '), '34, 10, 8, 6, 4, 2, 10, 10', 'can chain together array functions.');
deepEqual(numbers, [34, 10, 8, 6, 4, 2, 10, 10], 'can chain together array functions.');
});
test('chaining works in small stages', function() {

View File

@@ -9,17 +9,17 @@
var answers = [];
_.each([1, 2, 3], function(num){ answers.push(num * this.multiplier);}, {multiplier : 5});
equal(answers.join(', '), '5, 10, 15', 'context object property accessed');
deepEqual(answers, [5, 10, 15], 'context object property accessed');
answers = [];
_.forEach([1, 2, 3], function(num){ answers.push(num); });
equal(answers.join(', '), '1, 2, 3', 'aliased as "forEach"');
deepEqual(answers, [1, 2, 3], 'aliased as "forEach"');
answers = [];
var obj = {one : 1, two : 2, three : 3};
obj.constructor.prototype.four = 4;
_.each(obj, function(value, key){ answers.push(key); });
equal(answers.join(', '), 'one, two, three', 'iterating over objects works, and ignores the object prototype.');
deepEqual(answers, ['one', 'two', 'three'], 'iterating over objects works, and ignores the object prototype.');
delete obj.constructor.prototype.four;
var answer = null;
@@ -35,20 +35,26 @@
var a = [1, 2, 3];
strictEqual(_.each(a, function(){}), a);
strictEqual(_.each(null, function(){}), null);
var b = [1, 2, 3];
b.length = 100;
answers = 0;
_.each(b, function(){ ++answers; });
equal(answers, 100, 'enumerates [0, length)');
});
test('map', function() {
var doubled = _.map([1, 2, 3], function(num){ return num * 2; });
equal(doubled.join(', '), '2, 4, 6', 'doubled numbers');
deepEqual(doubled, [2, 4, 6], 'doubled numbers');
doubled = _.collect([1, 2, 3], function(num){ return num * 2; });
equal(doubled.join(', '), '2, 4, 6', 'aliased as "collect"');
deepEqual(doubled, [2, 4, 6], 'aliased as "collect"');
var tripled = _.map([1, 2, 3], function(num){ return num * this.multiplier; }, {multiplier : 3});
equal(tripled.join(', '), '3, 6, 9', 'tripled numbers with context');
deepEqual(tripled, [3, 6, 9], 'tripled numbers with context');
var doubled = _([1, 2, 3]).map(function(num){ return num * 2; });
equal(doubled.join(', '), '2, 4, 6', 'OO-style doubled numbers');
deepEqual(doubled, [2, 4, 6], 'OO-style doubled numbers');
if (document.querySelectorAll) {
var ids = _.map(document.querySelectorAll('#map-test *'), function(n){ return n.id; });
@@ -170,15 +176,15 @@
test('select', function() {
var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
equal(evens.join(', '), '2, 4, 6', 'selected each even number');
deepEqual(evens, [2, 4, 6], 'selected each even number');
evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
equal(evens.join(', '), '2, 4, 6', 'aliased as "filter"');
deepEqual(evens, [2, 4, 6], 'aliased as "filter"');
});
test('reject', function() {
var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
equal(odds.join(', '), '1, 3, 5', 'rejected each even number');
deepEqual(odds, [1, 3, 5], 'rejected each even number');
var context = 'obj';
@@ -186,7 +192,7 @@
equal(context, 'obj');
return num % 2 != 0;
}, context);
equal(evens.join(', '), '2, 4, 6', 'rejected each odd number');
deepEqual(evens, [2, 4, 6], 'rejected each odd number');
});
test('all', function() {
@@ -227,15 +233,15 @@
test('invoke', function() {
var list = [[5, 1, 7], [3, 2, 1]];
var result = _.invoke(list, 'sort');
equal(result[0].join(', '), '1, 5, 7', 'first array sorted');
equal(result[1].join(', '), '1, 2, 3', 'second array sorted');
deepEqual(result[0], [1, 5, 7], 'first array sorted');
deepEqual(result[1], [1, 2, 3], 'second array sorted');
});
test('invoke w/ function reference', function() {
var list = [[5, 1, 7], [3, 2, 1]];
var result = _.invoke(list, Array.prototype.sort);
equal(result[0].join(', '), '1, 5, 7', 'first array sorted');
equal(result[1].join(', '), '1, 2, 3', 'second array sorted');
deepEqual(result[0], [1, 5, 7], 'first array sorted');
deepEqual(result[1], [1, 2, 3], 'second array sorted');
});
// Relevant when using ClojureScript
@@ -247,15 +253,15 @@
var s = 'foo';
equal(s.call(), 42, 'call function exists');
var result = _.invoke(list, 'sort');
equal(result[0].join(', '), '1, 5, 7', 'first array sorted');
equal(result[1].join(', '), '1, 2, 3', 'second array sorted');
deepEqual(result[0], [1, 5, 7], 'first array sorted');
deepEqual(result[1], [1, 2, 3], 'second array sorted');
delete String.prototype.call;
equal(s.call, undefined, 'call function removed');
});
test('pluck', function() {
var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}];
equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'pulls names out of objects');
deepEqual(_.pluck(people, 'name'), ['moe', 'curly'], 'pulls names out of objects');
});
test('where', function() {
@@ -295,6 +301,9 @@
equal(_.max({'a': 'a'}), -Infinity, 'Maximum value of a non-numeric collection');
equal(299999, _.max(_.range(1,300000)), 'Maximum value of a too-big array');
equal(3, _.max([1, 2, 3, 'test']), 'Finds correct max in array starting with num and containing a NaN');
equal(3, _.max(['test', 1, 2, 3]), 'Finds correct max in array starting with NaN');
});
test('min', function() {
@@ -312,19 +321,22 @@
equal(_.min([now, then]), then);
equal(1, _.min(_.range(1,300000)), 'Minimum value of a too-big array');
equal(1, _.min([1, 2, 3, 'test']), 'Finds correct min in array starting with num and containing a NaN');
equal(1, _.min(['test', 1, 2, 3]), 'Finds correct min in array starting with NaN');
});
test('sortBy', function() {
var people = [{name : 'curly', age : 50}, {name : 'moe', age : 30}];
people = _.sortBy(people, function(person){ return person.age; });
equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'stooges sorted by age');
deepEqual(_.pluck(people, 'name'), ['moe', 'curly'], 'stooges sorted by age');
var list = [undefined, 4, 1, undefined, 3, 2];
equal(_.sortBy(list, _.identity).join(','), '1,2,3,4,,', 'sortBy with undefined values');
deepEqual(_.sortBy(list, _.identity), [1, 2, 3, 4, undefined, undefined], 'sortBy with undefined values');
var list = ['one', 'two', 'three', 'four', 'five'];
var sorted = _.sortBy(list, 'length');
equal(sorted.join(' '), 'one two four five three', 'sorted by length');
deepEqual(sorted, ['one', 'two', 'four', 'five', 'three'], 'sorted by length');
function Pair(x, y) {
this.x = x;
@@ -350,19 +362,19 @@
deepEqual(actual, collection, 'sortBy should be stable');
var list = ['q', 'w', 'e', 'r', 't', 'y'];
strictEqual(_.sortBy(list).join(''), 'eqrtwy', 'uses _.identity if iterator is not specified');
deepEqual(_.sortBy(list), ['e', 'q', 'r', 't', 'w', 'y'], 'uses _.identity if iterator is not specified');
});
test('groupBy', function() {
var parity = _.groupBy([1, 2, 3, 4, 5, 6], function(num){ return num % 2; });
ok('0' in parity && '1' in parity, 'created a group for each value');
equal(parity[0].join(', '), '2, 4, 6', 'put each even number in the right group');
deepEqual(parity[0], [2, 4, 6], 'put each even number in the right group');
var list = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
var grouped = _.groupBy(list, 'length');
equal(grouped['3'].join(' '), 'one two six ten');
equal(grouped['4'].join(' '), 'four five nine');
equal(grouped['5'].join(' '), 'three seven eight');
deepEqual(grouped['3'], ['one', 'two', 'six', 'ten']);
deepEqual(grouped['4'], ['four', 'five', 'nine']);
deepEqual(grouped['5'], ['three', 'seven', 'eight']);
var context = {};
_.groupBy([{}], function(){ ok(this === context); }, context);
@@ -459,15 +471,15 @@
var numbers = _.range(10);
var shuffled = _.shuffle(numbers).sort();
notStrictEqual(numbers, shuffled, 'original object is unmodified');
equal(shuffled.join(','), numbers.join(','), 'contains the same members before and after shuffle');
deepEqual(shuffled, numbers, 'contains the same members before and after shuffle');
});
test('sample', function() {
var numbers = _.range(10);
var all_sampled = _.sample(numbers, 10).sort();
equal(all_sampled.join(','), numbers.join(','), 'contains the same members before and after sample');
deepEqual(all_sampled, numbers, 'contains the same members before and after sample');
all_sampled = _.sample(numbers, 20).sort();
equal(all_sampled.join(','), numbers.join(','), 'also works when sampling more objects than are present');
deepEqual(all_sampled, numbers, 'also works when sampling more objects than are present');
ok(_.contains(numbers, _.sample(numbers)), 'sampling a single element returns something from the array');
strictEqual(_.sample([]), undefined, 'sampling empty array with no number returns undefined');
notStrictEqual(_.sample([], 5), [], 'sampling empty array with a number returns an empty array');
@@ -481,10 +493,10 @@
ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array');
var a = [1,2,3];
ok(_.toArray(a) !== a, 'array is cloned');
equal(_.toArray(a).join(', '), '1, 2, 3', 'cloned array contains same elements');
deepEqual(_.toArray(a), [1, 2, 3], 'cloned array contains same elements');
var numbers = _.toArray({one : 1, two : 2, three : 3});
equal(numbers.join(', '), '1, 2, 3', 'object flattened into array');
deepEqual(numbers, [1, 2, 3], 'object flattened into array');
// test in IE < 9
try {
@@ -511,4 +523,22 @@
equal(_.size(null), 0, 'handles nulls');
});
test('partition', function() {
var list = [0, 1, 2, 3, 4, 5];
deepEqual(_.partition(list, function(x) { return x < 4; }), [[0,1,2,3],[4,5]], 'handles bool return values');
deepEqual(_.partition(list, function(x) { return x & 1; }), [[1,3,5],[0,2,4]], 'handles 0 and 1 return values');
deepEqual(_.partition(list, function(x) { return x - 3; }), [[0,1,2,4,5],[3]], 'handles other numeric return values');
deepEqual(_.partition(list, function(x) { return x > 1 ? null : true; }), [[0,1],[2,3,4,5]], 'handles null return values');
deepEqual(_.partition(list, function(x) { if(x < 2) return true; }), [[0,1],[2,3,4,5]], 'handles undefined return values');
deepEqual(_.partition({a: 1, b: 2, c: 3}, function(x) { return x > 1; }), [[2, 3], [1]], 'handles objects');
// Default iterator
deepEqual(_.partition([1, false, true, '']), [[1, true], [false, '']], 'Default iterator');
deepEqual(_.partition([{x: 1}, {x: 0}, {x: 1}], 'x'), [[{x: 1}, {x: 1}], [{x: 0}]], 'Takes a string');
// 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');
});
})();

View File

@@ -278,6 +278,26 @@
}, 96);
});
asyncTest('throttle continues to function after system time is set backwards', 2, function() {
var counter = 0;
var incr = function(){ counter++; };
var throttledIncr = _.throttle(incr, 100);
var origNowFunc = _.now;
throttledIncr();
ok(counter == 1);
_.now = function () {
return new Date(2013, 0, 1, 1, 1, 1);
};
_.delay(function() {
throttledIncr();
ok(counter == 2);
start();
_.now = origNowFunc;
}, 200);
});
asyncTest('debounce', 1, function() {
var counter = 0;
var incr = function(){ counter++; };
@@ -314,6 +334,28 @@
_.delay(function(){ equal(counter, 1, 'incr was debounced'); start(); }, 96);
});
asyncTest('debounce after system time is set backwards', 2, function() {
var counter = 0;
var origNowFunc = _.now;
var debouncedIncr = _.debounce(function(){
counter++;
}, 100, true);
debouncedIncr();
equal(counter, 1, 'incr was called immediately');
_.now = function () {
return new Date(2013, 0, 1, 1, 1, 1);
};
_.delay(function() {
debouncedIncr();
equal(counter, 2, 'incr was debounced successfully');
start();
_.now = origNowFunc;
},200);
});
test('once', function() {
var num = 0;
var increment = _.once(function(){ num++; });
@@ -346,6 +388,12 @@
deepEqual(ret, [noop, ['whats', 'your'], 'vector', 'victor']);
});
test('negate', function() {
var isOdd = function(n){ return (n & 1) == 1; };
equal(_.negate(isOdd)(2), true, 'should return the complement of the given function');
equal(_.negate(isOdd)(3), false, 'should return the complement of the given function');
});
test('compose', function() {
var greet = function(name){ return 'hi: ' + name; };
var exclaim = function(sentence){ return sentence + '!'; };

View File

@@ -3,10 +3,10 @@
module('Objects');
test('keys', function() {
equal(_.keys({one : 1, two : 2}).join(', '), 'one, two', 'can extract the keys from an object');
deepEqual(_.keys({one : 1, two : 2}), ['one', 'two'], 'can extract the keys from an object');
// the test above is not safe because it relies on for-in enumeration order
var a = []; a[1] = 0;
equal(_.keys(a).join(', '), '1', 'is not fooled by sparse arrays; see issue #95');
deepEqual(_.keys(a), ['1'], 'is not fooled by sparse arrays; see issue #95');
deepEqual(_.keys(null), []);
deepEqual(_.keys(void 0), []);
deepEqual(_.keys(1), []);
@@ -15,8 +15,8 @@
});
test('values', function() {
equal(_.values({one: 1, two: 2}).join(', '), '1, 2', 'can extract the values from an object');
equal(_.values({one: 1, two: 2, length: 3}).join(', '), '1, 2, 3', '... even when one of them is "length"');
deepEqual(_.values({one: 1, two: 2}), [1, 2], 'can extract the values from an object');
deepEqual(_.values({one: 1, two: 2, length: 3}), [1, 2, 3], '... even when one of them is "length"');
});
test('pairs', function() {
@@ -26,7 +26,7 @@
test('invert', function() {
var obj = {first: 'Moe', second: 'Larry', third: 'Curly'};
equal(_.keys(_.invert(obj)).join(' '), 'Moe Larry Curly', 'can invert an object');
deepEqual(_.keys(_.invert(obj)), ['Moe', 'Larry', 'Curly'], 'can invert an object');
ok(_.isEqual(_.invert(_.invert(obj)), obj), 'two inverts gets you back where you started');
var obj = {length: 3};
@@ -39,7 +39,7 @@
var Animal = function(){};
Animal.prototype.run = function(){};
equal(_.functions(new Animal).join(''), 'run', 'also looks up functions on the prototype');
deepEqual(_.functions(new Animal), ['run'], 'also looks up functions on the prototype');
});
test('extend', function() {
@@ -52,7 +52,7 @@
result = _.extend({x:'x'}, {a:'a', x:2}, {a:'b'});
ok(_.isEqual(result, {x:2, a:'b'}), 'extending from multiple source objects last property trumps');
result = _.extend({}, {a: void 0, b: null});
equal(_.keys(result).join(''), 'ab', 'extend copies undefined values');
deepEqual(_.keys(result), ['a', 'b'], 'extend copies undefined values');
try {
result = {};
@@ -64,30 +64,52 @@
test('pick', function() {
var result;
result = _.pick({a:1, b:2, c:3}, 'a', 'c');
ok(_.isEqual(result, {a:1, c:3}), 'can restrict properties to those named');
result = _.pick({a:1, b:2, c:3}, ['b', 'c']);
ok(_.isEqual(result, {b:2, c:3}), 'can restrict properties to those named in an array');
result = _.pick({a:1, b:2, c:3}, ['a'], 'b');
ok(_.isEqual(result, {a:1, b:2}), 'can restrict properties to those named in mixed args');
result = _.pick({a: 1, b: 2, c: 3}, 'a', 'c');
deepEqual(result, {a: 1, c: 3}, 'can restrict properties to those named');
result = _.pick({a: 1, b: 2, c: 3}, ['b', 'c']);
deepEqual(result, {b: 2, c: 3}, 'can restrict properties to those named in an array');
result = _.pick({a: 1, b: 2, c: 3}, ['a'], 'b');
deepEqual(result, {a: 1, b: 2}, 'can restrict properties to those named in mixed args');
result = _.pick(['a', 'b'], 1);
deepEqual(result, {1: 'b'}, 'can pick numeric properties');
var data = {a: 1, b: 2, c: 3};
var callback = function(value, key, object) {
strictEqual(key, {1: 'a', 2: 'b', 3: 'c'}[value]);
strictEqual(object, data);
return value !== this.value;
};
result = _.pick(data, callback, {value: 2});
deepEqual(result, {a: 1, c: 3}, 'can accept a predicate and context');
var Obj = function(){};
Obj.prototype = {a: 1, b: 2, c: 3};
ok(_.isEqual(_.pick(new Obj, 'a', 'c'), {a:1, c: 3}), 'include prototype props');
deepEqual(_.pick(new Obj, 'a', 'c'), {a: 1, c: 3}, 'include prototype props');
});
test('omit', function() {
var result;
result = _.omit({a:1, b:2, c:3}, 'b');
ok(_.isEqual(result, {a:1, c:3}), 'can omit a single named property');
result = _.omit({a:1, b:2, c:3}, 'a', 'c');
ok(_.isEqual(result, {b:2}), 'can omit several named properties');
result = _.omit({a:1, b:2, c:3}, ['b', 'c']);
ok(_.isEqual(result, {a:1}), 'can omit properties named in an array');
result = _.omit({a: 1, b: 2, c: 3}, 'b');
deepEqual(result, {a: 1, c: 3}, 'can omit a single named property');
result = _.omit({a: 1, b: 2, c: 3}, 'a', 'c');
deepEqual(result, {b: 2}, 'can omit several named properties');
result = _.omit({a: 1, b: 2, c: 3}, ['b', 'c']);
deepEqual(result, {a: 1}, 'can omit properties named in an array');
result = _.omit(['a', 'b'], 0);
deepEqual(result, {1: 'b'}, 'can omit numeric properties');
var data = {a: 1, b: 2, c: 3};
var callback = function(value, key, object) {
strictEqual(key, {1: 'a', 2: 'b', 3: 'c'}[value]);
strictEqual(object, data);
return value !== this.value;
};
result = _.omit(data, callback, {value: 2});
deepEqual(result, {b: 2}, 'can accept a predicate');
var Obj = function(){};
Obj.prototype = {a: 1, b: 2, c: 3};
ok(_.isEqual(_.omit(new Obj, 'b'), {a:1, c: 3}), 'include prototype props');
deepEqual(_.omit(new Obj, 'b'), {a: 1, c: 3}, 'include prototype props');
});
test('defaults', function() {
@@ -389,6 +411,10 @@
var obj = {one : 1};
delete obj.one;
ok(_.isEmpty(obj), 'deleting all the keys from an object empties it');
var args = function(){ return arguments; };
ok(_.isEmpty(args()), 'empty arguments object is empty');
ok(!_.isEmpty(args('')), 'non-empty arguments object is not empty');
});
// Setup remote variables for iFrame tests.
@@ -587,14 +613,18 @@
ok(_.has(obj, "foo"), "has() works even when the hasOwnProperty method is deleted.");
var child = {};
child.prototype = obj;
ok(_.has(child, "foo") == false, "has() does not check the prototype chain for a property.")
ok(_.has(child, "foo") == false, "has() does not check the prototype chain for a property.");
});
test("matches", function() {
var moe = {name: 'Moe Howard', hair: true},
curly = {name: 'Curly Howard', hair: false},
stooges = [moe, curly];
ok(_.find(stooges, _.matches({hair: false})) === curly, "returns a predicate that can be used by finding functions.")
ok(_.find(stooges, _.matches(moe)) === moe, "can be used to locate an object exists in a collection.")
})
var moe = {name: 'Moe Howard', hair: true};
var curly = {name: 'Curly Howard', hair: false};
var stooges = [moe, curly];
ok(_.find(stooges, _.matches({hair: false})) === curly, "returns a predicate that can be used by finding functions.");
ok(_.find(stooges, _.matches(moe)) === moe, "can be used to locate an object exists in a collection.");
deepEqual(_.where([null, undefined], {a: 1}), [], 'Do not throw on null values.');
deepEqual(_.where([null, undefined], null), [null, undefined], 'null matches null');
deepEqual(_.where([null, undefined], {}), [null, undefined], 'null matches {}');
});
})();

View File

@@ -30,6 +30,10 @@
equal(_.constant(moe)(), moe, 'should create a function that returns moe');
});
test('noop', function() {
strictEqual(_.noop('curly', 'larry', 'moe'), undefined, 'should always return undefined');
});
test('property', function() {
var moe = {name : 'moe'};
equal(_.property('name')(moe), 'moe', 'should return the property with the given name');

View File

@@ -31,15 +31,6 @@
// All **ECMAScript 5** native function implementations that we hope to use
// are declared here.
var
nativeForEach = ArrayProto.forEach,
nativeMap = ArrayProto.map,
nativeReduce = ArrayProto.reduce,
nativeReduceRight = ArrayProto.reduceRight,
nativeFilter = ArrayProto.filter,
nativeEvery = ArrayProto.every,
nativeSome = ArrayProto.some,
nativeIndexOf = ArrayProto.indexOf,
nativeLastIndexOf = ArrayProto.lastIndexOf,
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind;
@@ -71,13 +62,11 @@
// --------------------
// The cornerstone, an `each` implementation, aka `forEach`.
// Handles objects with the built-in `forEach`, arrays, and raw objects.
// Delegates to **ECMAScript 5**'s native `forEach` if available.
var each = _.each = _.forEach = function(obj, iterator, context) {
// Handles raw objects in addition to array-likes. Treats all
// sparse array-likes as if they were dense.
_.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return obj;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
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;
}
@@ -91,12 +80,10 @@
};
// Return the results of applying the iterator to each element.
// Delegates to **ECMAScript 5**'s native `map` if available.
_.map = _.collect = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
each(obj, function(value, index, list) {
_.each(obj, function(value, index, list) {
results.push(iterator.call(context, value, index, list));
});
return results;
@@ -105,15 +92,11 @@
var reduceError = 'Reduce of empty array with no initial value';
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
// or `foldl`.
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
}
each(obj, function(value, index, list) {
_.each(obj, function(value, index, list) {
if (!initial) {
memo = value;
initial = true;
@@ -126,20 +109,15 @@
};
// The right-associative version of reduce, also known as `foldr`.
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
}
var length = obj.length;
if (length !== +length) {
var keys = _.keys(obj);
length = keys.length;
}
each(obj, function(value, index, list) {
_.each(obj, function(value, index, list) {
index = keys ? keys[--length] : --length;
if (!initial) {
memo = obj[index];
@@ -155,7 +133,7 @@
// Return the first value which passes a truth test. Aliased as `detect`.
_.find = _.detect = function(obj, predicate, context) {
var result;
any(obj, function(value, index, list) {
_.some(obj, function(value, index, list) {
if (predicate.call(context, value, index, list)) {
result = value;
return true;
@@ -165,13 +143,11 @@
};
// Return all the elements that pass a truth test.
// Delegates to **ECMAScript 5**'s native `filter` if available.
// Aliased as `select`.
_.filter = _.select = function(obj, predicate, context) {
var results = [];
if (obj == null) return results;
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(predicate, context);
each(obj, function(value, index, list) {
_.each(obj, function(value, index, list) {
if (predicate.call(context, value, index, list)) results.push(value);
});
return results;
@@ -179,34 +155,28 @@
// Return all the elements for which a truth test fails.
_.reject = function(obj, predicate, context) {
return _.filter(obj, function(value, index, list) {
return !predicate.call(context, value, index, list);
}, context);
return _.filter(obj, _.negate(predicate), context);
};
// Determine whether all of the elements match a truth test.
// Delegates to **ECMAScript 5**'s native `every` if available.
// Aliased as `all`.
_.every = _.all = function(obj, predicate, context) {
predicate || (predicate = _.identity);
var result = true;
if (obj == null) return result;
if (nativeEvery && obj.every === nativeEvery) return obj.every(predicate, context);
each(obj, function(value, index, list) {
_.each(obj, function(value, index, list) {
if (!(result = result && predicate.call(context, value, index, list))) return breaker;
});
return !!result;
};
// Determine if at least one element in the object matches a truth test.
// Delegates to **ECMAScript 5**'s native `some` if available.
// Aliased as `any`.
var any = _.some = _.any = function(obj, predicate, context) {
_.some = _.any = function(obj, predicate, context) {
predicate || (predicate = _.identity);
var result = false;
if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(predicate, context);
each(obj, function(value, index, list) {
_.each(obj, function(value, index, list) {
if (result || (result = predicate.call(context, value, index, list))) return breaker;
});
return !!result;
@@ -216,8 +186,8 @@
// Aliased as `include`.
_.contains = _.include = function(obj, target) {
if (obj == null) return false;
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
return any(obj, function(value) {
if (obj.length === +obj.length) return _.indexOf(obj, target) >= 0;
return _.some(obj, function(value) {
return value === target;
});
};
@@ -249,36 +219,48 @@
};
// Return the maximum element or (element-based computation).
// Can't optimize arrays of integers longer than 65,535 elements.
// See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797)
_.max = function(obj, iterator, context) {
if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
return Math.max.apply(Math, obj);
}
var result = -Infinity, lastComputed = -Infinity;
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
if (computed > lastComputed) {
result = value;
lastComputed = computed;
var result = -Infinity, lastComputed = -Infinity,
value, computed;
if (!iterator && _.isArray(obj)) {
for (var i = 0, length = obj.length; i < length; i++) {
value = obj[i];
if (value > result) {
result = value;
}
}
});
} else {
_.each(obj, function(value, index, list) {
computed = iterator ? iterator.call(context, value, index, list) : value;
if (computed > lastComputed) {
result = value;
lastComputed = computed;
}
});
}
return result;
};
// Return the minimum element (or element-based computation).
_.min = function(obj, iterator, context) {
if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
return Math.min.apply(Math, obj);
}
var result = Infinity, lastComputed = Infinity;
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
if (computed < lastComputed) {
result = value;
lastComputed = computed;
var result = Infinity, lastComputed = Infinity,
value, computed;
if (!iterator && _.isArray(obj)) {
for (var i = 0, length = obj.length; i < length; i++) {
value = obj[i];
if (value < result) {
result = value;
}
}
});
} else {
_.each(obj, function(value, index, list) {
computed = iterator ? iterator.call(context, value, index, list) : value;
if (computed < lastComputed) {
result = value;
lastComputed = computed;
}
});
}
return result;
};
@@ -288,7 +270,7 @@
var rand;
var index = 0;
var shuffled = [];
each(obj, function(value) {
_.each(obj, function(value) {
rand = _.random(index++);
shuffled[index - 1] = shuffled[rand];
shuffled[rand] = value;
@@ -308,20 +290,23 @@
};
// An internal function to generate lookup iterators.
var lookupIterator = function(value) {
var lookupIterator = function(value, context) {
if (value == null) return _.identity;
if (_.isFunction(value)) return value;
return _.property(value);
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);
iterator = lookupIterator(iterator, context);
return _.pluck(_.map(obj, function(value, index, list) {
return {
value: value,
index: index,
criteria: iterator.call(context, value, index, list)
criteria: iterator(value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria;
@@ -338,9 +323,9 @@
var group = function(behavior) {
return function(obj, iterator, context) {
var result = {};
iterator = lookupIterator(iterator);
each(obj, function(value, index) {
var key = iterator.call(context, value, index, obj);
iterator = lookupIterator(iterator, context);
_.each(obj, function(value, index) {
var key = iterator(value, index, obj);
behavior(result, key, value);
});
return result;
@@ -369,12 +354,12 @@
// 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);
var value = iterator.call(context, obj);
iterator = lookupIterator(iterator, context);
var value = iterator(obj);
var low = 0, high = array.length;
while (low < high) {
var mid = (low + high) >>> 1;
iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
iterator(array[mid]) < value ? low = mid + 1 : high = mid;
}
return low;
};
@@ -411,7 +396,7 @@
// the array, excluding the last N. The **guard** check allows it to work with
// `_.map`.
_.initial = function(array, n, guard) {
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
};
// Get the last element of an array. Passing **n** will return the last N
@@ -436,23 +421,26 @@
};
// Internal implementation of a recursive `flatten` function.
var flatten = function(input, shallow, output) {
var flatten = function(input, shallow, strict, output) {
if (shallow && _.every(input, _.isArray)) {
return concat.apply(output, input);
}
each(input, function(value) {
if (_.isArray(value) || _.isArguments(value)) {
shallow ? push.apply(output, value) : flatten(value, shallow, output);
for (var i = 0, length = input.length; i < length; i++) {
var value = input[i];
if (!_.isArray(value) && !_.isArguments(value)) {
if (!strict) output.push(value);
} else if (shallow) {
push.apply(output, value);
} else {
output.push(value);
flatten(value, shallow, strict, output);
}
});
}
return output;
};
// Flatten out an array, either recursively (by default), or just one level.
_.flatten = function(array, shallow) {
return flatten(array, shallow, []);
return flatten(array, shallow, false, []);
};
// Return a version of the array that does not contain the specified value(s).
@@ -462,9 +450,10 @@
// 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(array, predicate) {
_.partition = function(obj, predicate, context) {
predicate = lookupIterator(predicate, context);
var pass = [], fail = [];
each(array, function(elem) {
_.each(obj, function(elem) {
(predicate(elem) ? pass : fail).push(elem);
});
return [pass, fail];
@@ -474,44 +463,53 @@
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
_.uniq = _.unique = function(array, isSorted, iterator, context) {
if (array == null) return [];
if (_.isFunction(isSorted)) {
context = iterator;
iterator = isSorted;
isSorted = false;
}
var initial = iterator ? _.map(array, iterator, context) : array;
var results = [];
var result = [];
var seen = [];
each(initial, function(value, index) {
if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
seen.push(value);
results.push(array[index]);
for (var i = 0, length = array.length; i < length; i++) {
var value = array[i];
if (iterator) value = iterator.call(context, value, i, array);
if (isSorted ? (!i || seen !== value) : !_.contains(seen, value)) {
if (isSorted) seen = value;
else seen.push(value);
result.push(array[i]);
}
});
return results;
}
return result;
};
// Produce an array that contains the union: each distinct element from all of
// the passed-in arrays.
_.union = function() {
return _.uniq(_.flatten(arguments, true));
return _.uniq(flatten(arguments, true, true, []));
};
// Produce an array that contains every item shared between all the
// passed-in arrays.
_.intersection = function(array) {
var rest = slice.call(arguments, 1);
return _.filter(_.uniq(array), function(item) {
return _.every(rest, function(other) {
return _.contains(other, item);
});
});
if (array == null) return [];
var result = [];
var argsLength = arguments.length;
for (var i = 0, length = array.length; i < length; i++) {
var item = array[i];
if (_.contains(result, item)) continue;
for (var j = 1; j < argsLength; j++) {
if (!_.contains(arguments[j], item)) break;
}
if (j === argsLength) result.push(item);
}
return result;
};
// Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain.
_.difference = function(array) {
var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
var rest = flatten(slice.call(arguments, 1), true, true, []);
return _.filter(array, function(value){ return !_.contains(rest, value); });
};
@@ -542,10 +540,8 @@
return result;
};
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
// we need this function. Return the position of the first occurrence of an
// item in an array, or -1 if the item is not included in the array.
// Delegates to **ECMAScript 5**'s native `indexOf` if available.
// Return the position of the first occurrence of an item in an array,
// or -1 if the item is not included in the array.
// If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search.
_.indexOf = function(array, item, isSorted) {
@@ -559,19 +555,13 @@
return array[i] === item ? i : -1;
}
}
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
for (; i < length; i++) if (array[i] === item) return i;
return -1;
};
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
_.lastIndexOf = function(array, item, from) {
if (array == null) return -1;
var hasIndex = from != null;
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
}
var i = (hasIndex ? from : array.length);
var i = from == null ? array.length : from;
while (i--) if (array[i] === item) return i;
return -1;
};
@@ -645,7 +635,7 @@
_.bindAll = function(obj) {
var funcs = slice.call(arguments, 1);
if (funcs.length === 0) throw new Error('bindAll must be passed function names');
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
_.each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
return obj;
};
@@ -694,7 +684,7 @@
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0) {
if (remaining <= 0 || remaining > wait) {
clearTimeout(timeout);
timeout = null;
previous = now;
@@ -716,7 +706,8 @@
var later = function() {
var last = _.now() - timestamp;
if (last < wait) {
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
@@ -764,6 +755,13 @@
return _.partial(wrapper, func);
};
// Returns a negated version of the passed-in predicate.
_.negate = function(predicate) {
return function() {
return !predicate.apply(this, arguments);
};
};
// Returns a function that is the composition of a list of functions, each
// consuming the return value of the function that follows.
_.compose = function() {
@@ -843,7 +841,7 @@
// Extend a given object with all the properties in passed-in object(s).
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
_.each(slice.call(arguments, 1), function(source) {
if (source) {
for (var prop in source) {
obj[prop] = source[prop];
@@ -854,28 +852,38 @@
};
// Return a copy of the object only containing the whitelisted properties.
_.pick = function(obj) {
var copy = {};
var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
each(keys, function(key) {
if (key in obj) copy[key] = obj[key];
});
return copy;
_.pick = function(obj, iterator, context) {
var result = {};
if (_.isFunction(iterator)) {
for (var key in obj) {
var value = obj[key];
if (iterator.call(context, value, key, obj)) result[key] = value;
}
} else {
var keys = concat.apply([], slice.call(arguments, 1));
for (var i = 0, length = keys.length; i < length; i++) {
var key = keys[i];
if (key in obj) result[key] = obj[key];
}
}
return result;
};
// Return a copy of the object without the blacklisted properties.
_.omit = function(obj) {
var copy = {};
var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
for (var key in obj) {
if (!_.contains(keys, key)) copy[key] = obj[key];
_.omit = function(obj, iterator, context) {
var keys;
if (_.isFunction(iterator)) {
iterator = _.negate(iterator);
} else {
keys = _.map(concat.apply([], slice.call(arguments, 1)), String);
iterator = function(value, key) { return !_.contains(keys, key); };
}
return copy;
return _.pick(obj, iterator, context);
};
// Fill in a given object with default properties.
_.defaults = function(obj) {
each(slice.call(arguments, 1), function(source) {
_.each(slice.call(arguments, 1), function(source) {
if (source) {
for (var prop in source) {
if (obj[prop] === void 0) obj[prop] = source[prop];
@@ -1000,7 +1008,7 @@
// An "empty" object has no enumerable own-properties.
_.isEmpty = function(obj) {
if (obj == null) return true;
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
if (_.isArray(obj) || _.isString(obj) || _.isArguments(obj)) return obj.length === 0;
for (var key in obj) if (_.has(obj, key)) return false;
return true;
};
@@ -1022,7 +1030,7 @@
};
// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
_.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
_['is' + name] = function(obj) {
return toString.call(obj) == '[object ' + name + ']';
};
@@ -1090,11 +1098,13 @@
};
_.constant = function(value) {
return function () {
return function() {
return value;
};
};
_.noop = function(){};
_.property = function(key) {
return function(obj) {
return obj[key];
@@ -1104,11 +1114,9 @@
// Returns a predicate for checking whether an object has a given set of `key:value` pairs.
_.matches = function(attrs) {
return function(obj) {
if (obj === attrs) return true; //avoid comparing an object to itself.
for (var key in attrs) {
if (attrs[key] !== obj[key])
return false;
}
if (obj == null) return _.isEmpty(attrs);
if (obj === attrs) return true;
for (var key in attrs) if (attrs[key] !== obj[key]) return false;
return true;
}
};
@@ -1165,12 +1173,12 @@
_.result = function(object, property) {
if (object == null) return void 0;
var value = object[property];
return _.isFunction(value) ? value.call(object) : value;
return _.isFunction(value) ? object[property]() : value;
};
// Add your own custom functions to the Underscore object.
_.mixin = function(obj) {
each(_.functions(obj), function(name) {
_.each(_.functions(obj), function(name) {
var func = _[name] = obj[name];
_.prototype[name] = function() {
var args = [this._wrapped];
@@ -1208,18 +1216,20 @@
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\t': 't',
'\u2028': 'u2028',
'\u2029': 'u2029'
};
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
var escaper = /\\|'|\r|\n|\u2028|\u2029/g;
var escapeChar = function(match) {
return '\\' + escapes[match];
};
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
_.template = function(text, data, settings) {
var render;
settings = _.defaults({}, settings, _.templateSettings);
// Combine delimiters into one regular expression via alternation.
@@ -1233,19 +1243,18 @@
var index = 0;
var source = "__p+='";
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset)
.replace(escaper, function(match) { return '\\' + escapes[match]; });
source += text.slice(index, offset).replace(escaper, escapeChar);
index = offset + match.length;
if (escape) {
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
}
if (interpolate) {
} else if (interpolate) {
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
}
if (evaluate) {
} else if (evaluate) {
source += "';\n" + evaluate + "\n__p+='";
}
index = offset + match.length;
// Adobe VMs need the match returned to produce the correct offest.
return match;
});
source += "';\n";
@@ -1258,7 +1267,7 @@
source + "return __p;\n";
try {
render = new Function(settings.variable || 'obj', '_', source);
var render = new Function(settings.variable || 'obj', '_', source);
} catch (e) {
e.source = source;
throw e;
@@ -1269,8 +1278,9 @@
return render.call(this, data, _);
};
// Provide the compiled function source as a convenience for precompilation.
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
// Provide the compiled source as a convenience for precompilation.
var argument = settings.variable || 'obj';
template.source = 'function(' + argument + '){\n' + source + '}';
return template;
};
@@ -1295,7 +1305,7 @@
_.mixin(_);
// Add all mutator Array functions to the wrapper.
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
_.prototype[name] = function() {
var obj = this._wrapped;
@@ -1306,7 +1316,7 @@
});
// Add all accessor Array functions to the wrapper.
each(['concat', 'join', 'slice'], function(name) {
_.each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
_.prototype[name] = function() {
return result.call(this, method.apply(this._wrapped, arguments));