diff --git a/src/Angular.js b/src/Angular.js index ff18c96c..f449e1b9 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -210,9 +210,9 @@ function isArrayLike(obj) { * * @description * Invokes the `iterator` function once for each item in `obj` collection, which can be either an - * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value` - * is the value of an object property or an array element and `key` is the object property key or - * array element index. Specifying a `context` for the function is optional. + * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value` + * is the value of an object property or an array element, `key` is the object property key or + * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional. * * It is worth noting that `.forEach` does not iterate over inherited properties because it filters * using the `hasOwnProperty` method. @@ -240,22 +240,22 @@ function forEach(obj, iterator, context) { // Need to check if hasOwnProperty exists, // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { - iterator.call(context, obj[key], key); + iterator.call(context, obj[key], key, obj); } } } else if (isArray(obj) || isArrayLike(obj)) { var isPrimitive = typeof obj !== 'object'; for (key = 0, length = obj.length; key < length; key++) { if (isPrimitive || key in obj) { - iterator.call(context, obj[key], key); + iterator.call(context, obj[key], key, obj); } } } else if (obj.forEach && obj.forEach !== forEach) { - obj.forEach(iterator, context); + obj.forEach(iterator, context, obj); } else { for (key in obj) { if (obj.hasOwnProperty(key)) { - iterator.call(context, obj[key], key); + iterator.call(context, obj[key], key, obj); } } } diff --git a/test/AngularSpec.js b/test/AngularSpec.js index 42b2bbc4..bece0ef3 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -654,10 +654,79 @@ describe('angular', function() { var log = []; var collection = []; collection[5] = 'SPARSE'; - forEach(collection, function (item, index) { log.push(item + index); }); + forEach(collection, function (item, index) { + log.push(item + index); + }); expect(log.length).toBe(1); expect(log[0]).toBe('SPARSE5'); }); + + + describe('ES spec api compliance', function() { + + function testForEachSpec(expectedSize, collection) { + var that = {}; + + forEach(collection, function(value, key, collectionArg) { + expect(collectionArg).toBe(collection); + expect(collectionArg[key]).toBe(value); + + expect(this).toBe(that); + + expectedSize--; + }, that); + + expect(expectedSize).toBe(0); + } + + + it('should follow the ES spec when called with array', function() { + testForEachSpec(2, [1,2]); + }); + + + it('should follow the ES spec when called with arguments', function() { + testForEachSpec(2, (function(){ return arguments; }(1,2))); + }); + + + it('should follow the ES spec when called with string', function() { + testForEachSpec(2, '12'); + }); + + + it('should follow the ES spec when called with jQuery/jqLite', function() { + testForEachSpec(2, jqLite("ab")); + }); + + + it('should follow the ES spec when called with childNodes NodeList', function() { + testForEachSpec(2, jqLite("

ab

")[0].childNodes); + }); + + + it('should follow the ES spec when called with getElementsByTagName HTMLCollection', function() { + testForEachSpec(2, jqLite("

ab

")[0].getElementsByTagName("*")); + }); + + + it('should follow the ES spec when called with querySelectorAll HTMLCollection', function() { + testForEachSpec(2, jqLite("

ab

")[0].querySelectorAll("*")); + }); + + + it('should follow the ES spec when called with JSON', function() { + testForEachSpec(2, {a: 1, b: 2}); + }); + + + it('should follow the ES spec when called with function', function() { + function f(){} + f.a = 1; + f.b = 2; + testForEachSpec(2, f); + }); + }); });