Skip to content

Commit d188bc7

Browse files
jackcvierspetebacondarwin
authored andcommitted
fix(Angular.js): fix isArrayLike for unusual cases
Closes angular#10186 Closes angular#8000 Closes angular#4855 Closes angular#4751 Closes angular#10272
1 parent ad4296d commit d188bc7

File tree

3 files changed

+81
-11
lines changed

3 files changed

+81
-11
lines changed

src/Angular.js

+41-11
Original file line numberDiff line numberDiff line change
@@ -198,20 +198,50 @@ msie = document.documentMode;
198198
* String ...)
199199
*/
200200
function isArrayLike(obj) {
201-
if (obj == null || isWindow(obj)) {
202-
return false;
203-
}
204-
201+
// snake case is to avoid shadowing camel-cased globals
202+
var length, objExists, isNodeList, isArguments, isSomeOtherObj, is_array, is_string, is_object;
203+
objExists = isDefined(obj) && obj !== null;
205204
// Support: iOS 8.2 (not reproducible in simulator)
206205
// "length" in obj used to prevent JIT error (gh-11508)
207-
var length = "length" in Object(obj) && obj.length;
208-
209-
if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
210-
return true;
211-
}
206+
length = objExists ? "length" in Object(obj) && obj.length : false;
207+
is_array = isArray(obj);
208+
is_string = isString(obj);
209+
is_object = isObject(obj);
210+
isNodeList = objExists && obj.nodeType === 1 && length;
211+
isArguments = objExists &&
212+
(Object.prototype.toString.call(obj) === '[object Arguments]' ||
213+
(Object.prototype.hasOwnProperty.call(obj, 'length') &&
214+
Object.prototype.hasOwnProperty.call(obj, 'callee')));
215+
216+
// this only works if it doesn't return 'object' from typeof and isn't another arrayLike
217+
isSomeOtherObj = objExists &&
218+
!isNodeList &&
219+
!is_array &&
220+
!is_string &&
221+
!isArguments &&
222+
(
223+
(!is_object &&
224+
length === 0) ||
225+
(
226+
isNumber(length) &&
227+
length >= 0 &&
228+
(length - 1) in obj
229+
)
230+
);
212231

213-
return isString(obj) || isArray(obj) || length === 0 ||
214-
typeof length === 'number' && length > 0 && (length - 1) in obj;
232+
return (
233+
objExists &&
234+
!isWindow(obj) &&
235+
(
236+
(
237+
isNodeList ||
238+
is_string ||
239+
is_array ||
240+
isArguments
241+
) ||
242+
isSomeOtherObj
243+
)
244+
);
215245
}
216246

217247
/**

test/AngularSpec.js

+31
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ describe('angular', function() {
461461
});
462462

463463
describe("extend", function() {
464+
464465
it('should not copy the private $$hashKey', function() {
465466
var src,dst;
466467
src = {};
@@ -471,6 +472,24 @@ describe('angular', function() {
471472
});
472473

473474

475+
it('should copy the properties of the source object onto the destination object', function() {
476+
var destination, source;
477+
destination = {};
478+
source = {foo: true};
479+
destination = extend(destination, source);
480+
expect(isDefined(destination.foo)).toBe(true);
481+
});
482+
483+
484+
it('ISSUE #4751 - should copy the length property of an object source to the destination object', function() {
485+
var destination, source;
486+
destination = {};
487+
source = {radius: 30, length: 0};
488+
destination = extend(destination, source);
489+
expect(isDefined(destination.length)).toBe(true);
490+
expect(isDefined(destination.radius)).toBe(true);
491+
});
492+
474493
it('should retain the previous $$hashKey', function() {
475494
var src,dst,h;
476495
src = {};
@@ -1035,6 +1054,18 @@ describe('angular', function() {
10351054
});
10361055
});
10371056

1057+
describe('isArrayLike', function() {
1058+
it('should return false if passed a number', function() {
1059+
expect(isArrayLike(10)).toBe(false);
1060+
});
1061+
it('should return true if passed an array', function() {
1062+
expect(isArrayLike([1,2,3,4])).toBe(true);
1063+
});
1064+
it('should return true if passed an object', function() {
1065+
expect(isArrayLike({0:"test", 1:"bob", 2:"tree", length:3})).toBe(true);
1066+
});
1067+
});
1068+
10381069

10391070
describe('forEach', function() {
10401071
it('should iterate over *own* object properties', function() {

test/ng/directive/ngRepeatSpec.js

+9
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,15 @@ describe('ngRepeat', function() {
612612
expect(element.text()).toEqual('misko:m:0|shyam:s:1|frodo:f:2|');
613613
});
614614

615+
it('should expose iterator offset as $index when iterating over objects with length key value 0', function() {
616+
element = $compile(
617+
'<ul>' +
618+
'<li ng-repeat="(key, val) in items">{{key}}:{{val}}:{{$index}}|</li>' +
619+
'</ul>')(scope);
620+
scope.items = {'misko':'m', 'shyam':'s', 'frodo':'f', 'length':0};
621+
scope.$digest();
622+
expect(element.text()).toEqual('misko:m:0|shyam:s:1|frodo:f:2|length:0:3|');
623+
});
615624

616625
it('should expose iterator position as $first, $middle and $last when iterating over arrays',
617626
function() {

0 commit comments

Comments
 (0)