Update Underscore/Backbone tests and make them passable.

This commit is contained in:
John-David Dalton
2015-08-08 16:52:47 -07:00
parent de5c2b906e
commit bd9b38665d
13 changed files with 843 additions and 550 deletions

View File

@@ -38,12 +38,20 @@
'[1,2]', '[1,2]',
'0' '0'
], ],
'findIndex': [
'called with context'
],
'findLastIndex': [
'called with context'
],
'flatten': [ 'flatten': [
'Flattens empty arrays', 'Flattens empty arrays',
'can flatten nested arrays', 'can flatten nested arrays',
'can shallowly flatten nested arrays',
'works on an arguments object', 'works on an arguments object',
'can shallowly flatten arrays containing only other arrays' 'Flatten can handle very deep arrays'
],
'head': [
'alias for first'
], ],
'initial': [ 'initial': [
'initial can take an index', 'initial can take an index',
@@ -65,8 +73,25 @@
'rest can take an index', 'rest can take an index',
'works on arguments object' 'works on arguments object'
], ],
'sortedIndex': [
'2',
'3'
],
'tail': [
'alias for rest'
],
'take': [ 'take': [
'alias for first' 'alias for first'
],
'uniq': [
'can find the unique values of an array using a custom iterator',
'can find the unique values of an array using a custom iterator without specifying whether array is sorted',
'string iterator works with sorted array',
'can use pluck like iterator',
'can use falsey pluck like iterator'
],
'unique': [
'alias for uniq'
] ]
}, },
'Chaining': { 'Chaining': {
@@ -78,20 +103,104 @@
] ]
}, },
'Collections': { 'Collections': {
'lookupIterator with contexts': true,
'Iterating objects with sketchy length properties': true,
'Resistant to collection length and properties changing while iterating': true,
'all': [
'alias for all'
],
'any': [
'alias for any'
],
'collect': [
'alias for map'
],
'countBy': [
'true'
],
'detect': [
'alias for detect'
],
'each': [
'context object property accessed'
],
'every': [
'context works'
],
'filter': [ 'filter': [
'given context',
'OO-filter' 'OO-filter'
], ],
'find': [
'called with context'
],
'findWhere': [
'checks properties given function'
],
'foldl': [
'alias for reduce'
],
'foldr': [
'alias for reduceRight'
],
'groupBy': [
'true'
],
'invoke': [ 'invoke': [
'handles null & undefined' 'handles null & undefined'
], ],
'map': [ 'map': [
'tripled numbers with context',
'OO-style doubled numbers' 'OO-style doubled numbers'
], ],
'Resistant to collection length and properties changing while iterating': [ 'max': [
'Died on test #50' 'can perform a computation-based max',
'Respects iterator return value of -Infinity',
'String keys use property iterator',
'Lookup falsy iterator'
],
'min': [
'can perform a computation-based min',
'Respects iterator return value of Infinity',
'String keys use property iterator',
'Iterator context',
'Lookup falsy iterator'
],
'partition': [
'partition takes a context argument',
'function(a){[code]}'
],
'pluck': [
'[1]'
],
'reduce': [
'can reduce with a context object'
],
'reject': [
'Returns empty list given empty array'
],
'some': [
'context works'
],
'where': [
'checks properties given function'
],
'Can use various collection methods on NodeLists': [
'<span id="id2"></span>',
'<span id="id1"></span>'
] ]
}, },
'Functions': { 'Functions': {
'debounce asap': true,
'debounce after system time is set backwards': true,
'debounce asap recursively': true,
'throttle repeatedly with results': true,
'more throttle does not trigger leading call when leading is set to false': true,
'throttle does not trigger trailing call when trailing is set to false': true,
'before': [
'stores a memo to the last value',
'provides context'
],
'bind': [ 'bind': [
'Died on test #2' 'Died on test #2'
], ],
@@ -101,46 +210,65 @@
'memoize': [ 'memoize': [
'{"bar":"BAR","foo":"FOO"}', '{"bar":"BAR","foo":"FOO"}',
'Died on test #8' 'Died on test #8'
], ]
'throttle repeatedly with results': true,
'more throttle does not trigger leading call when leading is set to false': true,
'throttle does not trigger trailing call when trailing is set to false': true,
'debounce asap': true
}, },
'Objects': { 'Objects': {
'#1929 Typed Array constructors are functions': true, '#1929 Typed Array constructors are functions': true,
'allKeys': true, 'allKeys': [
'extendOwn': true, 'is not fooled by sparse arrays; see issue #95',
'mapObject': true, 'is not fooled by sparse arrays with additional properties',
'matcher': true, '[]'
'matcher ': true, ],
'defaults': [
'defaults skips nulls',
'defaults skips undefined'
],
'extend': [ 'extend': [
'extend copies all properties from source' 'extending null results in null',
'extending undefined results in undefined'
],
'extendOwn': [
'assigning non-objects results in returning the non-object value',
'assigning undefined results in undefined'
], ],
'isEqual': [ 'isEqual': [
'`0` is not equal to `-0`', '`0` is not equal to `-0`',
'Commutative equality is implemented for `0` and `-0`', 'Commutative equality is implemented for `0` and `-0`',
'`new Number(0)` and `-0` are not equal', '`new Number(0)` and `-0` are not equal',
'Commutative equality is implemented for `new Number(0)` and `-0`' 'Commutative equality is implemented for `new Number(0)` and `-0`',
'false'
], ],
'isFinite': [ 'isFinite': [
'Numeric strings are numbers', 'Numeric strings are numbers',
'Number instances can be finite' 'Number instances can be finite'
], ],
'isMatch': [ 'findKey': [
'inherited and own properties are checked on the test object', 'called with context'
'doesnt falsey match constructor on undefined/null'
], ],
'keys': [ 'keys': [
'is not fooled by sparse arrays; see issue #95', 'is not fooled by sparse arrays; see issue #95',
'[]' '[]'
], ],
'matches': [ 'mapObject': [
'inherited and own properties are checked on the test object', 'keep context',
'doesnt fasley match constructor on undefined/null' 'called with context',
'mapValue identity'
],
'matcher': [
'null matches null',
'treats primitives as empty'
],
'omit': [
'can accept a predicate',
'function is given context'
],
'pick': [
'can accept a predicate and context',
'function is given context'
] ]
}, },
'Utility': { 'Utility': {
'noConflict (node vm)': true,
'now': [ 'now': [
'Produces the correct time in milliseconds' 'Produces the correct time in milliseconds'
], ],
@@ -155,14 +283,38 @@
delete QUnit.config.excused.Functions['throttle repeatedly with results']; delete QUnit.config.excused.Functions['throttle repeatedly with results'];
delete QUnit.config.excused.Functions['more throttle does not trigger leading call when leading is set to false']; delete QUnit.config.excused.Functions['more throttle does not trigger leading call when leading is set to false'];
delete QUnit.config.excused.Functions['throttle does not trigger trailing call when trailing is set to false']; delete QUnit.config.excused.Functions['throttle does not trigger trailing call when trailing is set to false'];
delete QUnit.config.excused.Functions['debounce asap'];
delete QUnit.config.excused.Utility.now; delete QUnit.config.excused.Utility.now;
} }
// Load test scripts. // Load test scripts.
document.write(ui.urlParams.loader != 'none' document.write(ui.urlParams.loader == 'none'
? '<script data-dojo-config="async:1" src="' + ui.loaderPath + '"><\/script>' ? '<script src="' + ui.buildPath + '"><\/script>'
: ([ : '<script data-dojo-config="async:1" src="' + ui.loaderPath + '"><\/script>'
'<script src="' + ui.buildPath + '"><\/script>', );
</script>
<script>
function mixinPrereqs(_) {
_.mixin({
'allKeys': _.keysIn,
'compose': _.flowRight,
'contains': _.includes,
'extendOwn': _.assign,
'findWhere': _.find,
'include': _.includes,
'inject': _.reduce,
'mapObject': _.mapValues,
'matcher': _.matches,
'methods': _.functions,
'object': _.zipObject,
'pluck': _.map,
'restArgs': _.restParam,
'select': _.filter,
'where': _.filter
});
}
if (ui.urlParams.loader == 'none') {
mixinPrereqs(_);
document.write([
'<script src="../vendor/underscore/test/collections.js"><\/script>', '<script src="../vendor/underscore/test/collections.js"><\/script>',
'<script src="../vendor/underscore/test/arrays.js"><\/script>', '<script src="../vendor/underscore/test/arrays.js"><\/script>',
'<script src="../vendor/underscore/test/functions.js"><\/script>', '<script src="../vendor/underscore/test/functions.js"><\/script>',
@@ -170,8 +322,8 @@
'<script src="../vendor/underscore/test/cross-document.js"><\/script>', '<script src="../vendor/underscore/test/cross-document.js"><\/script>',
'<script src="../vendor/underscore/test/utility.js"><\/script>', '<script src="../vendor/underscore/test/utility.js"><\/script>',
'<script src="../vendor/underscore/test/chaining.js"><\/script>' '<script src="../vendor/underscore/test/chaining.js"><\/script>'
].join('\n')) ].join('\n'));
); }
</script> </script>
<script> <script>
(function() { (function() {
@@ -181,6 +333,13 @@
if (!window.require) { if (!window.require) {
return; return;
} }
// Wrap to work around tests assuming Node `require` use.
require = (function(func) {
return function() {
return arguments[0] === '..' ? window._ : func.apply(null, arguments);
};
}(require));
var reBasename = /[\w.-]+$/, var reBasename = /[\w.-]+$/,
basePath = ('//' + location.host + location.pathname.replace(reBasename, '')).replace(/\btest\/$/, ''), basePath = ('//' + location.host + location.pathname.replace(reBasename, '')).replace(/\btest\/$/, ''),
modulePath = ui.buildPath.replace(/\.js$/, ''), modulePath = ui.buildPath.replace(/\.js$/, ''),
@@ -221,6 +380,8 @@
QUnit.config.autostart = false; QUnit.config.autostart = false;
require(getConfig(), [moduleId], function(lodash) { require(getConfig(), [moduleId], function(lodash) {
mixinPrereqs(lodash);
if (ui.isModularize) { if (ui.isModularize) {
window._ = lodash; window._ = lodash;
} }

View File

@@ -977,16 +977,19 @@
// normal circumstances, as the set will maintain sort order as each item // normal circumstances, as the set will maintain sort order as each item
// is added. // is added.
sort: function(options) { sort: function(options) {
if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); var comparator = this.comparator;
if (!comparator) throw new Error('Cannot sort a set without a comparator');
options || (options = {}); options || (options = {});
// Run sort based on type of `comparator`. var length = comparator.length;
if (_.isString(this.comparator) || this.comparator.length === 1) { if (_.isFunction(comparator)) comparator = _.bind(comparator, this);
this.models = this.sortBy(this.comparator, this);
} else {
this.models.sort(_.bind(this.comparator, this));
}
// Run sort based on type of `comparator`.
if (length === 1 || _.isString(comparator)) {
this.models = this.sortBy(comparator);
} else {
this.models.sort(comparator);
}
if (!options.silent) this.trigger('sort', this, options); if (!options.silent) this.trigger('sort', this, options);
return this; return this;
}, },
@@ -1144,8 +1147,8 @@
// right here: // right here:
var collectionMethods = { forEach: 3, each: 3, map: 3, collect: 3, reduce: 4, var collectionMethods = { forEach: 3, each: 3, map: 3, collect: 3, reduce: 4,
foldl: 4, inject: 4, reduceRight: 4, foldr: 4, find: 3, detect: 3, filter: 3, foldl: 4, inject: 4, reduceRight: 4, foldr: 4, find: 3, detect: 3, filter: 3,
select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 2, select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3,
contains: 2, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3, contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3,
head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3, head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3,
without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3, without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3,
isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3, isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3,

View File

@@ -625,24 +625,26 @@
test("Underscore methods", 19, function() { test("Underscore methods", 19, function() {
equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d'); equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d');
equal(col.any(function(model){ return model.id === 100; }), false); equal(col.some(function(model){ return model.id === 100; }), false);
equal(col.any(function(model){ return model.id === 0; }), true); equal(col.some(function(model){ return model.id === 0; }), true);
equal(col.indexOf(b), 1); equal(col.indexOf(b), 1);
equal(col.size(), 4); equal(col.size(), 4);
equal(col.rest().length, 3); equal(col.rest().length, 3);
ok(!_.include(col.rest(), a)); ok(!_.includes(col.rest(), a));
ok(_.include(col.rest(), d)); ok(_.includes(col.rest(), d));
ok(!col.isEmpty()); ok(!col.isEmpty());
ok(!_.include(col.without(d), d)); ok(!_.includes(col.without(d), d));
equal(col.max(function(model){ return model.id; }).id, 3);
equal(col.min(function(model){ return model.id; }).id, 0); var wrapped = col.chain();
deepEqual(col.chain() equal(wrapped.map('id').max().value(), 3);
equal(wrapped.map('id').min().value(), 0);
deepEqual(wrapped
.filter(function(o){ return o.id % 2 === 0; }) .filter(function(o){ return o.id % 2 === 0; })
.map(function(o){ return o.id * 2; }) .map(function(o){ return o.id * 2; })
.value(), .value(),
[4, 0]); [4, 0]);
deepEqual(col.difference([c, d]), [a, b]); deepEqual(col.difference([c, d]), [a, b]);
ok(col.include(col.sample())); ok(col.includes(col.sample()));
var first = col.first(); var first = col.first();
deepEqual(col.groupBy(function(model){ return model.id; })[first.id], [first]); deepEqual(col.groupBy(function(model){ return model.id; })[first.id], [first]);
deepEqual(col.countBy(function(model){ return model.id; }), {0: 1, 1: 1, 2: 1, 3: 1}); deepEqual(col.countBy(function(model){ return model.id; }), {0: 1, 1: 1, 2: 1, 3: 1});
@@ -676,8 +678,8 @@
deepEqual(coll.partition({a: 4})[1], _.without(coll.models, model)); deepEqual(coll.partition({a: 4})[1], _.without(coll.models, model));
deepEqual(coll.map({a: 2}), [false, true, false, false]); deepEqual(coll.map({a: 2}), [false, true, false, false]);
deepEqual(coll.map('a'), [1, 2, 3, 4]); deepEqual(coll.map('a'), [1, 2, 3, 4]);
deepEqual(coll.max('a'), model); deepEqual(coll.sortBy('a')[3], model);
deepEqual(coll.min('e'), model); deepEqual(coll.sortBy('e')[0], model);
deepEqual(coll.countBy({a: 4}), {'false': 3, 'true': 1}); deepEqual(coll.countBy({a: 4}), {'false': 3, 'true': 1});
deepEqual(coll.countBy('d'), {'undefined': 4}); deepEqual(coll.countBy('d'), {'undefined': 4});
}); });
@@ -1175,7 +1177,7 @@
var Model = Backbone.Model.extend({}); var Model = Backbone.Model.extend({});
var Collection = Backbone.Collection.extend({ var Collection = Backbone.Collection.extend({
model: Model, model: Model,
parse: function (res) { return _.pluck(res.models, 'model'); } parse: function (res) { return _.map(res.models, 'model'); }
}); });
var model = new Model({id: 1}); var model = new Model({id: 1});
var collection = new Collection(model); var collection = new Collection(model);

View File

@@ -1,6 +1,4 @@
$('body').append( $('body').append(
'<div id="qunit"></div>' + '<div id="qunit"></div>' +
'<div id="qunit-fixture">' + '<div id="qunit-fixture"></div>'
'<div id="testElement"><h1>Test</h1></div>' +
'</div>'
); );

View File

@@ -5,6 +5,10 @@
module("Backbone.View", { module("Backbone.View", {
setup: function() { setup: function() {
$('#qunit-fixture').append(
'<div id="testElement"><h1>Test</h1></div>'
);
view = new Backbone.View({ view = new Backbone.View({
id : 'test-view', id : 'test-view',
className : 'test-view', className : 'test-view',

View File

@@ -16,7 +16,7 @@
result = (function() { return _.first([1, 2, 3], 2); }()); result = (function() { return _.first([1, 2, 3], 2); }());
deepEqual(result, [1, 2]); deepEqual(result, [1, 2]);
equal(_.first(null), undefined, 'handles nulls'); equal(_.first(null), void 0, 'handles nulls');
strictEqual(_.first([1, 2, 3], -1).length, 0); strictEqual(_.first([1, 2, 3], -1).length, 0);
}); });
@@ -69,7 +69,7 @@
result = _.map([[1, 2, 3], [1, 2, 3]], _.last); result = _.map([[1, 2, 3], [1, 2, 3]], _.last);
deepEqual(result, [3, 3], 'works well with _.map'); deepEqual(result, [3, 3], 'works well with _.map');
equal(_.last(null), undefined, 'handles nulls'); equal(_.last(null), void 0, 'handles nulls');
strictEqual(_.last([1, 2, 3], -1).length, 0); strictEqual(_.last([1, 2, 3], -1).length, 0);
}); });
@@ -98,6 +98,11 @@
equal(_.flatten([_.range(10), _.range(10), 5, 1, 3]).length, 23); equal(_.flatten([_.range(10), _.range(10), 5, 1, 3]).length, 23);
equal(_.flatten([new Array(1000000), _.range(56000), 5, 1, 3]).length, 1056003, 'Flatten can handle massive collections'); equal(_.flatten([new Array(1000000), _.range(56000), 5, 1, 3]).length, 1056003, 'Flatten can handle massive collections');
equal(_.flatten([new Array(1000000), _.range(56000), 5, 1, 3], true).length, 1056003, 'Flatten can handle massive collections'); equal(_.flatten([new Array(1000000), _.range(56000), 5, 1, 3], true).length, 1056003, 'Flatten can handle massive collections');
var x = _.range(100000);
for (var i = 0; i < 1000; i++) x = [x];
deepEqual(_.flatten(x), _.range(100000), 'Flatten can handle very deep arrays');
deepEqual(_.flatten(x, true), x[0], 'Flatten can handle very deep arrays with shallow');
}); });
test('without', function() { test('without', function() {
@@ -106,8 +111,8 @@
var result = (function(){ return _.without(arguments, 0, 1); }(1, 2, 1, 0, 3, 1, 4)); var result = (function(){ return _.without(arguments, 0, 1); }(1, 2, 1, 0, 3, 1, 4));
deepEqual(result, [2, 3, 4], 'works on an arguments object'); deepEqual(result, [2, 3, 4], 'works on an arguments object');
list = [{one : 1}, {two : 2}]; list = [{one: 1}, {two: 2}];
equal(_.without(list, {one : 1}).length, 2, 'uses real object identity for comparisons.'); equal(_.without(list, {one: 1}).length, 2, 'uses real object identity for comparisons.');
equal(_.without(list, list[0]).length, 1, 'ditto.'); equal(_.without(list, list[0]).length, 1, 'ditto.');
}); });
@@ -242,8 +247,8 @@
var names = ['moe', 'larry', 'curly'], ages = [30, 40, 50], leaders = [true]; var names = ['moe', 'larry', 'curly'], ages = [30, 40, 50], leaders = [true];
deepEqual(_.zip(names, ages, leaders), [ deepEqual(_.zip(names, ages, leaders), [
['moe', 30, true], ['moe', 30, true],
['larry', 40, undefined], ['larry', 40, void 0],
['curly', 50, undefined] ['curly', 50, void 0]
], 'zipped together arrays of different lengths'); ], 'zipped together arrays of different lengths');
var stooges = _.zip(['moe', 30, 'stooge 1'], ['larry', 40, 'stooge 2'], ['curly', 50, 'stooge 3']); var stooges = _.zip(['moe', 30, 'stooge 1'], ['larry', 40, 'stooge 2'], ['curly', 50, 'stooge 3']);
@@ -252,7 +257,7 @@
// In the case of difference lengths of the tuples undefineds // In the case of difference lengths of the tuples undefineds
// should be used as placeholder // should be used as placeholder
stooges = _.zip(['moe', 30], ['larry', 40], ['curly', 50, 'extra data']); stooges = _.zip(['moe', 30], ['larry', 40], ['curly', 50, 'extra data']);
deepEqual(stooges, [['moe', 'larry', 'curly'], [30, 40, 50], [undefined, undefined, 'extra data']], 'zipped pairs with empties'); deepEqual(stooges, [['moe', 'larry', 'curly'], [30, 40, 50], [void 0, void 0, 'extra data']], 'zipped pairs with empties');
var empty = _.zip([]); var empty = _.zip([]);
deepEqual(empty, [], 'unzipped empty'); deepEqual(empty, [], 'unzipped empty');
@@ -324,7 +329,7 @@
index = _.indexOf(numbers, 2, 5); index = _.indexOf(numbers, 2, 5);
equal(index, 7, 'supports the fromIndex argument'); equal(index, 7, 'supports the fromIndex argument');
index = _.indexOf([,,,], undefined); index = _.indexOf([,,, 0], void 0);
equal(index, 0, 'treats sparse arrays as if they were dense'); equal(index, 0, 'treats sparse arrays as if they were dense');
var array = [1, 2, 3, 1, 2, 3]; var array = [1, 2, 3, 1, 2, 3];
@@ -336,7 +341,7 @@
}); });
strictEqual(_.indexOf([1, 2, 3], 1, true), 0); strictEqual(_.indexOf([1, 2, 3], 1, true), 0);
index = _.indexOf([], undefined, true); index = _.indexOf([], void 0, true);
equal(index, -1, 'empty array with truthy `isSorted` returns -1'); equal(index, -1, 'empty array with truthy `isSorted` returns -1');
}); });
@@ -361,7 +366,7 @@
test('lastIndexOf', function() { test('lastIndexOf', function() {
var numbers = [1, 0, 1]; var numbers = [1, 0, 1];
var falsey = [void 0, '', 0, false, NaN, null, undefined]; var falsey = [void 0, '', 0, false, NaN, null, void 0];
equal(_.lastIndexOf(numbers, 1), 2); equal(_.lastIndexOf(numbers, 1), 2);
numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0]; numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0];
@@ -392,7 +397,7 @@
strictEqual(_.lastIndexOf(array, 1, 2), 0, 'should work with a positive `fromIndex`'); strictEqual(_.lastIndexOf(array, 1, 2), 0, 'should work with a positive `fromIndex`');
_.each([6, 8, Math.pow(2, 32), Infinity], function(fromIndex) { _.each([6, 8, Math.pow(2, 32), Infinity], function(fromIndex) {
strictEqual(_.lastIndexOf(array, undefined, fromIndex), -1); strictEqual(_.lastIndexOf(array, void 0, fromIndex), -1);
strictEqual(_.lastIndexOf(array, 1, fromIndex), 3); strictEqual(_.lastIndexOf(array, 1, fromIndex), 3);
strictEqual(_.lastIndexOf(array, '', fromIndex), -1); strictEqual(_.lastIndexOf(array, '', fromIndex), -1);
}); });
@@ -439,10 +444,10 @@
test('findIndex', function() { test('findIndex', function() {
var objects = [ var objects = [
{'a': 0, 'b': 0}, {a: 0, b: 0},
{'a': 1, 'b': 1}, {a: 1, b: 1},
{'a': 2, 'b': 2}, {a: 2, b: 2},
{'a': 0, 'b': 0} {a: 0, b: 0}
]; ];
equal(_.findIndex(objects, function(obj) { equal(_.findIndex(objects, function(obj) {
@@ -470,7 +475,7 @@
}, objects); }, objects);
var sparse = []; var sparse = [];
sparse[20] = {'a': 2, 'b': 2}; sparse[20] = {a: 2, b: 2};
equal(_.findIndex(sparse, function(obj) { equal(_.findIndex(sparse, function(obj) {
return obj && obj.b * obj.a === 4; return obj && obj.b * obj.a === 4;
}), 20, 'Works with sparse arrays'); }), 20, 'Works with sparse arrays');
@@ -482,10 +487,10 @@
test('findLastIndex', function() { test('findLastIndex', function() {
var objects = [ var objects = [
{'a': 0, 'b': 0}, {a: 0, b: 0},
{'a': 1, 'b': 1}, {a: 1, b: 1},
{'a': 2, 'b': 2}, {a: 2, b: 2},
{'a': 0, 'b': 0} {a: 0, b: 0}
]; ];
equal(_.findLastIndex(objects, function(obj) { equal(_.findLastIndex(objects, function(obj) {
@@ -513,7 +518,7 @@
}, objects); }, objects);
var sparse = []; var sparse = [];
sparse[20] = {'a': 2, 'b': 2}; sparse[20] = {a: 2, b: 2};
equal(_.findLastIndex(sparse, function(obj) { equal(_.findLastIndex(sparse, function(obj) {
return obj && obj.b * obj.a === 4; return obj && obj.b * obj.a === 4;
}), 20, 'Works with sparse arrays'); }), 20, 'Works with sparse arrays');
@@ -534,4 +539,19 @@
deepEqual(_.range(0, -10, -1), [0, -1, -2, -3, -4, -5, -6, -7, -8, -9], 'final example in the Python docs'); deepEqual(_.range(0, -10, -1), [0, -1, -2, -3, -4, -5, -6, -7, -8, -9], 'final example in the Python docs');
}); });
test('chunk', function() {
deepEqual(_.chunk([], 2), [], 'chunk for empty array returns an empty array');
deepEqual(_.chunk([1, 2, 3], 0), [], 'chunk into parts of 0 elements returns empty array');
deepEqual(_.chunk([1, 2, 3], -1), [], 'chunk into parts of negative amount of elements returns an empty array');
deepEqual(_.chunk([1, 2, 3]), [], 'defaults to empty array (chunk size 0)');
deepEqual(_.chunk([1, 2, 3], 1), [[1], [2], [3]], 'chunk into parts of 1 elements returns original array');
deepEqual(_.chunk([1, 2, 3], 3), [[1, 2, 3]], 'chunk into parts of current array length elements returns the original array');
deepEqual(_.chunk([1, 2, 3], 5), [[1, 2, 3]], 'chunk into parts of more then current array length elements returns the original array');
deepEqual(_.chunk([10, 20, 30, 40, 50, 60, 70], 2), [[10, 20], [30, 40], [50, 60], [70]], 'chunk into parts of less then current array length elements');
deepEqual(_.chunk([10, 20, 30, 40, 50, 60, 70], 3), [[10, 20, 30], [40, 50, 60], [70]], 'chunk into parts of less then current array length elements');
});
}()); }());

View File

@@ -17,7 +17,8 @@
hash[l] = hash[l] || 0; hash[l] = hash[l] || 0;
hash[l]++; hash[l]++;
return hash; return hash;
}, {}).value(); }, {})
.value();
equal(counts.a, 16, 'counted all the letters in the song'); equal(counts.a, 16, 'counted all the letters in the song');
equal(counts.e, 10, 'counted all the letters in the song'); equal(counts.e, 10, 'counted all the letters in the song');
}); });

View File

@@ -9,7 +9,7 @@
}); });
var answers = []; var answers = [];
_.each([1, 2, 3], function(num){ answers.push(num * this.multiplier);}, {multiplier : 5}); _.each([1, 2, 3], function(num){ answers.push(num * this.multiplier); }, {multiplier: 5});
deepEqual(answers, [5, 10, 15], 'context object property accessed'); deepEqual(answers, [5, 10, 15], 'context object property accessed');
answers = []; answers = [];
@@ -17,7 +17,7 @@
deepEqual(answers, [1, 2, 3], 'aliased as "forEach"'); deepEqual(answers, [1, 2, 3], 'aliased as "forEach"');
answers = []; answers = [];
var obj = {one : 1, two : 2, three : 3}; var obj = {one: 1, two: 2, three: 3};
obj.constructor.prototype.four = 4; obj.constructor.prototype.four = 4;
_.each(obj, function(value, key){ answers.push(key); }); _.each(obj, function(value, key){ answers.push(key); });
deepEqual(answers, ['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.');
@@ -26,8 +26,8 @@
// ensure the each function is JITed // ensure the each function is JITed
_(1000).times(function() { _.each([], function(){}); }); _(1000).times(function() { _.each([], function(){}); });
var count = 0; var count = 0;
obj = {1 : 'foo', 2 : 'bar', 3 : 'baz'}; obj = {1: 'foo', 2: 'bar', 3: 'baz'};
_.each(obj, function(value, key){ count++; }); _.each(obj, function(){ count++; });
equal(count, 3, 'the fun should be called only 3 times'); equal(count, 3, 'the fun should be called only 3 times');
var answer = null; var answer = null;
@@ -149,7 +149,7 @@
var doubled = _.map([1, 2, 3], function(num){ return num * 2; }); var doubled = _.map([1, 2, 3], function(num){ return num * 2; });
deepEqual(doubled, [2, 4, 6], 'doubled numbers'); deepEqual(doubled, [2, 4, 6], 'doubled numbers');
var tripled = _.map([1, 2, 3], function(num){ return num * this.multiplier; }, {multiplier : 3}); var tripled = _.map([1, 2, 3], function(num){ return num * this.multiplier; }, {multiplier: 3});
deepEqual(tripled, [3, 6, 9], 'tripled numbers with context'); deepEqual(tripled, [3, 6, 9], 'tripled numbers with context');
doubled = _([1, 2, 3]).map(function(num){ return num * 2; }); doubled = _([1, 2, 3]).map(function(num){ return num * 2; });
@@ -167,7 +167,7 @@
}, [5]), [1], 'called with context'); }, [5]), [1], 'called with context');
// Passing a property name like _.pluck. // Passing a property name like _.pluck.
var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}]; var people = [{name: 'moe', age: 30}, {name: 'curly', age: 50}];
deepEqual(_.map(people, 'name'), ['moe', 'curly'], 'predicate string map to object properties'); deepEqual(_.map(people, 'name'), ['moe', 'curly'], 'predicate string map to object properties');
}); });
@@ -176,29 +176,29 @@
}); });
test('reduce', function() { test('reduce', function() {
var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; }, 0); var sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 0);
equal(sum, 6, 'can sum up an array'); equal(sum, 6, 'can sum up an array');
var context = {multiplier : 3}; var context = {multiplier: 3};
sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num * this.multiplier; }, 0, context); sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num * this.multiplier; }, 0, context);
equal(sum, 18, 'can reduce with a context object'); equal(sum, 18, 'can reduce with a context object');
sum = _.inject([1, 2, 3], function(sum, num){ return sum + num; }, 0); sum = _.inject([1, 2, 3], function(memo, num){ return memo + num; }, 0);
equal(sum, 6, 'aliased as "inject"'); equal(sum, 6, 'aliased as "inject"');
sum = _([1, 2, 3]).reduce(function(sum, num){ return sum + num; }, 0); sum = _([1, 2, 3]).reduce(function(memo, num){ return memo + num; }, 0);
equal(sum, 6, 'OO-style reduce'); equal(sum, 6, 'OO-style reduce');
sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; }); sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; });
equal(sum, 6, 'default initial value'); equal(sum, 6, 'default initial value');
var prod = _.reduce([1, 2, 3, 4], function(prod, num){ return prod * num; }); var prod = _.reduce([1, 2, 3, 4], function(memo, num){ return memo * num; });
equal(prod, 24, 'can reduce via multiplication'); equal(prod, 24, 'can reduce via multiplication');
ok(_.reduce(null, _.noop, 138) === 138, 'handles a null (with initial value) properly'); ok(_.reduce(null, _.noop, 138) === 138, 'handles a null (with initial value) properly');
equal(_.reduce([], _.noop, undefined), undefined, 'undefined can be passed as a special case'); equal(_.reduce([], _.noop, void 0), void 0, 'undefined can be passed as a special case');
equal(_.reduce([_], _.noop), _, 'collection of length one with no initial value returns the first item'); equal(_.reduce([_], _.noop), _, 'collection of length one with no initial value returns the first item');
equal(_.reduce([], _.noop), undefined, 'returns undefined when collection is empty and no initial value'); equal(_.reduce([], _.noop), void 0, 'returns undefined when collection is empty and no initial value');
}); });
test('foldl', function() { test('foldl', function() {
@@ -212,45 +212,45 @@
list = _.reduceRight(['foo', 'bar', 'baz'], function(memo, str){ return memo + str; }); list = _.reduceRight(['foo', 'bar', 'baz'], function(memo, str){ return memo + str; });
equal(list, 'bazbarfoo', 'default initial value'); equal(list, 'bazbarfoo', 'default initial value');
var sum = _.reduceRight({a: 1, b: 2, c: 3}, function(sum, num){ return sum + num; }); var sum = _.reduceRight({a: 1, b: 2, c: 3}, function(memo, num){ return memo + num; });
equal(sum, 6, 'default initial value on object'); equal(sum, 6, 'default initial value on object');
ok(_.reduceRight(null, _.noop, 138) === 138, 'handles a null (with initial value) properly'); ok(_.reduceRight(null, _.noop, 138) === 138, 'handles a null (with initial value) properly');
equal(_.reduceRight([_], _.noop), _, 'collection of length one with no initial value returns the first item'); equal(_.reduceRight([_], _.noop), _, 'collection of length one with no initial value returns the first item');
equal(_.reduceRight([], _.noop, undefined), undefined, 'undefined can be passed as a special case'); equal(_.reduceRight([], _.noop, void 0), void 0, 'undefined can be passed as a special case');
equal(_.reduceRight([], _.noop), undefined, 'returns undefined when collection is empty and no initial value'); equal(_.reduceRight([], _.noop), void 0, 'returns undefined when collection is empty and no initial value');
// Assert that the correct arguments are being passed. // Assert that the correct arguments are being passed.
var args, var args,
memo = {}, init = {},
object = {a: 1, b: 2}, object = {a: 1, b: 2},
lastKey = _.keys(object).pop(); lastKey = _.keys(object).pop();
var expected = lastKey === 'a' var expected = lastKey === 'a'
? [memo, 1, 'a', object] ? [init, 1, 'a', object]
: [memo, 2, 'b', object]; : [init, 2, 'b', object];
_.reduceRight(object, function() { _.reduceRight(object, function() {
if (!args) args = _.toArray(arguments); if (!args) args = _.toArray(arguments);
}, memo); }, init);
deepEqual(args, expected); deepEqual(args, expected);
// And again, with numeric keys. // And again, with numeric keys.
object = {'2': 'a', '1': 'b'}; object = {2: 'a', 1: 'b'};
lastKey = _.keys(object).pop(); lastKey = _.keys(object).pop();
args = null; args = null;
expected = lastKey === '2' expected = lastKey === '2'
? [memo, 'a', '2', object] ? [init, 'a', '2', object]
: [memo, 'b', '1', object]; : [init, 'b', '1', object];
_.reduceRight(object, function() { _.reduceRight(object, function() {
if (!args) args = _.toArray(arguments); if (!args) args = _.toArray(arguments);
}, memo); }, init);
deepEqual(args, expected); deepEqual(args, expected);
}); });
@@ -290,9 +290,9 @@
return x.x === 4; return x.x === 4;
}), {x: 4, z: 1}); }), {x: 4, z: 1});
_.findIndex([{a: 1}], function(a, key, obj) { _.findIndex([{a: 1}], function(a, key, o) {
equal(key, 0); equal(key, 0);
deepEqual(obj, [{a: 1}]); deepEqual(o, [{a: 1}]);
strictEqual(this, _, 'called with context'); strictEqual(this, _, 'called with context');
}, _); }, _);
}); });
@@ -356,7 +356,7 @@
ok(!_.every([0, 11, 28], function(num){ return num % 2 === 0; }), 'an odd number'); ok(!_.every([0, 11, 28], function(num){ return num % 2 === 0; }), 'an odd number');
ok(_.every([1], _.identity) === true, 'cast to boolean - true'); ok(_.every([1], _.identity) === true, 'cast to boolean - true');
ok(_.every([0], _.identity) === false, 'cast to boolean - false'); ok(_.every([0], _.identity) === false, 'cast to boolean - false');
ok(!_.every([undefined, undefined, undefined], _.identity), 'works with arrays of undefined'); ok(!_.every([void 0, void 0, void 0], _.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 list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}];
ok(!_.every(list, {a: 1, b: 2}), 'Can be called with object'); ok(!_.every(list, {a: 1, b: 2}), 'Can be called with object');
@@ -493,13 +493,13 @@
deepEqual(result[0], [1, 5, 7], 'first array sorted'); deepEqual(result[0], [1, 5, 7], 'first array sorted');
deepEqual(result[1], [1, 2, 3], 'second array sorted'); deepEqual(result[1], [1, 2, 3], 'second array sorted');
delete String.prototype.call; delete String.prototype.call;
equal(s.call, undefined, 'call function removed'); equal(s.call, void 0, 'call function removed');
}); });
test('pluck', function() { 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'); deepEqual(_.pluck(people, 'name'), ['moe', 'curly'], 'pulls names out of objects');
deepEqual(_.pluck(people, 'address'), [undefined, undefined], 'missing properties are returned as undefined'); deepEqual(_.pluck(people, 'address'), [void 0, void 0], 'missing properties are returned as undefined');
//compat: most flexible handling of edge cases //compat: most flexible handling of edge cases
deepEqual(_.pluck([{'[object Object]': 1}], {}), [1]); deepEqual(_.pluck([{'[object Object]': 1}], {}), [1]);
}); });
@@ -547,7 +547,7 @@
test('max', function() { test('max', function() {
equal(-Infinity, _.max(null), 'can handle null/undefined'); equal(-Infinity, _.max(null), 'can handle null/undefined');
equal(-Infinity, _.max(undefined), 'can handle null/undefined'); equal(-Infinity, _.max(void 0), 'can handle null/undefined');
equal(-Infinity, _.max(null, _.identity), 'can handle null/undefined'); equal(-Infinity, _.max(null, _.identity), 'can handle null/undefined');
equal(3, _.max([1, 2, 3]), 'can perform a regular Math.max'); equal(3, _.max([1, 2, 3]), 'can perform a regular Math.max');
@@ -557,28 +557,30 @@
equal(-Infinity, _.max({}), 'Maximum value of an empty object'); equal(-Infinity, _.max({}), 'Maximum value of an empty object');
equal(-Infinity, _.max([]), 'Maximum value of an empty array'); equal(-Infinity, _.max([]), 'Maximum value of an empty array');
equal(_.max({'a': 'a'}), -Infinity, 'Maximum value of a non-numeric collection'); 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(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([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'); equal(3, _.max(['test', 1, 2, 3]), 'Finds correct max in array starting with NaN');
deepEqual([3, 6], _.map([[1, 2, 3], [4, 5, 6]], _.max), 'Finds correct max in array when mapping through multiple arrays');
var a = {x: -Infinity}; var a = {x: -Infinity};
var b = {x: -Infinity}; var b = {x: -Infinity};
var iterator = function(o){ return o.x; }; var iterator = function(o){ return o.x; };
equal(_.max([a, b], iterator), a, 'Respects iterator return value of -Infinity'); 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'); deepEqual(_.max([{a: 1}, {a: 0, b: 3}, {a: 4}, {a: 2}], 'a'), {a: 4}, 'String keys use property iterator');
deepEqual(_.max([0, 2], function(a){ return a * this.x; }, {x: 1}), 2, 'Iterator context'); deepEqual(_.max([0, 2], function(c){ return c * this.x; }, {x: 1}), 2, 'Iterator context');
deepEqual(_.max([[1], [2, 3], [-1, 4], [5]], 0), [5], 'Lookup falsy iterator'); deepEqual(_.max([[1], [2, 3], [-1, 4], [5]], 0), [5], 'Lookup falsy iterator');
deepEqual(_.max([{0: 1}, {0: 2}, {0: -1}, {a: 1}], 0), {0: 2}, 'Lookup falsy iterator'); deepEqual(_.max([{0: 1}, {0: 2}, {0: -1}, {a: 1}], 0), {0: 2}, 'Lookup falsy iterator');
}); });
test('min', function() { test('min', function() {
equal(Infinity, _.min(null), 'can handle null/undefined'); equal(Infinity, _.min(null), 'can handle null/undefined');
equal(Infinity, _.min(undefined), 'can handle null/undefined'); equal(Infinity, _.min(void 0), 'can handle null/undefined');
equal(Infinity, _.min(null, _.identity), 'can handle null/undefined'); equal(Infinity, _.min(null, _.identity), 'can handle null/undefined');
equal(1, _.min([1, 2, 3]), 'can perform a regular Math.min'); equal(1, _.min([1, 2, 3]), 'can perform a regular Math.min');
@@ -588,7 +590,9 @@
equal(Infinity, _.min({}), 'Minimum value of an empty object'); equal(Infinity, _.min({}), 'Minimum value of an empty object');
equal(Infinity, _.min([]), 'Minimum value of an empty array'); equal(Infinity, _.min([]), 'Minimum value of an empty array');
equal(_.min({'a': 'a'}), Infinity, 'Minimum value of a non-numeric collection'); equal(_.min({a: 'a'}), Infinity, 'Minimum value of a non-numeric collection');
deepEqual([1, 4], _.map([[1, 2, 3], [4, 5, 6]], _.min), 'Finds correct min in array when mapping through multiple arrays');
var now = new Date(9999999999); var now = new Date(9999999999);
var then = new Date(0); var then = new Date(0);
@@ -604,20 +608,20 @@
var iterator = function(o){ return o.x; }; var iterator = function(o){ return o.x; };
equal(_.min([a, b], iterator), a, 'Respects iterator return value of Infinity'); 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'); deepEqual(_.min([{a: 1}, {a: 0, b: 3}, {a: 4}, {a: 2}], 'a'), {a: 0, b: 3}, 'String keys use property iterator');
deepEqual(_.min([0, 2], function(a){ return a * this.x; }, {x: -1}), 2, 'Iterator context'); deepEqual(_.min([0, 2], function(c){ return c * this.x; }, {x: -1}), 2, 'Iterator context');
deepEqual(_.min([[1], [2, 3], [-1, 4], [5]], 0), [-1, 4], 'Lookup falsy iterator'); deepEqual(_.min([[1], [2, 3], [-1, 4], [5]], 0), [-1, 4], 'Lookup falsy iterator');
deepEqual(_.min([{0: 1}, {0: 2}, {0: -1}, {a: 1}], 0), {0: -1}, 'Lookup falsy iterator'); deepEqual(_.min([{0: 1}, {0: 2}, {0: -1}, {a: 1}], 0), {0: -1}, 'Lookup falsy iterator');
}); });
test('sortBy', function() { test('sortBy', function() {
var people = [{name : 'curly', age : 50}, {name : 'moe', age : 30}]; var people = [{name: 'curly', age: 50}, {name: 'moe', age: 30}];
people = _.sortBy(people, function(person){ return person.age; }); people = _.sortBy(people, function(person){ return person.age; });
deepEqual(_.pluck(people, 'name'), ['moe', 'curly'], 'stooges sorted by age'); deepEqual(_.pluck(people, 'name'), ['moe', 'curly'], 'stooges sorted by age');
var list = [undefined, 4, 1, undefined, 3, 2]; var list = [void 0, 4, 1, void 0, 3, 2];
deepEqual(_.sortBy(list, _.identity), [1, 2, 3, 4, undefined, undefined], 'sortBy with undefined values'); deepEqual(_.sortBy(list, _.identity), [1, 2, 3, 4, void 0, void 0], 'sortBy with undefined values');
list = ['one', 'two', 'three', 'four', 'five']; list = ['one', 'two', 'three', 'four', 'five'];
var sorted = _.sortBy(list, 'length'); var sorted = _.sortBy(list, 'length');
@@ -628,25 +632,32 @@
this.y = y; this.y = y;
} }
var collection = [ var stableArray = [
new Pair(1, 1), new Pair(1, 2), new Pair(1, 1), new Pair(1, 2),
new Pair(1, 3), new Pair(1, 4), new Pair(1, 3), new Pair(1, 4),
new Pair(1, 5), new Pair(1, 6), new Pair(1, 5), new Pair(1, 6),
new Pair(2, 1), new Pair(2, 2), new Pair(2, 1), new Pair(2, 2),
new Pair(2, 3), new Pair(2, 4), new Pair(2, 3), new Pair(2, 4),
new Pair(2, 5), new Pair(2, 6), new Pair(2, 5), new Pair(2, 6),
new Pair(undefined, 1), new Pair(undefined, 2), new Pair(void 0, 1), new Pair(void 0, 2),
new Pair(undefined, 3), new Pair(undefined, 4), new Pair(void 0, 3), new Pair(void 0, 4),
new Pair(undefined, 5), new Pair(undefined, 6) new Pair(void 0, 5), new Pair(void 0, 6)
]; ];
var actual = _.sortBy(collection, function(pair) { var stableObject = _.object('abcdefghijklmnopqr'.split(''), stableArray);
var actual = _.sortBy(stableArray, function(pair) {
return pair.x; return pair.x;
}); });
deepEqual(actual, collection, 'sortBy should be stable'); deepEqual(actual, stableArray, 'sortBy should be stable for arrays');
deepEqual(_.sortBy(stableArray, 'x'), stableArray, 'sortBy accepts property string');
deepEqual(_.sortBy(collection, 'x'), collection, 'sortBy accepts property string'); actual = _.sortBy(stableObject, function(pair) {
return pair.x;
});
deepEqual(actual, stableArray, 'sortBy should be stable for objects');
list = ['q', 'w', 'e', 'r', 't', 'y']; list = ['q', 'w', 'e', 'r', 't', 'y'];
deepEqual(_.sortBy(list), ['e', 'q', 'r', 't', 'w', 'y'], 'uses _.identity if iterator is not specified'); deepEqual(_.sortBy(list), ['e', 'q', 'r', 't', 'w', 'y'], 'uses _.identity if iterator is not specified');
@@ -737,12 +748,12 @@
}); });
test('shuffle', function() { test('shuffle', function() {
var numbers = _.range(10); deepEqual(_.shuffle([1]), [1], 'behaves correctly on size 1 arrays');
var numbers = _.range(20);
var shuffled = _.shuffle(numbers); var shuffled = _.shuffle(numbers);
notDeepEqual(numbers, shuffled, 'does change the order'); // Chance of false negative: 1 in ~2.4*10^18
notStrictEqual(numbers, shuffled, 'original object is unmodified'); notStrictEqual(numbers, shuffled, 'original object is unmodified');
ok(_.every(_.range(10), function() { //appears consistent? deepEqual(numbers, _.sortBy(shuffled), 'contains the same members before and after shuffle');
return _.every(numbers, _.partial(_.contains, numbers));
}), 'contains the same members before and after shuffle');
shuffled = _.shuffle({a: 1, b: 2, c: 3, d: 4}); shuffled = _.shuffle({a: 1, b: 2, c: 3, d: 4});
equal(shuffled.length, 4); equal(shuffled.length, 4);
@@ -750,17 +761,22 @@
}); });
test('sample', function() { test('sample', function() {
strictEqual(_.sample([1]), 1, 'behaves correctly when no second parameter is given');
deepEqual(_.sample([1, 2, 3], -2), [], 'behaves correctly on negative n');
var numbers = _.range(10); var numbers = _.range(10);
var allSampled = _.sample(numbers, 10).sort(); var allSampled = _.sample(numbers, 10).sort();
deepEqual(allSampled, numbers, 'contains the same members before and after sample'); deepEqual(allSampled, numbers, 'contains the same members before and after sample');
allSampled = _.sample(numbers, 20).sort(); allSampled = _.sample(numbers, 20).sort();
deepEqual(allSampled, numbers, 'also works when sampling more objects than are present'); deepEqual(allSampled, numbers, 'also works when sampling more objects than are present');
ok(_.contains(numbers, _.sample(numbers)), 'sampling a single element returns something from the array'); 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'); strictEqual(_.sample([]), void 0, 'sampling empty array with no number returns undefined');
notStrictEqual(_.sample([], 5), [], 'sampling empty array with a number returns an empty array'); notStrictEqual(_.sample([], 5), [], 'sampling empty array with a number returns an empty array');
notStrictEqual(_.sample([1, 2, 3], 0), [], 'sampling an array with 0 picks returns an empty array'); notStrictEqual(_.sample([1, 2, 3], 0), [], 'sampling an array with 0 picks returns an empty array');
deepEqual(_.sample([1, 2], -1), [], 'sampling a negative number of picks returns an empty array'); deepEqual(_.sample([1, 2], -1), [], 'sampling a negative number of picks returns an empty array');
ok(_.contains([1, 2, 3], _.sample({a: 1, b: 2, c: 3})), 'sample one value from an object'); ok(_.contains([1, 2, 3], _.sample({a: 1, b: 2, c: 3})), 'sample one value from an object');
var partialSample = _.sample(_.range(1000), 10);
var partialSampleSorted = partialSample.sort();
notDeepEqual(partialSampleSorted, _.range(10), 'samples from the whole array, not just the beginning');
}); });
test('toArray', function() { test('toArray', function() {
@@ -770,7 +786,7 @@
ok(_.toArray(a) !== a, 'array is cloned'); ok(_.toArray(a) !== a, 'array is cloned');
deepEqual(_.toArray(a), [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}); var numbers = _.toArray({one: 1, two: 2, three: 3});
deepEqual(numbers, [1, 2, 3], 'object flattened into array'); deepEqual(numbers, [1, 2, 3], 'object flattened into array');
if (typeof document != 'undefined') { if (typeof document != 'undefined') {
@@ -778,13 +794,13 @@
var actual; var actual;
try { try {
actual = _.toArray(document.childNodes); actual = _.toArray(document.childNodes);
} catch(ex) { } } catch(e) { /* ignored */ }
deepEqual(actual, _.map(document.childNodes, _.identity), 'works on NodeList'); deepEqual(actual, _.map(document.childNodes, _.identity), 'works on NodeList');
} }
}); });
test('size', function() { test('size', function() {
equal(_.size({one : 1, two : 2, three : 3}), 3, 'can compute the size of an object'); equal(_.size({one: 1, two: 2, three: 3}), 3, 'can compute the size of an object');
equal(_.size([1, 2, 3]), 3, 'can compute the size of an array'); equal(_.size([1, 2, 3]), 3, 'can compute the size of an array');
equal(_.size({length: 3, 0: 0, 1: 0, 2: 0}), 3, 'can compute the size of Array-likes'); equal(_.size({length: 3, 0: 0, 1: 0, 2: 0}), 3, 'can compute the size of Array-likes');
@@ -834,23 +850,23 @@
if (typeof document != 'undefined') { if (typeof document != 'undefined') {
test('Can use various collection methods on NodeLists', function() { test('Can use various collection methods on NodeLists', function() {
var parent = document.createElement('div'); var parent = document.createElement('div');
parent.innerHTML = '<span id=id1></span>textnode<span id=id2></span>'; parent.innerHTML = '<span id=id1></span>textnode<span id=id2></span>';
var elementChildren = _.filter(parent.childNodes, _.isElement); var elementChildren = _.filter(parent.childNodes, _.isElement);
equal(elementChildren.length, 2); equal(elementChildren.length, 2);
deepEqual(_.map(elementChildren, 'id'), ['id1', 'id2']); deepEqual(_.map(elementChildren, 'id'), ['id1', 'id2']);
deepEqual(_.map(parent.childNodes, 'nodeType'), [1, 3, 1]); deepEqual(_.map(parent.childNodes, 'nodeType'), [1, 3, 1]);
ok(!_.every(parent.childNodes, _.isElement)); ok(!_.every(parent.childNodes, _.isElement));
ok(_.some(parent.childNodes, _.isElement)); ok(_.some(parent.childNodes, _.isElement));
function compareNode(node) { function compareNode(node) {
return _.isElement(node) ? node.id.charAt(2) : void 0; return _.isElement(node) ? node.id.charAt(2) : void 0;
} }
equal(_.max(parent.childNodes, compareNode), _.last(parent.childNodes)); equal(_.max(parent.childNodes, compareNode), _.last(parent.childNodes));
equal(_.min(parent.childNodes, compareNode), _.first(parent.childNodes)); equal(_.min(parent.childNodes, compareNode), _.first(parent.childNodes));
}); });
} }

View File

@@ -5,7 +5,7 @@
QUnit.config.asyncRetries = 3; QUnit.config.asyncRetries = 3;
test('bind', function() { test('bind', function() {
var context = {name : 'moe'}; var context = {name: 'moe'};
var func = function(arg) { return 'name: ' + (this.name || arg); }; var func = function(arg) { return 'name: ' + (this.name || arg); };
var bound = _.bind(func, context); var bound = _.bind(func, context);
equal(bound(), 'name: moe', 'can bind a function to a context'); equal(bound(), 'name: moe', 'can bind a function to a context');
@@ -29,18 +29,18 @@
func = _.bind(func, this, 'hello', 'moe', 'curly'); func = _.bind(func, this, 'hello', 'moe', 'curly');
equal(func(), 'hello: moe curly', 'the function was partially applied in advance and can accept multiple arguments'); equal(func(), 'hello: moe curly', 'the function was partially applied in advance and can accept multiple arguments');
func = function(context, message) { equal(this, context, message); }; func = function(ctx, message) { equal(this, ctx, message); };
_.bind(func, 0, 0, 'can bind a function to `0`')(); _.bind(func, 0, 0, 'can bind a function to `0`')();
_.bind(func, '', '', 'can bind a function to an empty string')(); _.bind(func, '', '', 'can bind a function to an empty string')();
_.bind(func, false, false, 'can bind a function to `false`')(); _.bind(func, false, false, 'can bind a function to `false`')();
// These tests are only meaningful when using a browser without a native bind function // These tests are only meaningful when using a browser without a native bind function
// To test this with a modern browser, set underscore's nativeBind to undefined // To test this with a modern browser, set underscore's nativeBind to undefined
var F = function () { return this; }; var F = function() { return this; };
var boundf = _.bind(F, {hello: 'moe curly'}); var boundf = _.bind(F, {hello: 'moe curly'});
var Boundf = boundf; // make eslint happy. var Boundf = boundf; // make eslint happy.
var newBoundf = new Boundf(); var newBoundf = new Boundf();
equal(newBoundf.hello, undefined, 'function should not be bound to the context, to comply with ECMAScript 5'); equal(newBoundf.hello, void 0, 'function should not be bound to the context, to comply with ECMAScript 5');
equal(boundf().hello, 'moe curly', "When called without the new operator, it's OK to be bound to the context"); equal(boundf().hello, 'moe curly', "When called without the new operator, it's OK to be bound to the context");
ok(newBoundf instanceof F, 'a bound instance is an instance of the original function'); ok(newBoundf instanceof F, 'a bound instance is an instance of the original function');
@@ -77,13 +77,23 @@
ok(widget instanceof MyWidget, 'Can partially bind a constructor'); ok(widget instanceof MyWidget, 'Can partially bind a constructor');
equal(widget.get(), 'foo', 'keeps prototype'); equal(widget.get(), 'foo', 'keeps prototype');
deepEqual(widget.options, {a: 1}); deepEqual(widget.options, {a: 1});
_.partial.placeholder = obj;
func = _.partial(function() { return arguments.length; }, obj, 'b', obj, 'd');
equal(func('a'), 4, 'allows the placeholder to be swapped out');
_.partial.placeholder = {};
func = _.partial(function() { return arguments.length; }, obj, 'b', obj, 'd');
equal(func('a'), 5, 'swapping the placeholder preserves previously bound arguments');
_.partial.placeholder = _;
}); });
test('bindAll', function() { test('bindAll', function() {
var curly = {name : 'curly'}, moe = { var curly = {name: 'curly'}, moe = {
name : 'moe', name: 'moe',
getName : function() { return 'name: ' + this.name; }, getName: function() { return 'name: ' + this.name; },
sayHi : function() { return 'hi: ' + this.name; } sayHi: function() { return 'hi: ' + this.name; }
}; };
curly.getName = moe.getName; curly.getName = moe.getName;
_.bindAll(moe, 'getName', 'sayHi'); _.bindAll(moe, 'getName', 'sayHi');
@@ -91,12 +101,12 @@
equal(curly.getName(), 'name: curly', 'unbound function is bound to current object'); equal(curly.getName(), 'name: curly', 'unbound function is bound to current object');
equal(curly.sayHi(), 'hi: moe', 'bound function is still bound to original object'); equal(curly.sayHi(), 'hi: moe', 'bound function is still bound to original object');
curly = {name : 'curly'}; curly = {name: 'curly'};
moe = { moe = {
name : 'moe', name: 'moe',
getName : function() { return 'name: ' + this.name; }, getName: function() { return 'name: ' + this.name; },
sayHi : function() { return 'hi: ' + this.name; }, sayHi: function() { return 'hi: ' + this.name; },
sayLast : function() { return this.sayHi(_.last(arguments)); } sayLast: function() { return this.sayHi(_.last(arguments)); }
}; };
throws(function() { _.bindAll(moe); }, Error, 'throws an error for bindAll with no functions named'); throws(function() { _.bindAll(moe); }, Error, 'throws an error for bindAll with no functions named');
@@ -109,6 +119,10 @@
var sayLast = moe.sayLast; var sayLast = moe.sayLast;
equal(sayLast(1, 2, 3, 4, 5, 6, 7, 'Tom'), 'hi: moe', 'createCallback works with any number of arguments'); equal(sayLast(1, 2, 3, 4, 5, 6, 7, 'Tom'), 'hi: moe', 'createCallback works with any number of arguments');
_.bindAll(moe, ['getName']);
var getName = moe.getName;
equal(getName(), 'name: moe', 'flattens arguments into a single list');
}); });
test('memoize', function() { test('memoize', function() {
@@ -145,7 +159,7 @@
return key.toUpperCase(); return key.toUpperCase();
}); });
hashed('yep'); hashed('yep');
deepEqual(hashed.cache, {'YEP': 'yep'}, 'takes a hasher'); deepEqual(hashed.cache, {YEP: 'yep'}, 'takes a hasher');
// Test that the hash function can be used to swizzle the key. // Test that the hash function can be used to swizzle the key.
var objCacher = _.memoize(function(value, key) { var objCacher = _.memoize(function(value, key) {
@@ -155,7 +169,7 @@
}); });
var myObj = objCacher('a', 'alpha'); var myObj = objCacher('a', 'alpha');
var myObjAlias = objCacher('b', 'alpha'); var myObjAlias = objCacher('b', 'alpha');
notStrictEqual(myObj, undefined, 'object is created if second argument used as key'); notStrictEqual(myObj, void 0, 'object is created if second argument used as key');
strictEqual(myObj, myObjAlias, 'object is cached if second argument used as key'); strictEqual(myObj, myObjAlias, 'object is cached if second argument used as key');
strictEqual(myObj.value, 'a', 'object is not modified if second argument used as key'); strictEqual(myObj.value, 'a', 'object is not modified if second argument used as key');
}); });
@@ -346,7 +360,7 @@
throttledIncr(); throttledIncr();
equal(counter, 1); equal(counter, 1);
_.now = function () { _.now = function() {
return new Date(2013, 0, 1, 1, 1, 1); return new Date(2013, 0, 1, 1, 1, 1);
}; };
@@ -427,7 +441,7 @@
debouncedIncr(); debouncedIncr();
equal(counter, 1, 'incr was called immediately'); equal(counter, 1, 'incr was called immediately');
_.now = function () { _.now = function() {
return new Date(2013, 0, 1, 1, 1, 1); return new Date(2013, 0, 1, 1, 1, 1);
}; };
@@ -485,13 +499,13 @@
equal(backwards('moe'), 'hi: moe eom', 'wrapped the salutation function'); equal(backwards('moe'), 'hi: moe eom', 'wrapped the salutation function');
var inner = function(){ return 'Hello '; }; var inner = function(){ return 'Hello '; };
var obj = {name : 'Moe'}; var obj = {name: 'Moe'};
obj.hi = _.wrap(inner, function(fn){ return fn() + this.name; }); obj.hi = _.wrap(inner, function(fn){ return fn() + this.name; });
equal(obj.hi(), 'Hello Moe'); equal(obj.hi(), 'Hello Moe');
var noop = function(){}; var noop = function(){};
var wrapped = _.wrap(noop, function(){ return Array.prototype.slice.call(arguments, 0); }); var wrapped = _.wrap(noop, function(){ return Array.prototype.slice.call(arguments, 0); });
var ret = wrapped(['whats', 'your'], 'vector', 'victor'); var ret = wrapped(['whats', 'your'], 'vector', 'victor');
deepEqual(ret, [noop, ['whats', 'your'], 'vector', 'victor']); deepEqual(ret, [noop, ['whats', 'your'], 'vector', 'victor']);
}); });
@@ -578,4 +592,32 @@
}); });
test('restArgs', 10, function() {
_.restArgs(function(a, args) {
strictEqual(a, 1);
deepEqual(args, [2, 3], 'collects rest arguments into an array');
})(1, 2, 3);
_.restArgs(function(a, args) {
strictEqual(a, void 0);
deepEqual(args, [], 'passes empty array if there are not enough arguments');
})();
_.restArgs(function(a, b, c, args) {
strictEqual(arguments.length, 4);
deepEqual(args, [4, 5], 'works on functions with many named parameters');
})(1, 2, 3, 4, 5);
var obj = {};
_.restArgs(function() {
strictEqual(this, obj, 'invokes function with this context');
}).call(obj);
_.restArgs(function(array, iteratee, context) {
deepEqual(array, [1, 2, 3, 4], 'startIndex can be used manually specify index of rest parameter');
strictEqual(iteratee, void 0);
strictEqual(context, void 0);
}, 0)(1, 2, 3, 4);
});
}()); }());

View File

@@ -6,7 +6,7 @@
var testElement = typeof document === 'object' ? document.createElement('div') : void 0; var testElement = typeof document === 'object' ? document.createElement('div') : void 0;
test('keys', function() { test('keys', function() {
deepEqual(_.keys({one : 1, two : 2}), ['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 // the test above is not safe because it relies on for-in enumeration order
var a = []; a[1] = 0; var a = []; a[1] = 0;
deepEqual(_.keys(a), ['1'], 'is not fooled by sparse arrays; see issue #95'); deepEqual(_.keys(a), ['1'], 'is not fooled by sparse arrays; see issue #95');
@@ -18,17 +18,17 @@
// keys that may be missed if the implementation isn't careful // keys that may be missed if the implementation isn't careful
var trouble = { var trouble = {
'constructor': Object, constructor: Object,
'valueOf': _.noop, valueOf: _.noop,
'hasOwnProperty': null, hasOwnProperty: null,
'toString': 5, toString: 5,
'toLocaleString': undefined, toLocaleString: void 0,
'propertyIsEnumerable': /a/, propertyIsEnumerable: /a/,
'isPrototypeOf': this, isPrototypeOf: this,
'__defineGetter__': Boolean, __defineGetter__: Boolean,
'__defineSetter__': {}, __defineSetter__: {},
'__lookupSetter__': false, __lookupSetter__: false,
'__lookupGetter__': [] __lookupGetter__: []
}; };
var troubleKeys = ['constructor', 'valueOf', 'hasOwnProperty', 'toString', 'toLocaleString', 'propertyIsEnumerable', var troubleKeys = ['constructor', 'valueOf', 'hasOwnProperty', 'toString', 'toLocaleString', 'propertyIsEnumerable',
'isPrototypeOf', '__defineGetter__', '__defineSetter__', '__lookupSetter__', '__lookupGetter__'].sort(); 'isPrototypeOf', '__defineGetter__', '__defineSetter__', '__lookupSetter__', '__lookupGetter__'].sort();
@@ -36,7 +36,7 @@
}); });
test('allKeys', function() { test('allKeys', function() {
deepEqual(_.allKeys({one : 1, two : 2}), ['one', 'two'], 'can extract the allKeys from an object'); deepEqual(_.allKeys({one: 1, two: 2}), ['one', 'two'], 'can extract the allKeys from an object');
// the test above is not safe because it relies on for-in enumeration order // the test above is not safe because it relies on for-in enumeration order
var a = []; a[1] = 0; var a = []; a[1] = 0;
deepEqual(_.allKeys(a), ['1'], 'is not fooled by sparse arrays; see issue #95'); deepEqual(_.allKeys(a), ['1'], 'is not fooled by sparse arrays; see issue #95');
@@ -54,7 +54,7 @@
valueOf: _.noop, valueOf: _.noop,
hasOwnProperty: null, hasOwnProperty: null,
toString: 5, toString: 5,
toLocaleString: undefined, toLocaleString: void 0,
propertyIsEnumerable: /a/, propertyIsEnumerable: /a/,
isPrototypeOf: this isPrototypeOf: this
}; };
@@ -93,7 +93,7 @@
}); });
test('functions', function() { test('functions', function() {
var obj = {a : 'dash', b : _.map, c : /yo/, d : _.reduce}; var obj = {a: 'dash', b: _.map, c: /yo/, d: _.reduce};
deepEqual(['b', 'd'], _.functions(obj), 'can grab the function names of any passed-in object'); deepEqual(['b', 'd'], _.functions(obj), 'can grab the function names of any passed-in object');
var Animal = function(){}; var Animal = function(){};
@@ -127,13 +127,13 @@
try { try {
result = {}; result = {};
_.extend(result, null, undefined, {a: 1}); _.extend(result, null, void 0, {a: 1});
} catch(ex) {} } catch(e) { /* ignored */ }
equal(result.a, 1, 'should not error on `null` or `undefined` sources'); equal(result.a, 1, 'should not error on `null` or `undefined` sources');
strictEqual(_.extend(null, {a: 1}), null, 'extending null results in null'); strictEqual(_.extend(null, {a: 1}), null, 'extending null results in null');
strictEqual(_.extend(undefined, {a: 1}), undefined, 'extending undefined results in undefined'); strictEqual(_.extend(void 0, {a: 1}), void 0, 'extending undefined results in undefined');
}); });
test('extendOwn', function() { test('extendOwn', function() {
@@ -154,13 +154,13 @@
deepEqual(_.extendOwn({}, subObj), {c: 'd'}, 'assign copies own properties from source'); deepEqual(_.extendOwn({}, subObj), {c: 'd'}, 'assign copies own properties from source');
result = {}; result = {};
deepEqual(_.assign(result, null, undefined, {a: 1}), {a: 1}, 'should not error on `null` or `undefined` sources'); deepEqual(_.assign(result, null, void 0, {a: 1}), {a: 1}, 'should not error on `null` or `undefined` sources');
_.each(['a', 5, null, false], function(val) { _.each(['a', 5, null, false], function(val) {
strictEqual(_.assign(val, {a: 1}), val, 'assigning non-objects results in returning the non-object value'); strictEqual(_.assign(val, {a: 1}), val, 'assigning non-objects results in returning the non-object value');
}); });
strictEqual(_.extendOwn(undefined, {a: 1}), undefined, 'assigning undefined results in undefined'); strictEqual(_.extendOwn(void 0, {a: 1}), void 0, 'assigning undefined results in undefined');
result = _.extendOwn({a: 1, 0: 2, 1: '5', length: 6}, {0: 1, 1: 2, length: 2}); result = _.extendOwn({a: 1, 0: 2, 1: '5', length: 6}, {0: 1, 1: 2, length: 2});
deepEqual(result, {a: 1, 0: 1, 1: 2, length: 2}, 'assign should treat array-like objects like normal objects'); deepEqual(result, {a: 1, 0: 1, 1: 2, length: 2}, 'assign should treat array-like objects like normal objects');
@@ -219,7 +219,7 @@
deepEqual(result, {1: 'b'}, 'can omit numeric properties'); deepEqual(result, {1: 'b'}, 'can omit numeric properties');
deepEqual(_.omit(null, 'a', 'b'), {}, 'non objects return empty object'); deepEqual(_.omit(null, 'a', 'b'), {}, 'non objects return empty object');
deepEqual(_.omit(undefined, 'toString'), {}, 'null/undefined return empty object'); deepEqual(_.omit(void 0, 'toString'), {}, 'null/undefined return empty object');
deepEqual(_.omit(5, 'toString', 'b'), {}, 'returns empty object for primitives'); deepEqual(_.omit(5, 'toString', 'b'), {}, 'returns empty object for primitives');
var data = {a: 1, b: 2, c: 3}; var data = {a: 1, b: 2, c: 3};
@@ -257,17 +257,17 @@
try { try {
options = {}; options = {};
_.defaults(options, null, undefined, {a: 1}); _.defaults(options, null, void 0, {a: 1});
} catch(ex) {} } catch(e) { /* ignored */ }
equal(options.a, 1, 'should not error on `null` or `undefined` sources'); equal(options.a, 1, 'should not error on `null` or `undefined` sources');
strictEqual(_.defaults(null, {a: 1}), null, 'result is null if destination is null'); deepEqual(_.defaults(null, {a: 1}), {a: 1}, 'defaults skips nulls');
strictEqual(_.defaults(undefined, {a: 1}), undefined, 'result is undefined if destination is undefined'); deepEqual(_.defaults(void 0, {a: 1}), {a: 1}, 'defaults skips undefined');
}); });
test('clone', function() { test('clone', function() {
var moe = {name : 'moe', lucky : [13, 27, 34]}; var moe = {name: 'moe', lucky: [13, 27, 34]};
var clone = _.clone(moe); var clone = _.clone(moe);
equal(clone.name, 'moe', 'the clone as the attributes of the original'); equal(clone.name, 'moe', 'the clone as the attributes of the original');
@@ -277,7 +277,7 @@
clone.lucky.push(101); clone.lucky.push(101);
equal(_.last(moe.lucky), 101, 'changes to deep attributes are shared with the original'); equal(_.last(moe.lucky), 101, 'changes to deep attributes are shared with the original');
equal(_.clone(undefined), void 0, 'non objects should not be changed by clone'); equal(_.clone(void 0), void 0, 'non objects should not be changed by clone');
equal(_.clone(1), 1, 'non objects should not be changed by clone'); equal(_.clone(1), 1, 'non objects should not be changed by clone');
equal(_.clone(null), null, 'non objects should not be changed by clone'); equal(_.clone(null), null, 'non objects should not be changed by clone');
}); });
@@ -286,7 +286,7 @@
var Parent = function() {}; var Parent = function() {};
Parent.prototype = {foo: function() {}, bar: 2}; Parent.prototype = {foo: function() {}, bar: 2};
_.each(['foo', null, undefined, 1], function(val) { _.each(['foo', null, void 0, 1], function(val) {
deepEqual(_.create(val), {}, 'should return empty object when a non-object is provided'); deepEqual(_.create(val), {}, 'should return empty object when a non-object is provided');
}); });
@@ -324,8 +324,8 @@
ok(!_.isEqual(0, -0), '`0` is not equal to `-0`'); ok(!_.isEqual(0, -0), '`0` is not equal to `-0`');
ok(!_.isEqual(-0, 0), 'Commutative equality is implemented for `0` and `-0`'); ok(!_.isEqual(-0, 0), 'Commutative equality is implemented for `0` and `-0`');
ok(!_.isEqual(null, undefined), '`null` is not equal to `undefined`'); ok(!_.isEqual(null, void 0), '`null` is not equal to `undefined`');
ok(!_.isEqual(undefined, null), 'Commutative equality is implemented for `null` and `undefined`'); ok(!_.isEqual(void 0, null), 'Commutative equality is implemented for `null` and `undefined`');
// String object and primitive comparisons. // String object and primitive comparisons.
ok(_.isEqual('Curly', 'Curly'), 'Identical string primitives are equal'); ok(_.isEqual('Curly', 'Curly'), 'Identical string primitives are equal');
@@ -350,7 +350,7 @@
// Comparisons involving `NaN`. // Comparisons involving `NaN`.
ok(_.isEqual(NaN, NaN), '`NaN` is equal to `NaN`'); ok(_.isEqual(NaN, NaN), '`NaN` is equal to `NaN`');
ok(_.isEqual(new Object(NaN), NaN), 'Object(`NaN`) is equal to `NaN`'); ok(_.isEqual(new Number(NaN), NaN), 'Object(`NaN`) is equal to `NaN`');
ok(!_.isEqual(61, NaN), 'A number primitive is not equal to `NaN`'); ok(!_.isEqual(61, NaN), 'A number primitive is not equal to `NaN`');
ok(!_.isEqual(new Number(79), NaN), 'A number object is not equal to `NaN`'); ok(!_.isEqual(new Number(79), NaN), 'A number object is not equal to `NaN`');
ok(!_.isEqual(Infinity, NaN), '`Infinity` is not equal to `NaN`'); ok(!_.isEqual(Infinity, NaN), '`Infinity` is not equal to `NaN`');
@@ -431,7 +431,7 @@
var sparse = []; var sparse = [];
sparse[1] = 5; sparse[1] = 5;
ok(_.isEqual(sparse, [undefined, 5]), 'Handles sparse arrays as dense'); ok(_.isEqual(sparse, [void 0, 5]), 'Handles sparse arrays as dense');
// Simple objects. // Simple objects.
ok(_.isEqual({a: 'Curly', b: 1, c: true}, {a: 'Curly', b: 1, c: true}), 'Objects containing identical primitives are equal'); ok(_.isEqual({a: 'Curly', b: 1, c: true}, {a: 'Curly', b: 1, c: true}), 'Objects containing identical primitives are equal');
@@ -440,7 +440,7 @@
ok(!_.isEqual({a: 63, b: 75}, {a: 61, c: 55}), 'Objects of identical sizes with different property names are not equal'); ok(!_.isEqual({a: 63, b: 75}, {a: 61, c: 55}), 'Objects of identical sizes with different property names are not equal');
ok(!_.isEqual({a: 1, b: 2}, {a: 1}), 'Objects of different sizes are not equal'); ok(!_.isEqual({a: 1, b: 2}, {a: 1}), 'Objects of different sizes are not equal');
ok(!_.isEqual({a: 1}, {a: 1, b: 2}), 'Commutative equality is implemented for objects'); ok(!_.isEqual({a: 1}, {a: 1, b: 2}), 'Commutative equality is implemented for objects');
ok(!_.isEqual({x: 1, y: undefined}, {x: 1, z: 2}), 'Objects with identical keys and different values are not equivalent'); ok(!_.isEqual({x: 1, y: void 0}, {x: 1, z: 2}), 'Objects with identical keys and different values are not equivalent');
// `A` contains nested objects and arrays. // `A` contains nested objects and arrays.
a = { a = {
@@ -536,7 +536,7 @@
ok(_.isEqual(a, b), 'Cyclic structures with nested and identically-named properties are equal'); ok(_.isEqual(a, b), 'Cyclic structures with nested and identically-named properties are equal');
// Chaining. // Chaining.
ok(!_.isEqual(_({x: 1, y: undefined}).chain(), _({x: 1, z: 2}).chain()), 'Chained objects containing different values are not equal'); ok(!_.isEqual(_({x: 1, y: void 0}).chain(), _({x: 1, z: 2}).chain()), 'Chained objects containing different values are not equal');
a = _({x: 1, y: 2}).chain(); a = _({x: 1, y: 2}).chain();
b = _({x: 1, y: 2}).chain(); b = _({x: 1, y: 2}).chain();
@@ -544,9 +544,9 @@
// Objects without a `constructor` property // Objects without a `constructor` property
if (Object.create) { if (Object.create) {
a = Object.create(null, {x: {value: 1, enumerable: true}}); a = Object.create(null, {x: {value: 1, enumerable: true}});
b = {x: 1}; b = {x: 1};
ok(_.isEqual(a, b), 'Handles objects without a constructor (e.g. from Object.create'); ok(_.isEqual(a, b), 'Handles objects without a constructor (e.g. from Object.create');
} }
function Foo() { this.a = 1; } function Foo() { this.a = 1; }
@@ -554,12 +554,19 @@
var other = {a: 1}; var other = {a: 1};
strictEqual(_.isEqual(new Foo, other), false, 'Objects from different constructors are not equal'); strictEqual(_.isEqual(new Foo, other), false, 'Objects from different constructors are not equal');
// Tricky object cases val comparisions
equal(_.isEqual([0], [-0]), false);
equal(_.isEqual({a: 0}, {a: -0}), false);
equal(_.isEqual([NaN], [NaN]), true);
equal(_.isEqual({a: NaN}, {a: NaN}), true);
}); });
test('isEmpty', function() { test('isEmpty', function() {
ok(!_([1]).isEmpty(), '[1] is not empty'); ok(!_([1]).isEmpty(), '[1] is not empty');
ok(_.isEmpty([]), '[] is empty'); ok(_.isEmpty([]), '[] is empty');
ok(!_.isEmpty({one : 1}), '{one : 1} is not empty'); ok(!_.isEmpty({one: 1}), '{one: 1} is not empty');
ok(_.isEmpty({}), '{} is empty'); ok(_.isEmpty({}), '{} is empty');
ok(_.isEmpty(new RegExp('')), 'objects with prototype properties are empty'); ok(_.isEmpty(new RegExp('')), 'objects with prototype properties are empty');
ok(_.isEmpty(null), 'null is empty'); ok(_.isEmpty(null), 'null is empty');
@@ -567,7 +574,7 @@
ok(_.isEmpty(''), 'the empty string is empty'); ok(_.isEmpty(''), 'the empty string is empty');
ok(!_.isEmpty('moe'), 'but other strings are not'); ok(!_.isEmpty('moe'), 'but other strings are not');
var obj = {one : 1}; var obj = {one: 1};
delete obj.one; delete obj.one;
ok(_.isEmpty(obj), 'deleting all the keys from an object empties it'); ok(_.isEmpty(obj), 'deleting all the keys from an object empties it');
@@ -576,7 +583,7 @@
ok(!_.isEmpty(args('')), 'non-empty arguments object is not empty'); ok(!_.isEmpty(args('')), 'non-empty arguments object is not empty');
// covers collecting non-enumerable properties in IE < 9 // covers collecting non-enumerable properties in IE < 9
var nonEnumProp = {'toString': 5}; var nonEnumProp = {toString: 5};
ok(!_.isEmpty(nonEnumProp), 'non-enumerable property is not empty'); ok(!_.isEmpty(nonEnumProp), 'non-enumerable property is not empty');
}); });
@@ -602,9 +609,9 @@
if (testElement) { if (testElement) {
ok(_.isObject(testElement), 'and DOM element'); ok(_.isObject(testElement), 'and DOM element');
} }
ok(_.isObject(function () {}), 'and functions'); ok(_.isObject(function() {}), 'and functions');
ok(!_.isObject(null), 'but not null'); ok(!_.isObject(null), 'but not null');
ok(!_.isObject(undefined), 'and not undefined'); ok(!_.isObject(void 0), 'and not undefined');
ok(!_.isObject('string'), 'and not string'); ok(!_.isObject('string'), 'and not string');
ok(!_.isObject(12), 'and not number'); ok(!_.isObject(12), 'and not number');
ok(!_.isObject(true), 'and not boolean'); ok(!_.isObject(true), 'and not boolean');
@@ -612,7 +619,7 @@
}); });
test('isArray', function() { test('isArray', function() {
ok(!_.isArray(undefined), 'undefined vars are not arrays'); ok(!_.isArray(void 0), 'undefined vars are not arrays');
ok(!_.isArray(arguments), 'the arguments object is not an array'); ok(!_.isArray(arguments), 'the arguments object is not an array');
ok(_.isArray([1, 2, 3]), 'but arrays are'); ok(_.isArray([1, 2, 3]), 'but arrays are');
}); });
@@ -631,7 +638,7 @@
test('isNumber', function() { test('isNumber', function() {
ok(!_.isNumber('string'), 'a string is not a number'); ok(!_.isNumber('string'), 'a string is not a number');
ok(!_.isNumber(arguments), 'the arguments object is not a number'); ok(!_.isNumber(arguments), 'the arguments object is not a number');
ok(!_.isNumber(undefined), 'undefined is not a number'); ok(!_.isNumber(void 0), 'undefined is not a number');
ok(_.isNumber(3 * 4 - 7 / 10), 'but numbers are'); ok(_.isNumber(3 * 4 - 7 / 10), 'but numbers are');
ok(_.isNumber(NaN), 'NaN *is* a number'); ok(_.isNumber(NaN), 'NaN *is* a number');
ok(_.isNumber(Infinity), 'Infinity is a number'); ok(_.isNumber(Infinity), 'Infinity is a number');
@@ -644,7 +651,7 @@
ok(!_.isBoolean('false'), 'the string "false" is not a boolean'); ok(!_.isBoolean('false'), 'the string "false" is not a boolean');
ok(!_.isBoolean('true'), 'the string "true" is not a boolean'); ok(!_.isBoolean('true'), 'the string "true" is not a boolean');
ok(!_.isBoolean(arguments), 'the arguments object is not a boolean'); ok(!_.isBoolean(arguments), 'the arguments object is not a boolean');
ok(!_.isBoolean(undefined), 'undefined is not a boolean'); ok(!_.isBoolean(void 0), 'undefined is not a boolean');
ok(!_.isBoolean(NaN), 'NaN is not a boolean'); ok(!_.isBoolean(NaN), 'NaN is not a boolean');
ok(!_.isBoolean(null), 'null is not a boolean'); ok(!_.isBoolean(null), 'null is not a boolean');
ok(_.isBoolean(true), 'but true is'); ok(_.isBoolean(true), 'but true is');
@@ -652,7 +659,7 @@
}); });
test('isFunction', function() { test('isFunction', function() {
ok(!_.isFunction(undefined), 'undefined vars are not functions'); ok(!_.isFunction(void 0), 'undefined vars are not functions');
ok(!_.isFunction([1, 2, 3]), 'arrays are not functions'); ok(!_.isFunction([1, 2, 3]), 'arrays are not functions');
ok(!_.isFunction('moe'), 'strings are not functions'); ok(!_.isFunction('moe'), 'strings are not functions');
ok(_.isFunction(_.isFunction), 'but functions are'); ok(_.isFunction(_.isFunction), 'but functions are');
@@ -661,6 +668,11 @@
if (testElement) { if (testElement) {
ok(!_.isFunction(testElement), 'elements are not functions'); ok(!_.isFunction(testElement), 'elements are not functions');
} }
var nodelist = typeof document != 'undefined' && document.childNodes;
if (nodelist) {
ok(!_.isFunction(nodelist));
}
}); });
if (typeof Int8Array !== 'undefined') { if (typeof Int8Array !== 'undefined') {
@@ -669,9 +681,9 @@
.map(_.propertyOf(typeof GLOBAL != 'undefined' ? GLOBAL : window)) .map(_.propertyOf(typeof GLOBAL != 'undefined' ? GLOBAL : window))
.compact() .compact()
.each(function(TypedArray) { .each(function(TypedArray) {
// PhantomJS reports `typeof UInt8Array == 'object'` and doesn't report toString TypeArray // PhantomJS reports `typeof UInt8Array == 'object'` and doesn't report toString TypeArray
// as a function // as a function
strictEqual(_.isFunction(TypedArray), Object.prototype.toString.call(TypedArray) === '[object Function]'); strictEqual(_.isFunction(TypedArray), Object.prototype.toString.call(TypedArray) === '[object Function]');
}); });
}); });
} }
@@ -688,7 +700,7 @@
}); });
test('isFinite', function() { test('isFinite', function() {
ok(!_.isFinite(undefined), 'undefined is not finite'); ok(!_.isFinite(void 0), 'undefined is not finite');
ok(!_.isFinite(null), 'null is not finite'); ok(!_.isFinite(null), 'null is not finite');
ok(!_.isFinite(NaN), 'NaN is not finite'); ok(!_.isFinite(NaN), 'NaN is not finite');
ok(!_.isFinite(Infinity), 'Infinity is not finite'); ok(!_.isFinite(Infinity), 'Infinity is not finite');
@@ -704,15 +716,16 @@
}); });
test('isNaN', function() { test('isNaN', function() {
ok(!_.isNaN(undefined), 'undefined is not NaN'); ok(!_.isNaN(void 0), 'undefined is not NaN');
ok(!_.isNaN(null), 'null is not NaN'); ok(!_.isNaN(null), 'null is not NaN');
ok(!_.isNaN(0), '0 is not NaN'); ok(!_.isNaN(0), '0 is not NaN');
ok(!_.isNaN(new Number(0)), 'wrapped 0 is not NaN');
ok(_.isNaN(NaN), 'but NaN is'); ok(_.isNaN(NaN), 'but NaN is');
ok(_.isNaN(new Number(NaN)), 'wrapped NaN is still NaN'); ok(_.isNaN(new Number(NaN)), 'wrapped NaN is still NaN');
}); });
test('isNull', function() { test('isNull', function() {
ok(!_.isNull(undefined), 'undefined is not null'); ok(!_.isNull(void 0), 'undefined is not null');
ok(!_.isNull(NaN), 'NaN is not null'); ok(!_.isNull(NaN), 'NaN is not null');
ok(_.isNull(null), 'but null is'); ok(_.isNull(null), 'but null is');
}); });
@@ -723,7 +736,7 @@
ok(!_.isUndefined(false), 'false is defined'); ok(!_.isUndefined(false), 'false is defined');
ok(!_.isUndefined(NaN), 'NaN is defined'); ok(!_.isUndefined(NaN), 'NaN is defined');
ok(_.isUndefined(), 'nothing is undefined'); ok(_.isUndefined(), 'nothing is undefined');
ok(_.isUndefined(undefined), 'undefined is undefined'); ok(_.isUndefined(void 0), 'undefined is undefined');
}); });
test('isError', function() { test('isError', function() {
@@ -755,7 +768,7 @@
equal(intercepted, returned, 'can use tapped objects in a chain'); equal(intercepted, returned, 'can use tapped objects in a chain');
}); });
test('has', function () { test('has', function() {
var obj = {foo: 'bar', func: function(){}}; var obj = {foo: 'bar', func: function(){}};
ok(_.has(obj, 'foo'), 'has() checks that the object has a property.'); ok(_.has(obj, 'foo'), 'has() checks that the object has a property.');
ok(!_.has(obj, 'baz'), "has() returns false if the object doesn't have the property."); ok(!_.has(obj, 'baz'), "has() returns false if the object doesn't have the property.");
@@ -766,7 +779,7 @@
child.prototype = obj; child.prototype = obj;
ok(!_.has(child, 'foo'), 'has() does not check the prototype chain for a property.'); ok(!_.has(child, 'foo'), 'has() does not check the prototype chain for a property.');
strictEqual(_.has(null, 'foo'), false, 'has() returns false for null'); strictEqual(_.has(null, 'foo'), false, 'has() returns false for null');
strictEqual(_.has(undefined, 'foo'), false, 'has() returns false for undefined'); strictEqual(_.has(void 0, 'foo'), false, 'has() returns false for undefined');
}); });
test('isMatch', function() { test('isMatch', function() {
@@ -776,17 +789,17 @@
equal(_.isMatch(moe, {hair: true}), true, 'Returns a boolean'); equal(_.isMatch(moe, {hair: true}), true, 'Returns a boolean');
equal(_.isMatch(curly, {hair: true}), false, 'Returns a boolean'); equal(_.isMatch(curly, {hair: true}), false, 'Returns a boolean');
equal(_.isMatch(5, {__x__: undefined}), false, 'can match undefined props on primitives'); equal(_.isMatch(5, {__x__: void 0}), false, 'can match undefined props on primitives');
equal(_.isMatch({__x__: undefined}, {__x__: undefined}), true, 'can match undefined props'); equal(_.isMatch({__x__: void 0}, {__x__: void 0}), true, 'can match undefined props');
equal(_.isMatch(null, {}), true, 'Empty spec called with null object returns true'); equal(_.isMatch(null, {}), true, 'Empty spec called with null object returns true');
equal(_.isMatch(null, {a: 1}), false, 'Non-empty spec called with null object returns false'); equal(_.isMatch(null, {a: 1}), false, 'Non-empty spec called with null object returns false');
_.each([null, undefined], function(item) { strictEqual(_.isMatch(item, null), true, 'null matches null'); }); _.each([null, void 0], function(item) { strictEqual(_.isMatch(item, null), true, 'null matches null'); });
_.each([null, undefined], function(item) { strictEqual(_.isMatch(item, null), true, 'null matches {}'); }); _.each([null, void 0], function(item) { strictEqual(_.isMatch(item, null), true, 'null matches {}'); });
strictEqual(_.isMatch({b: 1}, {a: undefined}), false, 'handles undefined values (1683)'); strictEqual(_.isMatch({b: 1}, {a: void 0}), false, 'handles undefined values (1683)');
_.each([true, 5, NaN, null, undefined], function(item) { _.each([true, 5, NaN, null, void 0], function(item) {
strictEqual(_.isMatch({a: 1}, item), true, 'treats primitives as empty'); strictEqual(_.isMatch({a: 1}, item), true, 'treats primitives as empty');
}); });
@@ -805,8 +818,8 @@
ok(_.isMatch({x: 5, y: 1}, Prototest), 'spec can be a function'); ok(_.isMatch({x: 5, y: 1}, Prototest), 'spec can be a function');
//null edge cases //null edge cases
var oCon = {'constructor': Object}; var oCon = {constructor: Object};
deepEqual(_.map([null, undefined, 5, {}], _.partial(_.isMatch, _, oCon)), [false, false, false, true], 'doesnt falsey match constructor on undefined/null'); deepEqual(_.map([null, void 0, 5, {}], _.partial(_.isMatch, _, oCon)), [false, false, false, true], 'doesnt falsey match constructor on undefined/null');
}); });
test('matcher', function() { test('matcher', function() {
@@ -817,21 +830,21 @@
equal(_.matcher({hair: true})(moe), true, 'Returns a boolean'); equal(_.matcher({hair: true})(moe), true, 'Returns a boolean');
equal(_.matcher({hair: true})(curly), false, 'Returns a boolean'); equal(_.matcher({hair: true})(curly), false, 'Returns a boolean');
equal(_.matcher({__x__: undefined})(5), false, 'can match undefined props on primitives'); equal(_.matcher({__x__: void 0})(5), false, 'can match undefined props on primitives');
equal(_.matcher({__x__: undefined})({__x__: undefined}), true, 'can match undefined props'); equal(_.matcher({__x__: void 0})({__x__: void 0}), true, 'can match undefined props');
equal(_.matcher({})(null), true, 'Empty spec called with null object returns true'); equal(_.matcher({})(null), true, 'Empty spec called with null object returns true');
equal(_.matcher({a: 1})(null), false, 'Non-empty spec called with null object returns false'); equal(_.matcher({a: 1})(null), false, 'Non-empty spec called with null object returns false');
ok(_.find(stooges, _.matcher({hair: false})) === curly, 'returns a predicate that can be used by finding functions.'); ok(_.find(stooges, _.matcher({hair: false})) === curly, 'returns a predicate that can be used by finding functions.');
ok(_.find(stooges, _.matcher(moe)) === moe, 'can be used to locate an object exists in a collection.'); ok(_.find(stooges, _.matcher(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, void 0], {a: 1}), [], 'Do not throw on null values.');
deepEqual(_.where([null, undefined], null), [null, undefined], 'null matches null'); deepEqual(_.where([null, void 0], null), [null, void 0], 'null matches null');
deepEqual(_.where([null, undefined], {}), [null, undefined], 'null matches {}'); deepEqual(_.where([null, void 0], {}), [null, void 0], 'null matches {}');
deepEqual(_.where([{b: 1}], {a: undefined}), [], 'handles undefined values (1683)'); deepEqual(_.where([{b: 1}], {a: void 0}), [], 'handles undefined values (1683)');
_.each([true, 5, NaN, null, undefined], function(item) { _.each([true, 5, NaN, null, void 0], function(item) {
deepEqual(_.where([{a: 1}], item), [{a: 1}], 'treats primitives as empty'); deepEqual(_.where([{a: 1}], item), [{a: 1}], 'treats primitives as empty');
}); });
@@ -852,82 +865,25 @@
ok(_.matcher(Prototest)({x: 5, y: 1}), 'spec can be a function'); ok(_.matcher(Prototest)({x: 5, y: 1}), 'spec can be a function');
// #1729 // #1729
var o = {'b': 1}; var o = {b: 1};
var m = _.matcher(o); var m = _.matcher(o);
equal(m({'b': 1}), true); equal(m({b: 1}), true);
o.b = 2; o.b = 2;
o.a = 1; o.a = 1;
equal(m({'b': 1}), true, 'changing spec object doesnt change matches result'); equal(m({b: 1}), true, 'changing spec object doesnt change matches result');
//null edge cases //null edge cases
var oCon = _.matcher({'constructor': Object}); var oCon = _.matcher({constructor: Object});
deepEqual(_.map([null, undefined, 5, {}], oCon), [false, false, false, true], 'doesnt falsey match constructor on undefined/null'); deepEqual(_.map([null, void 0, 5, {}], oCon), [false, false, false, true], 'doesnt falsey match constructor on undefined/null');
});
test('matcher', function() {
var moe = {name: 'Moe Howard', hair: true};
var curly = {name: 'Curly Howard', hair: false};
var stooges = [moe, curly];
equal(_.matcher({hair: true})(moe), true, 'Returns a boolean');
equal(_.matcher({hair: true})(curly), false, 'Returns a boolean');
equal(_.matcher({__x__: undefined})(5), false, 'can match undefined props on primitives');
equal(_.matcher({__x__: undefined})({__x__: undefined}), true, 'can match undefined props');
equal(_.matcher({})(null), true, 'Empty spec called with null object returns true');
equal(_.matcher({a: 1})(null), false, 'Non-empty spec called with null object returns false');
ok(_.find(stooges, _.matcher({hair: false})) === curly, 'returns a predicate that can be used by finding functions.');
ok(_.find(stooges, _.matcher(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 {}');
deepEqual(_.where([{b: 1}], {a: undefined}), [], 'handles undefined values (1683)');
_.each([true, 5, NaN, null, undefined], function(item) {
deepEqual(_.where([{a: 1}], item), [{a: 1}], 'treats primitives as empty');
});
function Prototest() {}
Prototest.prototype.x = 1;
var specObj = new Prototest;
var protospec = _.matcher(specObj);
equal(protospec({x: 2}), true, 'spec is restricted to own properties');
specObj.y = 5;
protospec = _.matcher(specObj);
equal(protospec({x: 1, y: 5}), true);
equal(protospec({x: 1, y: 4}), false);
ok(_.matcher({x: 1, y: 5})(specObj), 'inherited and own properties are checked on the test object');
Prototest.x = 5;
ok(_.matcher(Prototest)({x: 5, y: 1}), 'spec can be a function');
// #1729
var o = {'b': 1};
var m = _.matcher(o);
equal(m({'b': 1}), true);
o.b = 2;
o.a = 1;
equal(m({'b': 1}), true, 'changing spec object doesnt change matches result');
//null edge cases
var oCon = _.matcher({'constructor': Object});
deepEqual(_.map([null, undefined, 5, {}], oCon), [false, false, false, true], 'doesnt falsey match constructor on undefined/null');
}); });
test('findKey', function() { test('findKey', function() {
var objects = { var objects = {
a: {'a': 0, 'b': 0}, a: {a: 0, b: 0},
b: {'a': 1, 'b': 1}, b: {a: 1, b: 1},
c: {'a': 2, 'b': 2} c: {a: 2, b: 2}
}; };
equal(_.findKey(objects, function(obj) { equal(_.findKey(objects, function(obj) {
@@ -942,7 +898,7 @@
equal(_.findKey(objects, function(obj) { equal(_.findKey(objects, function(obj) {
return obj.b * obj.a === 5; return obj.b * obj.a === 5;
}), undefined); }), void 0);
strictEqual(_.findKey([1, 2, 3, 4, 5, 6], function(obj) { strictEqual(_.findKey([1, 2, 3, 4, 5, 6], function(obj) {
return obj === 3; return obj === 3;
@@ -950,7 +906,7 @@
strictEqual(_.findKey(objects, function(a) { strictEqual(_.findKey(objects, function(a) {
return a.foo === null; return a.foo === null;
}), undefined); }), void 0);
_.findKey({a: {a: 1}}, function(a, key, obj) { _.findKey({a: {a: 1}}, function(a, key, obj) {
equal(key, 'a'); equal(key, 'a');
@@ -965,53 +921,53 @@
test('mapObject', function() { test('mapObject', function() {
var obj = {'a': 1, 'b': 2}; var obj = {a: 1, b: 2};
var objects = { var objects = {
a: {'a': 0, 'b': 0}, a: {a: 0, b: 0},
b: {'a': 1, 'b': 1}, b: {a: 1, b: 1},
c: {'a': 2, 'b': 2} c: {a: 2, b: 2}
}; };
deepEqual(_.mapObject(obj, function(val) { deepEqual(_.mapObject(obj, function(val) {
return val * 2; return val * 2;
}), {'a': 2, 'b': 4}, 'simple objects'); }), {a: 2, b: 4}, 'simple objects');
deepEqual(_.mapObject(objects, function(val) { deepEqual(_.mapObject(objects, function(val) {
return _.reduce(val, function(memo,v){ return _.reduce(val, function(memo, v){
return memo + v; return memo + v;
},0); }, 0);
}), {'a': 0, 'b': 2, 'c': 4}, 'nested objects'); }), {a: 0, b: 2, c: 4}, 'nested objects');
deepEqual(_.mapObject(obj, function(val,key,obj) { deepEqual(_.mapObject(obj, function(val, key, o) {
return obj[key] * 2; return o[key] * 2;
}), {'a': 2, 'b': 4}, 'correct keys'); }), {a: 2, b: 4}, 'correct keys');
deepEqual(_.mapObject([1,2], function(val) { deepEqual(_.mapObject([1, 2], function(val) {
return val * 2; return val * 2;
}), {'0': 2, '1': 4}, 'check behavior for arrays'); }), {0: 2, 1: 4}, 'check behavior for arrays');
deepEqual(_.mapObject(obj, function(val) { deepEqual(_.mapObject(obj, function(val) {
return val * this.multiplier; return val * this.multiplier;
}, {multiplier : 3}), {'a': 3, 'b': 6}, 'keep context'); }, {multiplier: 3}), {a: 3, b: 6}, 'keep context');
deepEqual(_.mapObject({a: 1}, function() { deepEqual(_.mapObject({a: 1}, function() {
return this.length; return this.length;
}, [1,2]), {'a': 2}, 'called with context'); }, [1, 2]), {a: 2}, 'called with context');
var ids = _.mapObject({length: 2, 0: {id: '1'}, 1: {id: '2'}}, function(n){ var ids = _.mapObject({length: 2, 0: {id: '1'}, 1: {id: '2'}}, function(n){
return n.id; return n.id;
}); });
deepEqual(ids, {'length': undefined, '0': '1', '1': '2'}, 'Check with array-like objects'); deepEqual(ids, {length: void 0, 0: '1', 1: '2'}, 'Check with array-like objects');
// Passing a property name like _.pluck. // Passing a property name like _.pluck.
var people = {'a': {name : 'moe', age : 30}, 'b': {name : 'curly', age : 50}}; var people = {a: {name: 'moe', age: 30}, b: {name: 'curly', age: 50}};
deepEqual(_.mapObject(people, 'name'), {'a': 'moe', 'b': 'curly'}, 'predicate string map to object properties'); deepEqual(_.mapObject(people, 'name'), {a: 'moe', b: 'curly'}, 'predicate string map to object properties');
_.each([null, void 0, 1, 'abc', [], {}, undefined], function(val){ _.each([null, void 0, 1, 'abc', [], {}, void 0], function(val){
deepEqual(_.mapObject(val, _.identity), {}, 'mapValue identity'); deepEqual(_.mapObject(val, _.identity), {}, 'mapValue identity');
}); });
var Proto = function(){this.a = 1;}; var Proto = function(){ this.a = 1; };
Proto.prototype.b = 1; Proto.prototype.b = 1;
var protoObj = new Proto(); var protoObj = new Proto();
deepEqual(_.mapObject(protoObj, _.identity), {a: 1}, 'ignore inherited values from prototypes'); deepEqual(_.mapObject(protoObj, _.identity), {a: 1}, 'ignore inherited values from prototypes');

View File

@@ -14,6 +14,37 @@
}); });
if (typeof this == 'object') {
test('noConflict', function() {
var underscore = _.noConflict();
equal(underscore.identity(1), 1);
if (typeof require != 'function') {
equal(this._, void 0, 'global underscore is removed');
this._ = underscore;
}
});
}
if (typeof require == 'function') {
asyncTest('noConflict (node vm)', 2, function() {
var fs = require('fs');
var vm = require('vm');
var filename = __dirname + '/../underscore.js';
fs.readFile(filename, function(err, content){
var sandbox = vm.createScript(
content + 'this.underscore = this._.noConflict();',
filename
);
var context = {_: 'oldvalue'};
sandbox.runInNewContext(context);
equal(context._, 'oldvalue');
equal(context.underscore.VERSION, _.VERSION);
start();
});
});
}
test('#750 - Return _ instance.', 2, function() { test('#750 - Return _ instance.', 2, function() {
var instance = _([]); var instance = _([]);
ok(_(instance) === instance); ok(_(instance) === instance);
@@ -21,31 +52,31 @@
}); });
test('identity', function() { test('identity', function() {
var stooge = {name : 'moe'}; var stooge = {name: 'moe'};
equal(_.identity(stooge), stooge, 'stooge is the same as his identity'); equal(_.identity(stooge), stooge, 'stooge is the same as his identity');
}); });
test('constant', function() { test('constant', function() {
var stooge = {name : 'moe'}; var stooge = {name: 'moe'};
equal(_.constant(stooge)(), stooge, 'should create a function that returns stooge'); equal(_.constant(stooge)(), stooge, 'should create a function that returns stooge');
}); });
test('noop', function() { test('noop', function() {
strictEqual(_.noop('curly', 'larry', 'moe'), undefined, 'should always return undefined'); strictEqual(_.noop('curly', 'larry', 'moe'), void 0, 'should always return undefined');
}); });
test('property', function() { test('property', function() {
var stooge = {name : 'moe'}; var stooge = {name: 'moe'};
equal(_.property('name')(stooge), 'moe', 'should return the property with the given name'); equal(_.property('name')(stooge), 'moe', 'should return the property with the given name');
equal(_.property('name')(null), undefined, 'should return undefined for null values'); equal(_.property('name')(null), void 0, 'should return undefined for null values');
equal(_.property('name')(undefined), undefined, 'should return undefined for undefined values'); equal(_.property('name')(void 0), void 0, 'should return undefined for undefined values');
}); });
test('propertyOf', function() { test('propertyOf', function() {
var stoogeRanks = _.propertyOf({curly: 2, moe: 1, larry: 3}); var stoogeRanks = _.propertyOf({curly: 2, moe: 1, larry: 3});
equal(stoogeRanks('curly'), 2, 'should return the property with the given name'); equal(stoogeRanks('curly'), 2, 'should return the property with the given name');
equal(stoogeRanks(null), undefined, 'should return undefined for null values'); equal(stoogeRanks(null), void 0, 'should return undefined for null values');
equal(stoogeRanks(undefined), undefined, 'should return undefined for undefined values'); equal(stoogeRanks(void 0), void 0, 'should return undefined for undefined values');
function MoreStooges() { this.shemp = 87; } function MoreStooges() { this.shemp = 87; }
MoreStooges.prototype = {curly: 2, moe: 1, larry: 3}; MoreStooges.prototype = {curly: 2, moe: 1, larry: 3};
@@ -53,10 +84,10 @@
equal(moreStoogeRanks('curly'), 2, 'should return properties from further up the prototype chain'); equal(moreStoogeRanks('curly'), 2, 'should return properties from further up the prototype chain');
var nullPropertyOf = _.propertyOf(null); var nullPropertyOf = _.propertyOf(null);
equal(nullPropertyOf('curly'), undefined, 'should return undefined when obj is null'); equal(nullPropertyOf('curly'), void 0, 'should return undefined when obj is null');
var undefPropertyOf = _.propertyOf(undefined); var undefPropertyOf = _.propertyOf(void 0);
equal(undefPropertyOf('curly'), undefined, 'should return undefined when obj is undefined'); equal(undefPropertyOf('curly'), void 0, 'should return undefined when obj is undefined');
}); });
test('random', function() { test('random', function() {
@@ -86,7 +117,7 @@
test('times', function() { test('times', function() {
var vals = []; var vals = [];
_.times(3, function (i) { vals.push(i); }); _.times(3, function(i) { vals.push(i); });
deepEqual(vals, [0, 1, 2], 'is 0 indexed'); deepEqual(vals, [0, 1, 2], 'is 0 indexed');
// //
vals = []; vals = [];
@@ -127,16 +158,16 @@
var escapeCharacters = ['<', '>', '"', '\'', '`']; var escapeCharacters = ['<', '>', '"', '\'', '`'];
_.each(escapeCharacters, function(escapeChar) { _.each(escapeCharacters, function(escapeChar) {
var str = 'a ' + escapeChar + ' string escaped'; var s = 'a ' + escapeChar + ' string escaped';
var escaped = _.escape(str); var e = _.escape(s);
notEqual(str, escaped, escapeChar + ' is escaped'); notEqual(s, e, escapeChar + ' is escaped');
equal(str, _.unescape(escaped), escapeChar + ' can be unescaped'); equal(s, _.unescape(e), escapeChar + ' can be unescaped');
str = 'a ' + escapeChar + escapeChar + escapeChar + 'some more string' + escapeChar; s = 'a ' + escapeChar + escapeChar + escapeChar + 'some more string' + escapeChar;
escaped = _.escape(str); e = _.escape(s);
equal(escaped.indexOf(escapeChar), -1, 'can escape multiple occurances of ' + escapeChar); equal(e.indexOf(escapeChar), -1, 'can escape multiple occurances of ' + escapeChar);
equal(_.unescape(escaped), str, 'multiple occurrences of ' + escapeChar + ' can be unescaped'); equal(_.unescape(e), s, 'multiple occurrences of ' + escapeChar + ' can be unescaped');
}); });
// handles multiple escape characters at once // handles multiple escape characters at once
@@ -158,7 +189,7 @@
test('template', function() { test('template', function() {
var basicTemplate = _.template("<%= thing %> is gettin' on my noives!"); var basicTemplate = _.template("<%= thing %> is gettin' on my noives!");
var result = basicTemplate({thing : 'This'}); var result = basicTemplate({thing: 'This'});
equal(result, "This is gettin' on my noives!", 'can do basic attribute interpolation'); equal(result, "This is gettin' on my noives!", 'can do basic attribute interpolation');
var sansSemicolonTemplate = _.template('A <% this %> B'); var sansSemicolonTemplate = _.template('A <% this %> B');
@@ -173,7 +204,7 @@
var fancyTemplate = _.template('<ul><% ' + var fancyTemplate = _.template('<ul><% ' +
' for (var key in people) { ' + ' for (var key in people) { ' +
'%><li><%= people[key] %></li><% } %></ul>'); '%><li><%= people[key] %></li><% } %></ul>');
result = fancyTemplate({people : {moe : 'Moe', larry : 'Larry', curly : 'Curly'}}); result = fancyTemplate({people: {moe: 'Moe', larry: 'Larry', curly: 'Curly'}});
equal(result, '<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>', 'can run arbitrary javascript in templates'); equal(result, '<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>', 'can run arbitrary javascript in templates');
var escapedCharsInJavascriptTemplate = _.template('<ul><% _.each(numbers.split("\\n"), function(item) { %><li><%= item %></li><% }) %></ul>'); var escapedCharsInJavascriptTemplate = _.template('<ul><% _.each(numbers.split("\\n"), function(item) { %><li><%= item %></li><% }) %></ul>');
@@ -222,15 +253,15 @@
' if (data) { data += 12345; }; %>\n ' + ' if (data) { data += 12345; }; %>\n ' +
' <li><%= data %></li>\n ' ' <li><%= data %></li>\n '
); );
equal(template({data : 12345}).replace(/\s/g, ''), '<li>24690</li>'); equal(template({data: 12345}).replace(/\s/g, ''), '<li>24690</li>');
_.templateSettings = { _.templateSettings = {
evaluate : /\{\{([\s\S]+?)\}\}/g, evaluate: /\{\{([\s\S]+?)\}\}/g,
interpolate : /\{\{=([\s\S]+?)\}\}/g interpolate: /\{\{=([\s\S]+?)\}\}/g
}; };
var custom = _.template('<ul>{{ for (var key in people) { }}<li>{{= people[key] }}</li>{{ } }}</ul>'); var custom = _.template('<ul>{{ for (var key in people) { }}<li>{{= people[key] }}</li>{{ } }}</ul>');
result = custom({people : {moe : 'Moe', larry : 'Larry', curly : 'Curly'}}); result = custom({people: {moe: 'Moe', larry: 'Larry', curly: 'Curly'}});
equal(result, '<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>', 'can run arbitrary javascript in templates'); equal(result, '<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>', 'can run arbitrary javascript in templates');
var customQuote = _.template("It's its, not it's"); var customQuote = _.template("It's its, not it's");
@@ -240,12 +271,12 @@
equal(quoteInStatementAndBody({foo: 'bar'}), "Statement quotes and 'quotes'."); equal(quoteInStatementAndBody({foo: 'bar'}), "Statement quotes and 'quotes'.");
_.templateSettings = { _.templateSettings = {
evaluate : /<\?([\s\S]+?)\?>/g, evaluate: /<\?([\s\S]+?)\?>/g,
interpolate : /<\?=([\s\S]+?)\?>/g interpolate: /<\?=([\s\S]+?)\?>/g
}; };
var customWithSpecialChars = _.template('<ul><? for (var key in people) { ?><li><?= people[key] ?></li><? } ?></ul>'); var customWithSpecialChars = _.template('<ul><? for (var key in people) { ?><li><?= people[key] ?></li><? } ?></ul>');
result = customWithSpecialChars({people : {moe : 'Moe', larry : 'Larry', curly : 'Curly'}}); result = customWithSpecialChars({people: {moe: 'Moe', larry: 'Larry', curly: 'Curly'}});
equal(result, '<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>', 'can run arbitrary javascript in templates'); equal(result, '<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>', 'can run arbitrary javascript in templates');
var customWithSpecialCharsQuote = _.template("It's its, not it's"); var customWithSpecialCharsQuote = _.template("It's its, not it's");
@@ -255,21 +286,22 @@
equal(quoteInStatementAndBody({foo: 'bar'}), "Statement quotes and 'quotes'."); equal(quoteInStatementAndBody({foo: 'bar'}), "Statement quotes and 'quotes'.");
_.templateSettings = { _.templateSettings = {
interpolate : /\{\{(.+?)\}\}/g interpolate: /\{\{(.+?)\}\}/g
}; };
var mustache = _.template('Hello {{planet}}!'); var mustache = _.template('Hello {{planet}}!');
equal(mustache({planet : 'World'}), 'Hello World!', 'can mimic mustache.js'); equal(mustache({planet: 'World'}), 'Hello World!', 'can mimic mustache.js');
var templateWithNull = _.template('a null undefined {{planet}}'); var templateWithNull = _.template('a null undefined {{planet}}');
equal(templateWithNull({planet : 'world'}), 'a null undefined world', 'can handle missing escape and evaluate settings'); equal(templateWithNull({planet: 'world'}), 'a null undefined world', 'can handle missing escape and evaluate settings');
}); });
test('_.template provides the generated function source, when a SyntaxError occurs', function() { test('_.template provides the generated function source, when a SyntaxError occurs', function() {
var source;
try { try {
_.template('<b><%= if x %></b>'); _.template('<b><%= if x %></b>');
} catch (ex) { } catch (ex) {
var source = ex.source; source = ex.source;
} }
ok(/__p/.test(source)); ok(/__p/.test(source));
}); });
@@ -284,13 +316,13 @@
strictEqual(_.result(obj, 'w'), ''); strictEqual(_.result(obj, 'w'), '');
strictEqual(_.result(obj, 'x'), 'x'); strictEqual(_.result(obj, 'x'), 'x');
strictEqual(_.result(obj, 'y'), 'x'); strictEqual(_.result(obj, 'y'), 'x');
strictEqual(_.result(obj, 'z'), undefined); strictEqual(_.result(obj, 'z'), void 0);
strictEqual(_.result(null, 'x'), undefined); strictEqual(_.result(null, 'x'), void 0);
}); });
test('result returns a default value if object is null or undefined', function() { test('result returns a default value if object is null or undefined', function() {
strictEqual(_.result(null, 'b', 'default'), 'default'); strictEqual(_.result(null, 'b', 'default'), 'default');
strictEqual(_.result(undefined, 'c', 'default'), 'default'); strictEqual(_.result(void 0, 'c', 'default'), 'default');
strictEqual(_.result(''.match('missing'), 1, 'default'), 'default'); strictEqual(_.result(''.match('missing'), 1, 'default'), 'default');
}); });
@@ -301,7 +333,7 @@
test('result only returns the default value if the object does not have the property or is undefined', function() { test('result only returns the default value if the object does not have the property or is undefined', function() {
strictEqual(_.result({}, 'b', 'default'), 'default'); strictEqual(_.result({}, 'b', 'default'), 'default');
strictEqual(_.result({d: undefined}, 'd', 'default'), 'default'); strictEqual(_.result({d: void 0}, 'd', 'default'), 'default');
}); });
test('result does not return the default if the property of an object is found in the prototype', function() { test('result does not return the default if the property of an object is found in the prototype', function() {
@@ -312,7 +344,7 @@
test('result does use the fallback when the result of invoking the property is undefined', function() { test('result does use the fallback when the result of invoking the property is undefined', function() {
var obj = {a: function() {}}; var obj = {a: function() {}};
strictEqual(_.result(obj, 'a', 'failed'), undefined); strictEqual(_.result(obj, 'a', 'failed'), void 0);
}); });
test('result fallback can use a function', function() { test('result fallback can use a function', function() {
@@ -341,11 +373,11 @@
test('#556 - undefined template variables.', function() { test('#556 - undefined template variables.', function() {
var template = _.template('<%=x%>'); var template = _.template('<%=x%>');
strictEqual(template({x: null}), ''); strictEqual(template({x: null}), '');
strictEqual(template({x: undefined}), ''); strictEqual(template({x: void 0}), '');
var templateEscaped = _.template('<%-x%>'); var templateEscaped = _.template('<%-x%>');
strictEqual(templateEscaped({x: null}), ''); strictEqual(templateEscaped({x: null}), '');
strictEqual(templateEscaped({x: undefined}), ''); strictEqual(templateEscaped({x: void 0}), '');
var templateWithProperty = _.template('<%=x.foo%>'); var templateWithProperty = _.template('<%=x.foo%>');
strictEqual(templateWithProperty({x: {}}), ''); strictEqual(templateWithProperty({x: {}}), '');

View File

@@ -8,29 +8,32 @@
// Baseline setup // Baseline setup
// -------------- // --------------
// Establish the root object, `window` in the browser, or `exports` on the server. // Establish the root object, `window` (`self`) in the browser, `global`
var root = this; // on the server, or `this` in some virtual machines. We use `self`
// instead of `window` for `WebWorker` support.
var root = typeof self === 'object' && self.self === self && self ||
typeof global === 'object' && global.global === global && global ||
this;
// Save the previous value of the `_` variable. // Save the previous value of the `_` variable.
var previousUnderscore = root._; var previousUnderscore = root._;
// Save bytes in the minified (but not gzipped) version: // Save bytes in the minified (but not gzipped) version:
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; var ArrayProto = Array.prototype, ObjProto = Object.prototype;
// Create quick reference variables for speed access to core prototypes. // Create quick reference variables for speed access to core prototypes.
var var
push = ArrayProto.push, push = ArrayProto.push,
slice = ArrayProto.slice, slice = ArrayProto.slice,
toString = ObjProto.toString, toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty; hasOwnProperty = ObjProto.hasOwnProperty;
// All **ECMAScript 5** native function implementations that we hope to use // All **ECMAScript 5** native function implementations that we hope to use
// are declared here. // are declared here.
var var
nativeIsArray = Array.isArray, nativeIsArray = Array.isArray,
nativeKeys = Object.keys, nativeKeys = Object.keys,
nativeBind = FuncProto.bind, nativeCreate = Object.create;
nativeCreate = Object.create;
// Naked function reference for surrogate-prototype-swapping. // Naked function reference for surrogate-prototype-swapping.
var Ctor = function(){}; var Ctor = function(){};
@@ -43,7 +46,7 @@
}; };
// Export the Underscore object for **Node.js**, with // Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in // backwards-compatibility for their old module API. If we're in
// the browser, add `_` as a global object. // the browser, add `_` as a global object.
if (typeof exports !== 'undefined') { if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) { if (typeof module !== 'undefined' && module.exports) {
@@ -66,9 +69,8 @@
case 1: return function(value) { case 1: return function(value) {
return func.call(context, value); return func.call(context, value);
}; };
case 2: return function(value, other) { // The 2-parameter case has been omitted only because no current consumers
return func.call(context, value, other); // made use of it.
};
case 3: return function(value, index, collection) { case 3: return function(value, index, collection) {
return func.call(context, value, index, collection); return func.call(context, value, index, collection);
}; };
@@ -94,21 +96,27 @@
return cb(value, context, Infinity); return cb(value, context, Infinity);
}; };
// An internal function for creating assigner functions. // Similar to ES6's rest param (http://ariya.ofilabs.com/2013/03/es6-and-rest-parameter.html)
var createAssigner = function(keysFunc, undefinedOnly) { // This accumulates the arguments passed into an array, after a given index.
return function(obj) { var restArgs = function(func, startIndex) {
var length = arguments.length; startIndex = startIndex == null ? func.length - 1 : +startIndex;
if (length < 2 || obj == null) return obj; return function() {
for (var index = 1; index < length; index++) { var length = Math.max(arguments.length - startIndex, 0);
var source = arguments[index], var rest = Array(length);
keys = keysFunc(source), for (var index = 0; index < length; index++) {
l = keys.length; rest[index] = arguments[index + startIndex];
for (var i = 0; i < l; i++) {
var key = keys[i];
if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
}
} }
return obj; switch (startIndex) {
case 0: return func.call(this, rest);
case 1: return func.call(this, arguments[0], rest);
case 2: return func.call(this, arguments[0], arguments[1], rest);
}
var args = Array(startIndex + 1);
for (index = 0; index < startIndex; index++) {
args[index] = arguments[index];
}
args[startIndex] = rest;
return func.apply(this, args);
}; };
}; };
@@ -175,30 +183,29 @@
}; };
// Create a reducing function iterating left or right. // Create a reducing function iterating left or right.
function createReduce(dir) { var createReduce = function(dir) {
// Optimized iterator function as using arguments.length // Optimized iterator function as using arguments.length
// in the main function will deoptimize the, see #1991. // in the main function will deoptimize the, see #1991.
function iterator(obj, iteratee, memo, keys, index, length) { var reducer = function(obj, iteratee, memo, initial) {
var keys = !isArrayLike(obj) && _.keys(obj),
length = (keys || obj).length,
index = dir > 0 ? 0 : length - 1;
if (!initial) {
memo = obj[keys ? keys[index] : index];
index += dir;
}
for (; index >= 0 && index < length; index += dir) { for (; index >= 0 && index < length; index += dir) {
var currentKey = keys ? keys[index] : index; var currentKey = keys ? keys[index] : index;
memo = iteratee(memo, obj[currentKey], currentKey, obj); memo = iteratee(memo, obj[currentKey], currentKey, obj);
} }
return memo; return memo;
} };
return function(obj, iteratee, memo, context) { return function(obj, iteratee, memo, context) {
iteratee = optimizeCb(iteratee, context, 4); var initial = arguments.length >= 3;
var keys = !isArrayLike(obj) && _.keys(obj), return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
length = (keys || obj).length,
index = dir > 0 ? 0 : length - 1;
// Determine the initial value if none is provided.
if (arguments.length < 3) {
memo = obj[keys ? keys[index] : index];
index += dir;
}
return iterator(obj, iteratee, memo, keys, index, length);
}; };
} };
// **Reduce** builds up a single result from a list of values, aka `inject`, // **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`. // or `foldl`.
@@ -269,14 +276,13 @@
}; };
// Invoke a method (with arguments) on every item in a collection. // Invoke a method (with arguments) on every item in a collection.
_.invoke = function(obj, method) { _.invoke = restArgs(function(obj, method, args) {
var args = slice.call(arguments, 2);
var isFunc = _.isFunction(method); var isFunc = _.isFunction(method);
return _.map(obj, function(value) { return _.map(obj, function(value) {
var func = isFunc ? method : value[method]; var func = isFunc ? method : value[method];
return func == null ? func : func.apply(value, args); return func == null ? func : func.apply(value, args);
}); });
}; });
// Convenience version of a common use case of `map`: fetching a property. // Convenience version of a common use case of `map`: fetching a property.
_.pluck = function(obj, key) { _.pluck = function(obj, key) {
@@ -299,7 +305,7 @@
_.max = function(obj, iteratee, context) { _.max = function(obj, iteratee, context) {
var result = -Infinity, lastComputed = -Infinity, var result = -Infinity, lastComputed = -Infinity,
value, computed; value, computed;
if (iteratee == null && obj != null) { if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) {
obj = isArrayLike(obj) ? obj : _.values(obj); obj = isArrayLike(obj) ? obj : _.values(obj);
for (var i = 0, length = obj.length; i < length; i++) { for (var i = 0, length = obj.length; i < length; i++) {
value = obj[i]; value = obj[i];
@@ -309,10 +315,10 @@
} }
} else { } else {
iteratee = cb(iteratee, context); iteratee = cb(iteratee, context);
_.each(obj, function(value, index, list) { _.each(obj, function(v, index, list) {
computed = iteratee(value, index, list); computed = iteratee(v, index, list);
if (computed > lastComputed || computed === -Infinity && result === -Infinity) { if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
result = value; result = v;
lastComputed = computed; lastComputed = computed;
} }
}); });
@@ -324,7 +330,7 @@
_.min = function(obj, iteratee, context) { _.min = function(obj, iteratee, context) {
var result = Infinity, lastComputed = Infinity, var result = Infinity, lastComputed = Infinity,
value, computed; value, computed;
if (iteratee == null && obj != null) { if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) {
obj = isArrayLike(obj) ? obj : _.values(obj); obj = isArrayLike(obj) ? obj : _.values(obj);
for (var i = 0, length = obj.length; i < length; i++) { for (var i = 0, length = obj.length; i < length; i++) {
value = obj[i]; value = obj[i];
@@ -334,10 +340,10 @@
} }
} else { } else {
iteratee = cb(iteratee, context); iteratee = cb(iteratee, context);
_.each(obj, function(value, index, list) { _.each(obj, function(v, index, list) {
computed = iteratee(value, index, list); computed = iteratee(v, index, list);
if (computed < lastComputed || computed === Infinity && result === Infinity) { if (computed < lastComputed || computed === Infinity && result === Infinity) {
result = value; result = v;
lastComputed = computed; lastComputed = computed;
} }
}); });
@@ -345,21 +351,13 @@
return result; return result;
}; };
// Shuffle a collection, using the modern version of the // Shuffle a collection.
// [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/FisherYates_shuffle).
_.shuffle = function(obj) { _.shuffle = function(obj) {
var set = isArrayLike(obj) ? obj : _.values(obj); return _.sample(obj, Infinity);
var length = set.length;
var shuffled = Array(length);
for (var index = 0, rand; index < length; index++) {
rand = _.random(0, index);
if (rand !== index) shuffled[index] = shuffled[rand];
shuffled[rand] = set[index];
}
return shuffled;
}; };
// Sample **n** random values from a collection. // Sample **n** random values from a collection using the modern version of the
// [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/FisherYates_shuffle).
// If **n** is not specified, returns a single random element. // If **n** is not specified, returns a single random element.
// The internal `guard` argument allows it to work with `map`. // The internal `guard` argument allows it to work with `map`.
_.sample = function(obj, n, guard) { _.sample = function(obj, n, guard) {
@@ -367,17 +365,28 @@
if (!isArrayLike(obj)) obj = _.values(obj); if (!isArrayLike(obj)) obj = _.values(obj);
return obj[_.random(obj.length - 1)]; return obj[_.random(obj.length - 1)];
} }
return _.shuffle(obj).slice(0, Math.max(0, n)); var sample = isArrayLike(obj) ? _.clone(obj) : _.values(obj);
var length = getLength(sample);
n = Math.max(Math.min(n, length), 0);
var last = length - 1;
for (var index = 0; index < n; index++) {
var rand = _.random(index, last);
var temp = sample[index];
sample[index] = sample[rand];
sample[rand] = temp;
}
return sample.slice(0, n);
}; };
// Sort the object's values by a criterion produced by an iteratee. // Sort the object's values by a criterion produced by an iteratee.
_.sortBy = function(obj, iteratee, context) { _.sortBy = function(obj, iteratee, context) {
var index = 0;
iteratee = cb(iteratee, context); iteratee = cb(iteratee, context);
return _.pluck(_.map(obj, function(value, index, list) { return _.pluck(_.map(obj, function(value, key, list) {
return { return {
value: value, value: value,
index: index, index: index++,
criteria: iteratee(value, index, list) criteria: iteratee(value, key, list)
}; };
}).sort(function(left, right) { }).sort(function(left, right) {
var a = left.criteria; var a = left.criteria;
@@ -391,9 +400,9 @@
}; };
// An internal function used for aggregate "group by" operations. // An internal function used for aggregate "group by" operations.
var group = function(behavior) { var group = function(behavior, partition) {
return function(obj, iteratee, context) { return function(obj, iteratee, context) {
var result = {}; var result = partition ? [[], []] : {};
iteratee = cb(iteratee, context); iteratee = cb(iteratee, context);
_.each(obj, function(value, index) { _.each(obj, function(value, index) {
var key = iteratee(value, index, obj); var key = iteratee(value, index, obj);
@@ -438,14 +447,9 @@
// Split a collection into two arrays: one whose elements all satisfy the given // Split a collection into two arrays: one whose elements all satisfy the given
// predicate, and one whose elements all do not satisfy the predicate. // predicate, and one whose elements all do not satisfy the predicate.
_.partition = function(obj, predicate, context) { _.partition = group(function(result, value, pass) {
predicate = cb(predicate, context); result[pass ? 0 : 1].push(value);
var pass = [], fail = []; }, true);
_.each(obj, function(value, key, obj) {
(predicate(value, key, obj) ? pass : fail).push(value);
});
return [pass, fail];
};
// Array Functions // Array Functions
// --------------- // ---------------
@@ -487,17 +491,19 @@
}; };
// Internal implementation of a recursive `flatten` function. // Internal implementation of a recursive `flatten` function.
var flatten = function(input, shallow, strict, startIndex) { var flatten = function(input, shallow, strict, output) {
var output = [], idx = 0; output = output || [];
for (var i = startIndex || 0, length = getLength(input); i < length; i++) { var idx = output.length;
for (var i = 0, length = getLength(input); i < length; i++) {
var value = input[i]; var value = input[i];
if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
//flatten current level of array or arguments object //flatten current level of array or arguments object
if (!shallow) value = flatten(value, shallow, strict); if (shallow) {
var j = 0, len = value.length; var j = 0, len = value.length;
output.length += len; while (j < len) output[idx++] = value[j++];
while (j < len) { } else {
output[idx++] = value[j++]; flatten(value, shallow, strict, output);
idx = output.length;
} }
} else if (!strict) { } else if (!strict) {
output[idx++] = value; output[idx++] = value;
@@ -512,9 +518,9 @@
}; };
// Return a version of the array that does not contain the specified value(s). // Return a version of the array that does not contain the specified value(s).
_.without = function(array) { _.without = restArgs(function(array, otherArrays) {
return _.difference(array, slice.call(arguments, 1)); return _.difference(array, otherArrays);
}; });
// Produce a duplicate-free version of the array. If the array has already // Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm. // been sorted, you have the option of using a faster algorithm.
@@ -548,9 +554,9 @@
// Produce an array that contains the union: each distinct element from all of // Produce an array that contains the union: each distinct element from all of
// the passed-in arrays. // the passed-in arrays.
_.union = function() { _.union = restArgs(function(arrays) {
return _.uniq(flatten(arguments, true, true)); return _.uniq(flatten(arrays, true, true));
}; });
// Produce an array that contains every item shared between all the // Produce an array that contains every item shared between all the
// passed-in arrays. // passed-in arrays.
@@ -560,7 +566,8 @@
for (var i = 0, length = getLength(array); i < length; i++) { for (var i = 0, length = getLength(array); i < length; i++) {
var item = array[i]; var item = array[i];
if (_.contains(result, item)) continue; if (_.contains(result, item)) continue;
for (var j = 1; j < argsLength; j++) { var j;
for (j = 1; j < argsLength; j++) {
if (!_.contains(arguments[j], item)) break; if (!_.contains(arguments[j], item)) break;
} }
if (j === argsLength) result.push(item); if (j === argsLength) result.push(item);
@@ -570,18 +577,12 @@
// Take the difference between one array and a number of other arrays. // Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain. // Only the elements present in just the first array will remain.
_.difference = function(array) { _.difference = restArgs(function(array, rest) {
var rest = flatten(arguments, true, true, 1); rest = flatten(rest, true, true);
return _.filter(array, function(value){ return _.filter(array, function(value){
return !_.contains(rest, value); return !_.contains(rest, value);
}); });
}; });
// Zip together multiple lists into a single array -- elements that share
// an index go together.
_.zip = function() {
return _.unzip(arguments);
};
// Complement of _.zip. Unzip accepts an array of arrays and groups // Complement of _.zip. Unzip accepts an array of arrays and groups
// each array's elements on shared indices // each array's elements on shared indices
@@ -595,6 +596,10 @@
return result; return result;
}; };
// Zip together multiple lists into a single array -- elements that share
// an index go together.
_.zip = restArgs(_.unzip);
// Converts lists into objects. Pass either a single array of `[key, value]` // Converts lists into objects. Pass either a single array of `[key, value]`
// pairs, or two parallel arrays of the same length -- one of keys, and one of // pairs, or two parallel arrays of the same length -- one of keys, and one of
// the corresponding values. // the corresponding values.
@@ -611,7 +616,7 @@
}; };
// Generator function to create the findIndex and findLastIndex functions // Generator function to create the findIndex and findLastIndex functions
function createPredicateIndexFinder(dir) { var createPredicateIndexFinder = function(dir) {
return function(array, predicate, context) { return function(array, predicate, context) {
predicate = cb(predicate, context); predicate = cb(predicate, context);
var length = getLength(array); var length = getLength(array);
@@ -621,7 +626,7 @@
} }
return -1; return -1;
}; };
} };
// Returns the first index on an array-like that passes a predicate test // Returns the first index on an array-like that passes a predicate test
_.findIndex = createPredicateIndexFinder(1); _.findIndex = createPredicateIndexFinder(1);
@@ -641,14 +646,14 @@
}; };
// Generator function to create the indexOf and lastIndexOf functions // Generator function to create the indexOf and lastIndexOf functions
function createIndexFinder(dir, predicateFind, sortedIndex) { var createIndexFinder = function(dir, predicateFind, sortedIndex) {
return function(array, item, idx) { return function(array, item, idx) {
var i = 0, length = getLength(array); var i = 0, length = getLength(array);
if (typeof idx == 'number') { if (typeof idx == 'number') {
if (dir > 0) { if (dir > 0) {
i = idx >= 0 ? idx : Math.max(idx + length, i); i = idx >= 0 ? idx : Math.max(idx + length, i);
} else { } else {
length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
} }
} else if (sortedIndex && idx && length) { } else if (sortedIndex && idx && length) {
idx = sortedIndex(array, item); idx = sortedIndex(array, item);
@@ -663,7 +668,7 @@
} }
return -1; return -1;
}; };
} };
// Return the position of the first occurrence of an item in an array, // Return the position of the first occurrence of an item in an array,
// or -1 if the item is not included in the array. // or -1 if the item is not included in the array.
@@ -692,6 +697,19 @@
return range; return range;
}; };
// Split an **array** into several arrays containing **count** or less elements
// of initial array
_.chunk = function(array, count) {
if (count == null || count < 1) return [];
var result = [];
var i = 0, length = array.length;
while (i < length) {
result.push(slice.call(array, i, i += count));
}
return result;
};
// Function (ahem) Functions // Function (ahem) Functions
// ------------------ // ------------------
@@ -708,45 +726,46 @@
// Create a function bound to a given object (assigning `this`, and arguments, // Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
// available. // available.
_.bind = function(func, context) { _.bind = restArgs(function(func, context, args) {
if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
var args = slice.call(arguments, 2); var bound = restArgs(function(callArgs) {
var bound = function() { return executeBound(func, bound, context, this, args.concat(callArgs));
return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); });
};
return bound; return bound;
}; });
// Partially apply a function by creating a version that has had some of its // Partially apply a function by creating a version that has had some of its
// arguments pre-filled, without changing its dynamic `this` context. _ acts // arguments pre-filled, without changing its dynamic `this` context. _ acts
// as a placeholder, allowing any combination of arguments to be pre-filled. // as a placeholder by default, allowing any combination of arguments to be
_.partial = function(func) { // pre-filled. Set `_.partial.placeholder` for a custom placeholder argument.
var boundArgs = slice.call(arguments, 1); _.partial = restArgs(function(func, boundArgs) {
var placeholder = _.partial.placeholder;
var bound = function() { var bound = function() {
var position = 0, length = boundArgs.length; var position = 0, length = boundArgs.length;
var args = Array(length); var args = Array(length);
for (var i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i]; args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i];
} }
while (position < arguments.length) args.push(arguments[position++]); while (position < arguments.length) args.push(arguments[position++]);
return executeBound(func, bound, this, this, args); return executeBound(func, bound, this, this, args);
}; };
return bound; return bound;
}; });
_.partial.placeholder = _;
// Bind a number of an object's methods to that object. Remaining arguments // Bind a number of an object's methods to that object. Remaining arguments
// are the method names to be bound. Useful for ensuring that all callbacks // are the method names to be bound. Useful for ensuring that all callbacks
// defined on an object belong to it. // defined on an object belong to it.
_.bindAll = function(obj) { _.bindAll = restArgs(function(obj, keys) {
var i, length = arguments.length, key; keys = flatten(keys, false, false);
if (length <= 1) throw new Error('bindAll must be passed function names'); var index = keys.length;
for (i = 1; i < length; i++) { if (index < 1) throw new Error('bindAll must be passed function names');
key = arguments[i]; while (index--) {
var key = keys[index];
obj[key] = _.bind(obj[key], obj); obj[key] = _.bind(obj[key], obj);
} }
return obj; });
};
// Memoize an expensive function by storing its results. // Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) { _.memoize = function(func, hasher) {
@@ -762,12 +781,11 @@
// Delays a function for the given number of milliseconds, and then calls // Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied. // it with the arguments supplied.
_.delay = function(func, wait) { _.delay = restArgs(function(func, wait, args) {
var args = slice.call(arguments, 2);
return setTimeout(function(){ return setTimeout(function(){
return func.apply(null, args); return func.apply(null, args);
}, wait); }, wait);
}; });
// Defers a function, scheduling it to run after the current call stack has // Defers a function, scheduling it to run after the current call stack has
// cleared. // cleared.
@@ -898,6 +916,8 @@
// often you call it. Useful for lazy initialization. // often you call it. Useful for lazy initialization.
_.once = _.partial(_.before, 2); _.once = _.partial(_.before, 2);
_.restArgs = restArgs;
// Object Functions // Object Functions
// ---------------- // ----------------
@@ -906,10 +926,10 @@
var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];
function collectNonEnumProps(obj, keys) { var collectNonEnumProps = function(obj, keys) {
var nonEnumIdx = nonEnumerableProps.length; var nonEnumIdx = nonEnumerableProps.length;
var constructor = obj.constructor; var constructor = obj.constructor;
var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto; var proto = _.isFunction(constructor) && constructor.prototype || ObjProto;
// Constructor is a special case. // Constructor is a special case.
var prop = 'constructor'; var prop = 'constructor';
@@ -921,7 +941,7 @@
keys.push(prop); keys.push(prop);
} }
} }
} };
// Retrieve the names of an object's own properties. // Retrieve the names of an object's own properties.
// Delegates to **ECMAScript 5**'s native `Object.keys` // Delegates to **ECMAScript 5**'s native `Object.keys`
@@ -960,15 +980,14 @@
// In contrast to _.map it returns an object // In contrast to _.map it returns an object
_.mapObject = function(obj, iteratee, context) { _.mapObject = function(obj, iteratee, context) {
iteratee = cb(iteratee, context); iteratee = cb(iteratee, context);
var keys = _.keys(obj), var keys = _.keys(obj),
length = keys.length, length = keys.length,
results = {}, results = {};
currentKey; for (var index = 0; index < length; index++) {
for (var index = 0; index < length; index++) { var currentKey = keys[index];
currentKey = keys[index]; results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
results[currentKey] = iteratee(obj[currentKey], currentKey, obj); }
} return results;
return results;
}; };
// Convert an object into a list of `[key, value]` pairs. // Convert an object into a list of `[key, value]` pairs.
@@ -1002,6 +1021,25 @@
return names.sort(); return names.sort();
}; };
// An internal function for creating assigner functions.
var createAssigner = function(keysFunc, defaults) {
return function(obj) {
var length = arguments.length;
if (defaults) obj = Object(obj);
if (length < 2 || obj == null) return obj;
for (var index = 1; index < length; index++) {
var source = arguments[index],
keys = keysFunc(source),
l = keys.length;
for (var i = 0; i < l; i++) {
var key = keys[i];
if (!defaults || obj[key] === void 0) obj[key] = source[key];
}
}
return obj;
};
};
// Extend a given object with all the properties in passed-in object(s). // Extend a given object with all the properties in passed-in object(s).
_.extend = createAssigner(_.allKeys); _.extend = createAssigner(_.allKeys);
@@ -1019,16 +1057,21 @@
} }
}; };
// Internal pick helper function to determine if `obj` has key `key`.
var keyInObj = function(value, key, obj) {
return key in obj;
};
// Return a copy of the object only containing the whitelisted properties. // Return a copy of the object only containing the whitelisted properties.
_.pick = function(object, oiteratee, context) { _.pick = restArgs(function(obj, keys) {
var result = {}, obj = object, iteratee, keys; var result = {}, iteratee = keys[0];
if (obj == null) return result; if (obj == null) return result;
if (_.isFunction(oiteratee)) { if (_.isFunction(iteratee)) {
if (keys.length > 1) iteratee = optimizeCb(iteratee, keys[1]);
keys = _.allKeys(obj); keys = _.allKeys(obj);
iteratee = optimizeCb(oiteratee, context);
} else { } else {
keys = flatten(arguments, false, false, 1); iteratee = keyInObj;
iteratee = function(value, key, obj) { return key in obj; }; keys = flatten(keys, false, false);
obj = Object(obj); obj = Object(obj);
} }
for (var i = 0, length = keys.length; i < length; i++) { for (var i = 0, length = keys.length; i < length; i++) {
@@ -1037,20 +1080,22 @@
if (iteratee(value, key, obj)) result[key] = value; if (iteratee(value, key, obj)) result[key] = value;
} }
return result; return result;
}; });
// Return a copy of the object without the blacklisted properties. // Return a copy of the object without the blacklisted properties.
_.omit = function(obj, iteratee, context) { _.omit = restArgs(function(obj, keys) {
var iteratee = keys[0], context;
if (_.isFunction(iteratee)) { if (_.isFunction(iteratee)) {
iteratee = _.negate(iteratee); iteratee = _.negate(iteratee);
if (keys.length > 1) context = keys[1];
} else { } else {
var keys = _.map(flatten(arguments, false, false, 1), String); keys = _.map(flatten(keys, false, false), String);
iteratee = function(value, key) { iteratee = function(value, key) {
return !_.contains(keys, key); return !_.contains(keys, key);
}; };
} }
return _.pick(obj, iteratee, context); return _.pick(obj, iteratee, context);
}; });
// Fill in a given object with default properties. // Fill in a given object with default properties.
_.defaults = createAssigner(_.allKeys, true); _.defaults = createAssigner(_.allKeys, true);
@@ -1092,12 +1137,23 @@
// Internal recursive comparison function for `isEqual`. // Internal recursive comparison function for `isEqual`.
var eq = function(a, b, aStack, bStack) { var eq, deepEq;
eq = function(a, b, aStack, bStack) {
// Identical objects are equal. `0 === -0`, but they aren't identical. // Identical objects are equal. `0 === -0`, but they aren't identical.
// See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
if (a === b) return a !== 0 || 1 / a === 1 / b; if (a === b) return a !== 0 || 1 / a === 1 / b;
// A strict comparison is necessary because `null == undefined`. // A strict comparison is necessary because `null == undefined`.
if (a == null || b == null) return a === b; if (a == null || b == null) return a === b;
// `NaN`s are equivalent, but non-reflexive.
if (a !== a) return b !== b;
// Exhaust primitive checks
var type = typeof a;
if (type !== 'function' && type !== 'object' && typeof b !== 'object') return false;
return deepEq(a, b, aStack, bStack);
};
// Internal recursive comparison function for `isEqual`.
deepEq = function(a, b, aStack, bStack) {
// Unwrap any wrapped objects. // Unwrap any wrapped objects.
if (a instanceof _) a = a._wrapped; if (a instanceof _) a = a._wrapped;
if (b instanceof _) b = b._wrapped; if (b instanceof _) b = b._wrapped;
@@ -1230,8 +1286,9 @@
} }
// Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8, // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8,
// IE 11 (#1621), and in Safari 8 (#1929). // IE 11 (#1621), Safari 8 (#1929), and PhantomJS (#2236).
if (typeof /./ != 'function' && typeof Int8Array != 'object') { var nodelist = root.document && root.document.childNodes;
if (typeof /./ != 'function' && typeof Int8Array != 'object' && typeof nodelist != 'function') {
_.isFunction = function(obj) { _.isFunction = function(obj) {
return typeof obj == 'function' || false; return typeof obj == 'function' || false;
}; };
@@ -1242,9 +1299,9 @@
return isFinite(obj) && !isNaN(parseFloat(obj)); return isFinite(obj) && !isNaN(parseFloat(obj));
}; };
// Is the given value `NaN`? (NaN is the only number which does not equal itself). // Is the given value `NaN`?
_.isNaN = function(obj) { _.isNaN = function(obj) {
return _.isNumber(obj) && obj !== +obj; return _.isNumber(obj) && isNaN(obj);
}; };
// Is a given value a boolean? // Is a given value a boolean?
@@ -1362,8 +1419,8 @@
// If the value of the named `property` is a function then invoke it with the // If the value of the named `property` is a function then invoke it with the
// `object` as context; otherwise, return it. // `object` as context; otherwise, return it.
_.result = function(object, property, fallback) { _.result = function(object, prop, fallback) {
var value = object == null ? void 0 : object[property]; var value = object == null ? void 0 : object[prop];
if (value === void 0) { if (value === void 0) {
value = fallback; value = fallback;
} }
@@ -1381,9 +1438,9 @@
// By default, Underscore uses ERB-style template delimiters, change the // By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters. // following template settings to use alternative delimiters.
_.templateSettings = { _.templateSettings = {
evaluate : /<%([\s\S]+?)%>/g, evaluate: /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g, interpolate: /<%=([\s\S]+?)%>/g,
escape : /<%-([\s\S]+?)%>/g escape: /<%-([\s\S]+?)%>/g
}; };
// When customizing `templateSettings`, if you don't want to define an // When customizing `templateSettings`, if you don't want to define an
@@ -1394,15 +1451,15 @@
// Certain characters need to be escaped so that they can be put into a // Certain characters need to be escaped so that they can be put into a
// string literal. // string literal.
var escapes = { var escapes = {
"'": "'", "'": "'",
'\\': '\\', '\\': '\\',
'\r': 'r', '\r': 'r',
'\n': 'n', '\n': 'n',
'\u2028': 'u2028', '\u2028': 'u2028',
'\u2029': 'u2029' '\u2029': 'u2029'
}; };
var escaper = /\\|'|\r|\n|\u2028|\u2029/g; var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g;
var escapeChar = function(match) { var escapeChar = function(match) {
return '\\' + escapes[match]; return '\\' + escapes[match];
@@ -1427,7 +1484,7 @@
var index = 0; var index = 0;
var source = "__p+='"; var source = "__p+='";
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset).replace(escaper, escapeChar); source += text.slice(index, offset).replace(escapeRegExp, escapeChar);
index = offset + match.length; index = offset + match.length;
if (escape) { if (escape) {
@@ -1438,7 +1495,7 @@
source += "';\n" + evaluate + "\n__p+='"; source += "';\n" + evaluate + "\n__p+='";
} }
// Adobe VMs need the match returned to produce the correct offest. // Adobe VMs need the match returned to produce the correct offset.
return match; return match;
}); });
source += "';\n"; source += "';\n";
@@ -1450,8 +1507,9 @@
"print=function(){__p+=__j.call(arguments,'');};\n" + "print=function(){__p+=__j.call(arguments,'');};\n" +
source + 'return __p;\n'; source + 'return __p;\n';
var render;
try { try {
var render = new Function(settings.variable || 'obj', '_', source); render = new Function(settings.variable || 'obj', '_', source);
} catch (e) { } catch (e) {
e.source = source; e.source = source;
throw e; throw e;
@@ -1482,7 +1540,7 @@
// underscore functions. Wrapped objects may be chained. // underscore functions. Wrapped objects may be chained.
// Helper function to continue chaining intermediate results. // Helper function to continue chaining intermediate results.
var result = function(instance, obj) { var chainResult = function(instance, obj) {
return instance._chain ? _(obj).chain() : obj; return instance._chain ? _(obj).chain() : obj;
}; };
@@ -1493,7 +1551,7 @@
_.prototype[name] = function() { _.prototype[name] = function() {
var args = [this._wrapped]; var args = [this._wrapped];
push.apply(args, arguments); push.apply(args, arguments);
return result(this, func.apply(_, args)); return chainResult(this, func.apply(_, args));
}; };
}); });
}; };
@@ -1508,7 +1566,7 @@
var obj = this._wrapped; var obj = this._wrapped;
method.apply(obj, arguments); method.apply(obj, arguments);
if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
return result(this, obj); return chainResult(this, obj);
}; };
}); });
@@ -1516,7 +1574,7 @@
_.each(['concat', 'join', 'slice'], function(name) { _.each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name]; var method = ArrayProto[name];
_.prototype[name] = function() { _.prototype[name] = function() {
return result(this, method.apply(this._wrapped, arguments)); return chainResult(this, method.apply(this._wrapped, arguments));
}; };
}); });
@@ -1545,4 +1603,4 @@
return _; return _;
}); });
} }
}.call(this)); }());