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