1(function() {
2  var _ = typeof require == 'function' ? require('..') : window._;
3
4  QUnit.module('Collections');
5
6  QUnit.test('each', function(assert) {
7    _.each([1, 2, 3], function(num, i) {
8      assert.equal(num, i + 1, 'each iterators provide value and iteration count');
9    });
10
11    var answers = [];
12    _.each([1, 2, 3], function(num){ answers.push(num * this.multiplier); }, {multiplier: 5});
13    assert.deepEqual(answers, [5, 10, 15], 'context object property accessed');
14
15    answers = [];
16    _.each([1, 2, 3], function(num){ answers.push(num); });
17    assert.deepEqual(answers, [1, 2, 3], 'can iterate a simple array');
18
19    answers = [];
20    var obj = {one: 1, two: 2, three: 3};
21    obj.constructor.prototype.four = 4;
22    _.each(obj, function(value, key){ answers.push(key); });
23    assert.deepEqual(answers, ['one', 'two', 'three'], 'iterating over objects works, and ignores the object prototype.');
24    delete obj.constructor.prototype.four;
25
26    // ensure the each function is JITed
27    _(1000).times(function() { _.each([], function(){}); });
28    var count = 0;
29    obj = {1: 'foo', 2: 'bar', 3: 'baz'};
30    _.each(obj, function(){ count++; });
31    assert.equal(count, 3, 'the fun should be called only 3 times');
32
33    var answer = null;
34    _.each([1, 2, 3], function(num, index, arr){ if (_.include(arr, num)) answer = true; });
35    assert.ok(answer, 'can reference the original collection from inside the iterator');
36
37    answers = 0;
38    _.each(null, function(){ ++answers; });
39    assert.equal(answers, 0, 'handles a null properly');
40
41    _.each(false, function(){});
42
43    var a = [1, 2, 3];
44    assert.strictEqual(_.each(a, function(){}), a);
45    assert.strictEqual(_.each(null, function(){}), null);
46  });
47
48  QUnit.test('forEach', function(assert) {
49    assert.strictEqual(_.forEach, _.each, 'is an alias for each');
50  });
51
52  QUnit.test('lookupIterator with contexts', function(assert) {
53    _.each([true, false, 'yes', '', 0, 1, {}], function(context) {
54      _.each([1], function() {
55        assert.equal(this, context);
56      }, context);
57    });
58  });
59
60  QUnit.test('Iterating objects with sketchy length properties', function(assert) {
61    var functions = [
62      'each', 'map', 'filter', 'find',
63      'some', 'every', 'max', 'min',
64      'groupBy', 'countBy', 'partition', 'indexBy'
65    ];
66    var reducers = ['reduce', 'reduceRight'];
67
68    var tricks = [
69      {length: '5'},
70      {length: {valueOf: _.constant(5)}},
71      {length: Math.pow(2, 53) + 1},
72      {length: Math.pow(2, 53)},
73      {length: null},
74      {length: -2},
75      {length: new Number(15)}
76    ];
77
78    assert.expect(tricks.length * (functions.length + reducers.length + 4));
79
80    _.each(tricks, function(trick) {
81      var length = trick.length;
82      assert.strictEqual(_.size(trick), 1, 'size on obj with length: ' + length);
83      assert.deepEqual(_.toArray(trick), [length], 'toArray on obj with length: ' + length);
84      assert.deepEqual(_.shuffle(trick), [length], 'shuffle on obj with length: ' + length);
85      assert.deepEqual(_.sample(trick), length, 'sample on obj with length: ' + length);
86
87
88      _.each(functions, function(method) {
89        _[method](trick, function(val, key) {
90          assert.strictEqual(key, 'length', method + ': ran with length = ' + val);
91        });
92      });
93
94      _.each(reducers, function(method) {
95        assert.strictEqual(_[method](trick), trick.length, method);
96      });
97    });
98  });
99
100  QUnit.test('Resistant to collection length and properties changing while iterating', function(assert) {
101
102    var collection = [
103      'each', 'map', 'filter', 'find',
104      'some', 'every', 'max', 'min', 'reject',
105      'groupBy', 'countBy', 'partition', 'indexBy',
106      'reduce', 'reduceRight'
107    ];
108    var array = [
109      'findIndex', 'findLastIndex'
110    ];
111    var object = [
112      'mapObject', 'findKey', 'pick', 'omit'
113    ];
114
115    _.each(collection.concat(array), function(method) {
116      var sparseArray = [1, 2, 3];
117      sparseArray.length = 100;
118      var answers = 0;
119      _[method](sparseArray, function(){
120        ++answers;
121        return method === 'every' ? true : null;
122      }, {});
123      assert.equal(answers, 100, method + ' enumerates [0, length)');
124
125      var growingCollection = [1, 2, 3], count = 0;
126      _[method](growingCollection, function() {
127        if (count < 10) growingCollection.push(count++);
128        return method === 'every' ? true : null;
129      }, {});
130      assert.equal(count, 3, method + ' is resistant to length changes');
131    });
132
133    _.each(collection.concat(object), function(method) {
134      var changingObject = {0: 0, 1: 1}, count = 0;
135      _[method](changingObject, function(val) {
136        if (count < 10) changingObject[++count] = val + 1;
137        return method === 'every' ? true : null;
138      }, {});
139
140      assert.equal(count, 2, method + ' is resistant to property changes');
141    });
142  });
143
144  QUnit.test('map', function(assert) {
145    var doubled = _.map([1, 2, 3], function(num){ return num * 2; });
146    assert.deepEqual(doubled, [2, 4, 6], 'doubled numbers');
147
148    var tripled = _.map([1, 2, 3], function(num){ return num * this.multiplier; }, {multiplier: 3});
149    assert.deepEqual(tripled, [3, 6, 9], 'tripled numbers with context');
150
151    doubled = _([1, 2, 3]).map(function(num){ return num * 2; });
152    assert.deepEqual(doubled, [2, 4, 6], 'OO-style doubled numbers');
153
154    var ids = _.map({length: 2, 0: {id: '1'}, 1: {id: '2'}}, function(n){
155      return n.id;
156    });
157    assert.deepEqual(ids, ['1', '2'], 'Can use collection methods on Array-likes.');
158
159    assert.deepEqual(_.map(null, _.noop), [], 'handles a null properly');
160
161    assert.deepEqual(_.map([1], function() {
162      return this.length;
163    }, [5]), [1], 'called with context');
164
165    // Passing a property name like _.pluck.
166    var people = [{name: 'moe', age: 30}, {name: 'curly', age: 50}];
167    assert.deepEqual(_.map(people, 'name'), ['moe', 'curly'], 'predicate string map to object properties');
168  });
169
170  QUnit.test('collect', function(assert) {
171    assert.strictEqual(_.collect, _.map, 'is an alias for map');
172  });
173
174  QUnit.test('reduce', function(assert) {
175    var sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 0);
176    assert.equal(sum, 6, 'can sum up an array');
177
178    var context = {multiplier: 3};
179    sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num * this.multiplier; }, 0, context);
180    assert.equal(sum, 18, 'can reduce with a context object');
181
182    sum = _([1, 2, 3]).reduce(function(memo, num){ return memo + num; }, 0);
183    assert.equal(sum, 6, 'OO-style reduce');
184
185    sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; });
186    assert.equal(sum, 6, 'default initial value');
187
188    var prod = _.reduce([1, 2, 3, 4], function(memo, num){ return memo * num; });
189    assert.equal(prod, 24, 'can reduce via multiplication');
190
191    assert.strictEqual(_.reduce(null, _.noop, 138), 138, 'handles a null (with initial value) properly');
192    assert.equal(_.reduce([], _.noop, void 0), void 0, 'undefined can be passed as a special case');
193    assert.equal(_.reduce([_], _.noop), _, 'collection of length one with no initial value returns the first item');
194    assert.equal(_.reduce([], _.noop), void 0, 'returns undefined when collection is empty and no initial value');
195  });
196
197  QUnit.test('foldl', function(assert) {
198    assert.strictEqual(_.foldl, _.reduce, 'is an alias for reduce');
199  });
200
201  QUnit.test('inject', function(assert) {
202    assert.strictEqual(_.inject, _.reduce, 'is an alias for reduce');
203  });
204
205  QUnit.test('reduceRight', function(assert) {
206    var list = _.reduceRight(['foo', 'bar', 'baz'], function(memo, str){ return memo + str; }, '');
207    assert.equal(list, 'bazbarfoo', 'can perform right folds');
208
209    list = _.reduceRight(['foo', 'bar', 'baz'], function(memo, str){ return memo + str; });
210    assert.equal(list, 'bazbarfoo', 'default initial value');
211
212    var sum = _.reduceRight({a: 1, b: 2, c: 3}, function(memo, num){ return memo + num; });
213    assert.equal(sum, 6, 'default initial value on object');
214
215    assert.strictEqual(_.reduceRight(null, _.noop, 138), 138, 'handles a null (with initial value) properly');
216    assert.equal(_.reduceRight([_], _.noop), _, 'collection of length one with no initial value returns the first item');
217
218    assert.equal(_.reduceRight([], _.noop, void 0), void 0, 'undefined can be passed as a special case');
219    assert.equal(_.reduceRight([], _.noop), void 0, 'returns undefined when collection is empty and no initial value');
220
221    // Assert that the correct arguments are being passed.
222
223    var args,
224        init = {},
225        object = {a: 1, b: 2},
226        lastKey = _.keys(object).pop();
227
228    var expected = lastKey === 'a'
229      ? [init, 1, 'a', object]
230      : [init, 2, 'b', object];
231
232    _.reduceRight(object, function() {
233      if (!args) args = _.toArray(arguments);
234    }, init);
235
236    assert.deepEqual(args, expected);
237
238    // And again, with numeric keys.
239
240    object = {2: 'a', 1: 'b'};
241    lastKey = _.keys(object).pop();
242    args = null;
243
244    expected = lastKey === '2'
245      ? [init, 'a', '2', object]
246      : [init, 'b', '1', object];
247
248    _.reduceRight(object, function() {
249      if (!args) args = _.toArray(arguments);
250    }, init);
251
252    assert.deepEqual(args, expected);
253  });
254
255  QUnit.test('foldr', function(assert) {
256    assert.strictEqual(_.foldr, _.reduceRight, 'is an alias for reduceRight');
257  });
258
259  QUnit.test('find', function(assert) {
260    var array = [1, 2, 3, 4];
261    assert.strictEqual(_.find(array, function(n) { return n > 2; }), 3, 'should return first found `value`');
262    assert.strictEqual(_.find(array, function() { return false; }), void 0, 'should return `undefined` if `value` is not found');
263
264    array.dontmatch = 55;
265    assert.strictEqual(_.find(array, function(x) { return x === 55; }), void 0, 'iterates array-likes correctly');
266
267    // Matching an object like _.findWhere.
268    var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}, {a: 2, b: 4}];
269    assert.deepEqual(_.find(list, {a: 1}), {a: 1, b: 2}, 'can be used as findWhere');
270    assert.deepEqual(_.find(list, {b: 4}), {a: 1, b: 4});
271    assert.notOk(_.find(list, {c: 1}), 'undefined when not found');
272    assert.notOk(_.find([], {c: 1}), 'undefined when searching empty list');
273
274    var result = _.find([1, 2, 3], function(num){ return num * 2 === 4; });
275    assert.equal(result, 2, 'found the first "2" and broke the loop');
276
277    var obj = {
278      a: {x: 1, z: 3},
279      b: {x: 2, z: 2},
280      c: {x: 3, z: 4},
281      d: {x: 4, z: 1}
282    };
283
284    assert.deepEqual(_.find(obj, {x: 2}), {x: 2, z: 2}, 'works on objects');
285    assert.deepEqual(_.find(obj, {x: 2, z: 1}), void 0);
286    assert.deepEqual(_.find(obj, function(x) {
287      return x.x === 4;
288    }), {x: 4, z: 1});
289
290    _.findIndex([{a: 1}], function(a, key, o) {
291      assert.equal(key, 0);
292      assert.deepEqual(o, [{a: 1}]);
293      assert.strictEqual(this, _, 'called with context');
294    }, _);
295  });
296
297  QUnit.test('detect', function(assert) {
298    assert.strictEqual(_.detect, _.find, 'is an alias for find');
299  });
300
301  QUnit.test('filter', function(assert) {
302    var evenArray = [1, 2, 3, 4, 5, 6];
303    var evenObject = {one: 1, two: 2, three: 3};
304    var isEven = function(num){ return num % 2 === 0; };
305
306    assert.deepEqual(_.filter(evenArray, isEven), [2, 4, 6]);
307    assert.deepEqual(_.filter(evenObject, isEven), [2], 'can filter objects');
308    assert.deepEqual(_.filter([{}, evenObject, []], 'two'), [evenObject], 'predicate string map to object properties');
309
310    _.filter([1], function() {
311      assert.equal(this, evenObject, 'given context');
312    }, evenObject);
313
314    // Can be used like _.where.
315    var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}];
316    assert.deepEqual(_.filter(list, {a: 1}), [{a: 1, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]);
317    assert.deepEqual(_.filter(list, {b: 2}), [{a: 1, b: 2}, {a: 2, b: 2}]);
318    assert.deepEqual(_.filter(list, {}), list, 'Empty object accepts all items');
319    assert.deepEqual(_(list).filter({}), list, 'OO-filter');
320  });
321
322  QUnit.test('select', function(assert) {
323    assert.strictEqual(_.select, _.filter, 'is an alias for filter');
324  });
325
326  QUnit.test('reject', function(assert) {
327    var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 === 0; });
328    assert.deepEqual(odds, [1, 3, 5], 'rejected each even number');
329
330    var context = 'obj';
331
332    var evens = _.reject([1, 2, 3, 4, 5, 6], function(num){
333      assert.equal(context, 'obj');
334      return num % 2 !== 0;
335    }, context);
336    assert.deepEqual(evens, [2, 4, 6], 'rejected each odd number');
337
338    assert.deepEqual(_.reject([odds, {one: 1, two: 2, three: 3}], 'two'), [odds], 'predicate string map to object properties');
339
340    // Can be used like _.where.
341    var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}];
342    assert.deepEqual(_.reject(list, {a: 1}), [{a: 2, b: 2}]);
343    assert.deepEqual(_.reject(list, {b: 2}), [{a: 1, b: 3}, {a: 1, b: 4}]);
344    assert.deepEqual(_.reject(list, {}), [], 'Returns empty list given empty object');
345    assert.deepEqual(_.reject(list, []), [], 'Returns empty list given empty array');
346  });
347
348  QUnit.test('every', function(assert) {
349    assert.ok(_.every([], _.identity), 'the empty set');
350    assert.ok(_.every([true, true, true], _.identity), 'every true values');
351    assert.notOk(_.every([true, false, true], _.identity), 'one false value');
352    assert.ok(_.every([0, 10, 28], function(num){ return num % 2 === 0; }), 'even numbers');
353    assert.notOk(_.every([0, 11, 28], function(num){ return num % 2 === 0; }), 'an odd number');
354    assert.strictEqual(_.every([1], _.identity), true, 'cast to boolean - true');
355    assert.strictEqual(_.every([0], _.identity), false, 'cast to boolean - false');
356    assert.notOk(_.every([void 0, void 0, void 0], _.identity), 'works with arrays of undefined');
357
358    var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}];
359    assert.notOk(_.every(list, {a: 1, b: 2}), 'Can be called with object');
360    assert.ok(_.every(list, 'a'), 'String mapped to object property');
361
362    list = [{a: 1, b: 2}, {a: 2, b: 2, c: true}];
363    assert.ok(_.every(list, {b: 2}), 'Can be called with object');
364    assert.notOk(_.every(list, 'c'), 'String mapped to object property');
365
366    assert.ok(_.every({a: 1, b: 2, c: 3, d: 4}, _.isNumber), 'takes objects');
367    assert.notOk(_.every({a: 1, b: 2, c: 3, d: 4}, _.isObject), 'takes objects');
368    assert.ok(_.every(['a', 'b', 'c', 'd'], _.hasOwnProperty, {a: 1, b: 2, c: 3, d: 4}), 'context works');
369    assert.notOk(_.every(['a', 'b', 'c', 'd', 'f'], _.hasOwnProperty, {a: 1, b: 2, c: 3, d: 4}), 'context works');
370  });
371
372  QUnit.test('all', function(assert) {
373    assert.strictEqual(_.all, _.every, 'is an alias for every');
374  });
375
376  QUnit.test('some', function(assert) {
377    assert.notOk(_.some([]), 'the empty set');
378    assert.notOk(_.some([false, false, false]), 'all false values');
379    assert.ok(_.some([false, false, true]), 'one true value');
380    assert.ok(_.some([null, 0, 'yes', false]), 'a string');
381    assert.notOk(_.some([null, 0, '', false]), 'falsy values');
382    assert.notOk(_.some([1, 11, 29], function(num){ return num % 2 === 0; }), 'all odd numbers');
383    assert.ok(_.some([1, 10, 29], function(num){ return num % 2 === 0; }), 'an even number');
384    assert.strictEqual(_.some([1], _.identity), true, 'cast to boolean - true');
385    assert.strictEqual(_.some([0], _.identity), false, 'cast to boolean - false');
386    assert.ok(_.some([false, false, true]));
387
388    var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}];
389    assert.notOk(_.some(list, {a: 5, b: 2}), 'Can be called with object');
390    assert.ok(_.some(list, 'a'), 'String mapped to object property');
391
392    list = [{a: 1, b: 2}, {a: 2, b: 2, c: true}];
393    assert.ok(_.some(list, {b: 2}), 'Can be called with object');
394    assert.notOk(_.some(list, 'd'), 'String mapped to object property');
395
396    assert.ok(_.some({a: '1', b: '2', c: '3', d: '4', e: 6}, _.isNumber), 'takes objects');
397    assert.notOk(_.some({a: 1, b: 2, c: 3, d: 4}, _.isObject), 'takes objects');
398    assert.ok(_.some(['a', 'b', 'c', 'd'], _.hasOwnProperty, {a: 1, b: 2, c: 3, d: 4}), 'context works');
399    assert.notOk(_.some(['x', 'y', 'z'], _.hasOwnProperty, {a: 1, b: 2, c: 3, d: 4}), 'context works');
400  });
401
402  QUnit.test('any', function(assert) {
403    assert.strictEqual(_.any, _.some, 'is an alias for some');
404  });
405
406  QUnit.test('includes', function(assert) {
407    _.each([null, void 0, 0, 1, NaN, {}, []], function(val) {
408      assert.strictEqual(_.includes(val, 'hasOwnProperty'), false);
409    });
410    assert.strictEqual(_.includes([1, 2, 3], 2), true, 'two is in the array');
411    assert.notOk(_.includes([1, 3, 9], 2), 'two is not in the array');
412
413    assert.strictEqual(_.includes([5, 4, 3, 2, 1], 5, true), true, 'doesn\'t delegate to binary search');
414
415    assert.strictEqual(_.includes({moe: 1, larry: 3, curly: 9}, 3), true, '_.includes on objects checks their values');
416    assert.ok(_([1, 2, 3]).includes(2), 'OO-style includes');
417
418    var numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3];
419    assert.strictEqual(_.includes(numbers, 1, 1), true, 'takes a fromIndex');
420    assert.strictEqual(_.includes(numbers, 1, -1), false, 'takes a fromIndex');
421    assert.strictEqual(_.includes(numbers, 1, -2), false, 'takes a fromIndex');
422    assert.strictEqual(_.includes(numbers, 1, -3), true, 'takes a fromIndex');
423    assert.strictEqual(_.includes(numbers, 1, 6), true, 'takes a fromIndex');
424    assert.strictEqual(_.includes(numbers, 1, 7), false, 'takes a fromIndex');
425
426    assert.ok(_.every([1, 2, 3], _.partial(_.includes, numbers)), 'fromIndex is guarded');
427  });
428
429  QUnit.test('include', function(assert) {
430    assert.strictEqual(_.include, _.includes, 'is an alias for includes');
431  });
432
433  QUnit.test('contains', function(assert) {
434    assert.strictEqual(_.contains, _.includes, 'is an alias for includes');
435
436  });
437
438  QUnit.test('includes with NaN', function(assert) {
439    assert.strictEqual(_.includes([1, 2, NaN, NaN], NaN), true, 'Expected [1, 2, NaN] to contain NaN');
440    assert.strictEqual(_.includes([1, 2, Infinity], NaN), false, 'Expected [1, 2, NaN] to contain NaN');
441  });
442
443  QUnit.test('includes with +- 0', function(assert) {
444    _.each([-0, +0], function(val) {
445      assert.strictEqual(_.includes([1, 2, val, val], val), true);
446      assert.strictEqual(_.includes([1, 2, val, val], -val), true);
447      assert.strictEqual(_.includes([-1, 1, 2], -val), false);
448    });
449  });
450
451
452  QUnit.test('invoke', function(assert) {
453    assert.expect(5);
454    var list = [[5, 1, 7], [3, 2, 1]];
455    var result = _.invoke(list, 'sort');
456    assert.deepEqual(result[0], [1, 5, 7], 'first array sorted');
457    assert.deepEqual(result[1], [1, 2, 3], 'second array sorted');
458
459    _.invoke([{
460      method: function() {
461        assert.deepEqual(_.toArray(arguments), [1, 2, 3], 'called with arguments');
462      }
463    }], 'method', 1, 2, 3);
464
465    assert.deepEqual(_.invoke([{a: null}, {}, {a: _.constant(1)}], 'a'), [null, void 0, 1], 'handles null & undefined');
466
467    assert.raises(function() {
468      _.invoke([{a: 1}], 'a');
469    }, TypeError, 'throws for non-functions');
470  });
471
472  QUnit.test('invoke w/ function reference', function(assert) {
473    var list = [[5, 1, 7], [3, 2, 1]];
474    var result = _.invoke(list, Array.prototype.sort);
475    assert.deepEqual(result[0], [1, 5, 7], 'first array sorted');
476    assert.deepEqual(result[1], [1, 2, 3], 'second array sorted');
477
478    assert.deepEqual(_.invoke([1, 2, 3], function(a) {
479      return a + this;
480    }, 5), [6, 7, 8], 'receives params from invoke');
481  });
482
483  // Relevant when using ClojureScript
484  QUnit.test('invoke when strings have a call method', function(assert) {
485    String.prototype.call = function() {
486      return 42;
487    };
488    var list = [[5, 1, 7], [3, 2, 1]];
489    var s = 'foo';
490    assert.equal(s.call(), 42, 'call function exists');
491    var result = _.invoke(list, 'sort');
492    assert.deepEqual(result[0], [1, 5, 7], 'first array sorted');
493    assert.deepEqual(result[1], [1, 2, 3], 'second array sorted');
494    delete String.prototype.call;
495    assert.equal(s.call, void 0, 'call function removed');
496  });
497
498  QUnit.test('pluck', function(assert) {
499    var people = [{name: 'moe', age: 30}, {name: 'curly', age: 50}];
500    assert.deepEqual(_.pluck(people, 'name'), ['moe', 'curly'], 'pulls names out of objects');
501    assert.deepEqual(_.pluck(people, 'address'), [void 0, void 0], 'missing properties are returned as undefined');
502    //compat: most flexible handling of edge cases
503    assert.deepEqual(_.pluck([{'[object Object]': 1}], {}), [1]);
504  });
505
506  QUnit.test('where', function(assert) {
507    var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}];
508    var result = _.where(list, {a: 1});
509    assert.equal(result.length, 3);
510    assert.equal(result[result.length - 1].b, 4);
511    result = _.where(list, {b: 2});
512    assert.equal(result.length, 2);
513    assert.equal(result[0].a, 1);
514    result = _.where(list, {});
515    assert.equal(result.length, list.length);
516
517    function test() {}
518    test.map = _.map;
519    assert.deepEqual(_.where([_, {a: 1, b: 2}, _], test), [_, _], 'checks properties given function');
520  });
521
522  QUnit.test('findWhere', function(assert) {
523    var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}, {a: 2, b: 4}];
524    var result = _.findWhere(list, {a: 1});
525    assert.deepEqual(result, {a: 1, b: 2});
526    result = _.findWhere(list, {b: 4});
527    assert.deepEqual(result, {a: 1, b: 4});
528
529    result = _.findWhere(list, {c: 1});
530    assert.ok(_.isUndefined(result), 'undefined when not found');
531
532    result = _.findWhere([], {c: 1});
533    assert.ok(_.isUndefined(result), 'undefined when searching empty list');
534
535    function test() {}
536    test.map = _.map;
537    assert.equal(_.findWhere([_, {a: 1, b: 2}, _], test), _, 'checks properties given function');
538
539    function TestClass() {
540      this.y = 5;
541      this.x = 'foo';
542    }
543    var expect = {c: 1, x: 'foo', y: 5};
544    assert.deepEqual(_.findWhere([{y: 5, b: 6}, expect], new TestClass()), expect, 'uses class instance properties');
545  });
546
547  QUnit.test('max', function(assert) {
548    assert.equal(-Infinity, _.max(null), 'can handle null/undefined');
549    assert.equal(-Infinity, _.max(void 0), 'can handle null/undefined');
550    assert.equal(-Infinity, _.max(null, _.identity), 'can handle null/undefined');
551
552    assert.equal(_.max([1, 2, 3]), 3, 'can perform a regular Math.max');
553
554    var neg = _.max([1, 2, 3], function(num){ return -num; });
555    assert.equal(neg, 1, 'can perform a computation-based max');
556
557    assert.equal(-Infinity, _.max({}), 'Maximum value of an empty object');
558    assert.equal(-Infinity, _.max([]), 'Maximum value of an empty array');
559    assert.equal(_.max({a: 'a'}), -Infinity, 'Maximum value of a non-numeric collection');
560
561    assert.equal(_.max(_.range(1, 300000)), 299999, 'Maximum value of a too-big array');
562
563    assert.equal(_.max([1, 2, 3, 'test']), 3, 'Finds correct max in array starting with num and containing a NaN');
564    assert.equal(_.max(['test', 1, 2, 3]), 3, 'Finds correct max in array starting with NaN');
565
566    assert.equal(_.max([1, 2, 3, null]), 3, 'Finds correct max in array starting with num and containing a `null`');
567    assert.equal(_.max([null, 1, 2, 3]), 3, 'Finds correct max in array starting with a `null`');
568
569    assert.equal(_.max([1, 2, 3, '']), 3, 'Finds correct max in array starting with num and containing an empty string');
570    assert.equal(_.max(['', 1, 2, 3]), 3, 'Finds correct max in array starting with an empty string');
571
572    assert.equal(_.max([1, 2, 3, false]), 3, 'Finds correct max in array starting with num and containing a false');
573    assert.equal(_.max([false, 1, 2, 3]), 3, 'Finds correct max in array starting with a false');
574
575    assert.equal(_.max([0, 1, 2, 3, 4]), 4, 'Finds correct max in array containing a zero');
576    assert.equal(_.max([-3, -2, -1, 0]), 0, 'Finds correct max in array containing negative numbers');
577
578    assert.deepEqual(_.map([[1, 2, 3], [4, 5, 6]], _.max), [3, 6], 'Finds correct max in array when mapping through multiple arrays');
579
580    var a = {x: -Infinity};
581    var b = {x: -Infinity};
582    var iterator = function(o){ return o.x; };
583    assert.equal(_.max([a, b], iterator), a, 'Respects iterator return value of -Infinity');
584
585    assert.deepEqual(_.max([{a: 1}, {a: 0, b: 3}, {a: 4}, {a: 2}], 'a'), {a: 4}, 'String keys use property iterator');
586
587    assert.deepEqual(_.max([0, 2], function(c){ return c * this.x; }, {x: 1}), 2, 'Iterator context');
588    assert.deepEqual(_.max([[1], [2, 3], [-1, 4], [5]], 0), [5], 'Lookup falsy iterator');
589    assert.deepEqual(_.max([{0: 1}, {0: 2}, {0: -1}, {a: 1}], 0), {0: 2}, 'Lookup falsy iterator');
590  });
591
592  QUnit.test('min', function(assert) {
593    assert.equal(_.min(null), Infinity, 'can handle null/undefined');
594    assert.equal(_.min(void 0), Infinity, 'can handle null/undefined');
595    assert.equal(_.min(null, _.identity), Infinity, 'can handle null/undefined');
596
597    assert.equal(_.min([1, 2, 3]), 1, 'can perform a regular Math.min');
598
599    var neg = _.min([1, 2, 3], function(num){ return -num; });
600    assert.equal(neg, 3, 'can perform a computation-based min');
601
602    assert.equal(_.min({}), Infinity, 'Minimum value of an empty object');
603    assert.equal(_.min([]), Infinity, 'Minimum value of an empty array');
604    assert.equal(_.min({a: 'a'}), Infinity, 'Minimum value of a non-numeric collection');
605
606    assert.deepEqual(_.map([[1, 2, 3], [4, 5, 6]], _.min), [1, 4], 'Finds correct min in array when mapping through multiple arrays');
607
608    var now = new Date(9999999999);
609    var then = new Date(0);
610    assert.equal(_.min([now, then]), then);
611
612    assert.equal(_.min(_.range(1, 300000)), 1, 'Minimum value of a too-big array');
613
614    assert.equal(_.min([1, 2, 3, 'test']), 1, 'Finds correct min in array starting with num and containing a NaN');
615    assert.equal(_.min(['test', 1, 2, 3]), 1, 'Finds correct min in array starting with NaN');
616
617    assert.equal(_.min([1, 2, 3, null]), 1, 'Finds correct min in array starting with num and containing a `null`');
618    assert.equal(_.min([null, 1, 2, 3]), 1, 'Finds correct min in array starting with a `null`');
619
620    assert.equal(_.min([0, 1, 2, 3, 4]), 0, 'Finds correct min in array containing a zero');
621    assert.equal(_.min([-3, -2, -1, 0]), -3, 'Finds correct min in array containing negative numbers');
622
623    var a = {x: Infinity};
624    var b = {x: Infinity};
625    var iterator = function(o){ return o.x; };
626    assert.equal(_.min([a, b], iterator), a, 'Respects iterator return value of Infinity');
627
628    assert.deepEqual(_.min([{a: 1}, {a: 0, b: 3}, {a: 4}, {a: 2}], 'a'), {a: 0, b: 3}, 'String keys use property iterator');
629
630    assert.deepEqual(_.min([0, 2], function(c){ return c * this.x; }, {x: -1}), 2, 'Iterator context');
631    assert.deepEqual(_.min([[1], [2, 3], [-1, 4], [5]], 0), [-1, 4], 'Lookup falsy iterator');
632    assert.deepEqual(_.min([{0: 1}, {0: 2}, {0: -1}, {a: 1}], 0), {0: -1}, 'Lookup falsy iterator');
633  });
634
635  QUnit.test('sortBy', function(assert) {
636    var people = [{name: 'curly', age: 50}, {name: 'moe', age: 30}];
637    people = _.sortBy(people, function(person){ return person.age; });
638    assert.deepEqual(_.pluck(people, 'name'), ['moe', 'curly'], 'stooges sorted by age');
639
640    var list = [void 0, 4, 1, void 0, 3, 2];
641    assert.deepEqual(_.sortBy(list, _.identity), [1, 2, 3, 4, void 0, void 0], 'sortBy with undefined values');
642
643    list = ['one', 'two', 'three', 'four', 'five'];
644    var sorted = _.sortBy(list, 'length');
645    assert.deepEqual(sorted, ['one', 'two', 'four', 'five', 'three'], 'sorted by length');
646
647    function Pair(x, y) {
648      this.x = x;
649      this.y = y;
650    }
651
652    var stableArray = [
653      new Pair(1, 1), new Pair(1, 2),
654      new Pair(1, 3), new Pair(1, 4),
655      new Pair(1, 5), new Pair(1, 6),
656      new Pair(2, 1), new Pair(2, 2),
657      new Pair(2, 3), new Pair(2, 4),
658      new Pair(2, 5), new Pair(2, 6),
659      new Pair(void 0, 1), new Pair(void 0, 2),
660      new Pair(void 0, 3), new Pair(void 0, 4),
661      new Pair(void 0, 5), new Pair(void 0, 6)
662    ];
663
664    var stableObject = _.object('abcdefghijklmnopqr'.split(''), stableArray);
665
666    var actual = _.sortBy(stableArray, function(pair) {
667      return pair.x;
668    });
669
670    assert.deepEqual(actual, stableArray, 'sortBy should be stable for arrays');
671    assert.deepEqual(_.sortBy(stableArray, 'x'), stableArray, 'sortBy accepts property string');
672
673    actual = _.sortBy(stableObject, function(pair) {
674      return pair.x;
675    });
676
677    assert.deepEqual(actual, stableArray, 'sortBy should be stable for objects');
678
679    list = ['q', 'w', 'e', 'r', 't', 'y'];
680    assert.deepEqual(_.sortBy(list), ['e', 'q', 'r', 't', 'w', 'y'], 'uses _.identity if iterator is not specified');
681  });
682
683  QUnit.test('groupBy', function(assert) {
684    var parity = _.groupBy([1, 2, 3, 4, 5, 6], function(num){ return num % 2; });
685    assert.ok('0' in parity && '1' in parity, 'created a group for each value');
686    assert.deepEqual(parity[0], [2, 4, 6], 'put each even number in the right group');
687
688    var list = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
689    var grouped = _.groupBy(list, 'length');
690    assert.deepEqual(grouped['3'], ['one', 'two', 'six', 'ten']);
691    assert.deepEqual(grouped['4'], ['four', 'five', 'nine']);
692    assert.deepEqual(grouped['5'], ['three', 'seven', 'eight']);
693
694    var context = {};
695    _.groupBy([{}], function(){ assert.strictEqual(this, context); }, context);
696
697    grouped = _.groupBy([4.2, 6.1, 6.4], function(num) {
698      return Math.floor(num) > 4 ? 'hasOwnProperty' : 'constructor';
699    });
700    assert.equal(grouped.constructor.length, 1);
701    assert.equal(grouped.hasOwnProperty.length, 2);
702
703    var array = [{}];
704    _.groupBy(array, function(value, index, obj){ assert.strictEqual(obj, array); });
705
706    array = [1, 2, 1, 2, 3];
707    grouped = _.groupBy(array);
708    assert.equal(grouped['1'].length, 2);
709    assert.equal(grouped['3'].length, 1);
710
711    var matrix = [
712      [1, 2],
713      [1, 3],
714      [2, 3]
715    ];
716    assert.deepEqual(_.groupBy(matrix, 0), {1: [[1, 2], [1, 3]], 2: [[2, 3]]});
717    assert.deepEqual(_.groupBy(matrix, 1), {2: [[1, 2]], 3: [[1, 3], [2, 3]]});
718  });
719
720  QUnit.test('indexBy', function(assert) {
721    var parity = _.indexBy([1, 2, 3, 4, 5], function(num){ return num % 2 === 0; });
722    assert.equal(parity['true'], 4);
723    assert.equal(parity['false'], 5);
724
725    var list = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
726    var grouped = _.indexBy(list, 'length');
727    assert.equal(grouped['3'], 'ten');
728    assert.equal(grouped['4'], 'nine');
729    assert.equal(grouped['5'], 'eight');
730
731    var array = [1, 2, 1, 2, 3];
732    grouped = _.indexBy(array);
733    assert.equal(grouped['1'], 1);
734    assert.equal(grouped['2'], 2);
735    assert.equal(grouped['3'], 3);
736  });
737
738  QUnit.test('countBy', function(assert) {
739    var parity = _.countBy([1, 2, 3, 4, 5], function(num){ return num % 2 === 0; });
740    assert.equal(parity['true'], 2);
741    assert.equal(parity['false'], 3);
742
743    var list = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
744    var grouped = _.countBy(list, 'length');
745    assert.equal(grouped['3'], 4);
746    assert.equal(grouped['4'], 3);
747    assert.equal(grouped['5'], 3);
748
749    var context = {};
750    _.countBy([{}], function(){ assert.strictEqual(this, context); }, context);
751
752    grouped = _.countBy([4.2, 6.1, 6.4], function(num) {
753      return Math.floor(num) > 4 ? 'hasOwnProperty' : 'constructor';
754    });
755    assert.equal(grouped.constructor, 1);
756    assert.equal(grouped.hasOwnProperty, 2);
757
758    var array = [{}];
759    _.countBy(array, function(value, index, obj){ assert.strictEqual(obj, array); });
760
761    array = [1, 2, 1, 2, 3];
762    grouped = _.countBy(array);
763    assert.equal(grouped['1'], 2);
764    assert.equal(grouped['3'], 1);
765  });
766
767  QUnit.test('shuffle', function(assert) {
768    assert.deepEqual(_.shuffle([1]), [1], 'behaves correctly on size 1 arrays');
769    var numbers = _.range(20);
770    var shuffled = _.shuffle(numbers);
771    assert.notDeepEqual(numbers, shuffled, 'does change the order'); // Chance of false negative: 1 in ~2.4*10^18
772    assert.notStrictEqual(numbers, shuffled, 'original object is unmodified');
773    assert.deepEqual(numbers, _.sortBy(shuffled), 'contains the same members before and after shuffle');
774
775    shuffled = _.shuffle({a: 1, b: 2, c: 3, d: 4});
776    assert.equal(shuffled.length, 4);
777    assert.deepEqual(shuffled.sort(), [1, 2, 3, 4], 'works on objects');
778  });
779
780  QUnit.test('sample', function(assert) {
781    assert.strictEqual(_.sample([1]), 1, 'behaves correctly when no second parameter is given');
782    assert.deepEqual(_.sample([1, 2, 3], -2), [], 'behaves correctly on negative n');
783    var numbers = _.range(10);
784    var allSampled = _.sample(numbers, 10).sort();
785    assert.deepEqual(allSampled, numbers, 'contains the same members before and after sample');
786    allSampled = _.sample(numbers, 20).sort();
787    assert.deepEqual(allSampled, numbers, 'also works when sampling more objects than are present');
788    assert.ok(_.contains(numbers, _.sample(numbers)), 'sampling a single element returns something from the array');
789    assert.strictEqual(_.sample([]), void 0, 'sampling empty array with no number returns undefined');
790    assert.notStrictEqual(_.sample([], 5), [], 'sampling empty array with a number returns an empty array');
791    assert.notStrictEqual(_.sample([1, 2, 3], 0), [], 'sampling an array with 0 picks returns an empty array');
792    assert.deepEqual(_.sample([1, 2], -1), [], 'sampling a negative number of picks returns an empty array');
793    assert.ok(_.contains([1, 2, 3], _.sample({a: 1, b: 2, c: 3})), 'sample one value from an object');
794    var partialSample = _.sample(_.range(1000), 10);
795    var partialSampleSorted = partialSample.sort();
796    assert.notDeepEqual(partialSampleSorted, _.range(10), 'samples from the whole array, not just the beginning');
797  });
798
799  QUnit.test('toArray', function(assert) {
800    assert.notOk(_.isArray(arguments), 'arguments object is not an array');
801    assert.ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array');
802    var a = [1, 2, 3];
803    assert.notStrictEqual(_.toArray(a), a, 'array is cloned');
804    assert.deepEqual(_.toArray(a), [1, 2, 3], 'cloned array contains same elements');
805
806    var numbers = _.toArray({one: 1, two: 2, three: 3});
807    assert.deepEqual(numbers, [1, 2, 3], 'object flattened into array');
808
809    var hearts = '\uD83D\uDC95';
810    var pair = hearts.split('');
811    var expected = [pair[0], hearts, '&', hearts, pair[1]];
812    assert.deepEqual(_.toArray(expected.join('')), expected, 'maintains astral characters');
813    assert.deepEqual(_.toArray(''), [], 'empty string into empty array');
814
815    if (typeof document != 'undefined') {
816      // test in IE < 9
817      var actual;
818      try {
819        actual = _.toArray(document.childNodes);
820      } catch (e) { /* ignored */ }
821      assert.deepEqual(actual, _.map(document.childNodes, _.identity), 'works on NodeList');
822    }
823  });
824
825  QUnit.test('size', function(assert) {
826    assert.equal(_.size({one: 1, two: 2, three: 3}), 3, 'can compute the size of an object');
827    assert.equal(_.size([1, 2, 3]), 3, 'can compute the size of an array');
828    assert.equal(_.size({length: 3, 0: 0, 1: 0, 2: 0}), 3, 'can compute the size of Array-likes');
829
830    var func = function() {
831      return _.size(arguments);
832    };
833
834    assert.equal(func(1, 2, 3, 4), 4, 'can test the size of the arguments object');
835
836    assert.equal(_.size('hello'), 5, 'can compute the size of a string literal');
837    assert.equal(_.size(new String('hello')), 5, 'can compute the size of string object');
838
839    assert.equal(_.size(null), 0, 'handles nulls');
840    assert.equal(_.size(0), 0, 'handles numbers');
841  });
842
843  QUnit.test('partition', function(assert) {
844    var list = [0, 1, 2, 3, 4, 5];
845    assert.deepEqual(_.partition(list, function(x) { return x < 4; }), [[0, 1, 2, 3], [4, 5]], 'handles bool return values');
846    assert.deepEqual(_.partition(list, function(x) { return x & 1; }), [[1, 3, 5], [0, 2, 4]], 'handles 0 and 1 return values');
847    assert.deepEqual(_.partition(list, function(x) { return x - 3; }), [[0, 1, 2, 4, 5], [3]], 'handles other numeric return values');
848    assert.deepEqual(_.partition(list, function(x) { return x > 1 ? null : true; }), [[0, 1], [2, 3, 4, 5]], 'handles null return values');
849    assert.deepEqual(_.partition(list, function(x) { if (x < 2) return true; }), [[0, 1], [2, 3, 4, 5]], 'handles undefined return values');
850    assert.deepEqual(_.partition({a: 1, b: 2, c: 3}, function(x) { return x > 1; }), [[2, 3], [1]], 'handles objects');
851
852    assert.deepEqual(_.partition(list, function(x, index) { return index % 2; }), [[1, 3, 5], [0, 2, 4]], 'can reference the array index');
853    assert.deepEqual(_.partition(list, function(x, index, arr) { return x === arr.length - 1; }), [[5], [0, 1, 2, 3, 4]], 'can reference the collection');
854
855    // Default iterator
856    assert.deepEqual(_.partition([1, false, true, '']), [[1, true], [false, '']], 'Default iterator');
857    assert.deepEqual(_.partition([{x: 1}, {x: 0}, {x: 1}], 'x'), [[{x: 1}, {x: 1}], [{x: 0}]], 'Takes a string');
858
859    // Context
860    var predicate = function(x){ return x === this.x; };
861    assert.deepEqual(_.partition([1, 2, 3], predicate, {x: 2}), [[2], [1, 3]], 'partition takes a context argument');
862
863    assert.deepEqual(_.partition([{a: 1}, {b: 2}, {a: 1, b: 2}], {a: 1}), [[{a: 1}, {a: 1, b: 2}], [{b: 2}]], 'predicate can be object');
864
865    var object = {a: 1};
866    _.partition(object, function(val, key, obj) {
867      assert.equal(val, 1);
868      assert.equal(key, 'a');
869      assert.equal(obj, object);
870      assert.equal(this, predicate);
871    }, predicate);
872  });
873
874  if (typeof document != 'undefined') {
875    QUnit.test('Can use various collection methods on NodeLists', function(assert) {
876      var parent = document.createElement('div');
877      parent.innerHTML = '<span id=id1></span>textnode<span id=id2></span>';
878
879      var elementChildren = _.filter(parent.childNodes, _.isElement);
880      assert.equal(elementChildren.length, 2);
881
882      assert.deepEqual(_.map(elementChildren, 'id'), ['id1', 'id2']);
883      assert.deepEqual(_.map(parent.childNodes, 'nodeType'), [1, 3, 1]);
884
885      assert.notOk(_.every(parent.childNodes, _.isElement));
886      assert.ok(_.some(parent.childNodes, _.isElement));
887
888      function compareNode(node) {
889        return _.isElement(node) ? node.id.charAt(2) : void 0;
890      }
891      assert.equal(_.max(parent.childNodes, compareNode), _.last(parent.childNodes));
892      assert.equal(_.min(parent.childNodes, compareNode), _.first(parent.childNodes));
893    });
894  }
895
896}());
897