Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 70edec9

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

File tree

3 files changed

+78
-10
lines changed

3 files changed

+78
-10
lines changed

src/Angular.js

+14-10
Original file line numberDiff line numberDiff line change
@@ -191,27 +191,31 @@ var
191191
msie = document.documentMode;
192192

193193

194+
function isNodeList(obj) {
195+
return typeof obj.length == 'number' &&
196+
typeof obj.item == 'function';
197+
}
198+
194199
/**
195200
* @private
196201
* @param {*} obj
197202
* @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
198203
* String ...)
199204
*/
200205
function isArrayLike(obj) {
201-
if (obj == null || isWindow(obj)) {
202-
return false;
203-
}
206+
207+
// `null`, `undefined` and `window` are not array-like
208+
if (obj == null || isWindow(obj)) return false;
209+
210+
// arrays and strings are array like
211+
if (isArray(obj) || isString(obj)) return true;
204212

205213
// Support: iOS 8.2 (not reproducible in simulator)
206214
// "length" in obj used to prevent JIT error (gh-11508)
207215
var length = "length" in Object(obj) && obj.length;
208216

209-
if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
210-
return true;
211-
}
212-
213-
return isString(obj) || isArray(obj) || length === 0 ||
214-
typeof length === 'number' && length > 0 && (length - 1) in obj;
217+
// node lists and objects with suitable length characteristics are array-like
218+
return (isNumber(length) && length >= 0 && (length - 1) in obj) || isNodeList(obj);
215219
}
216220

217221
/**
@@ -471,7 +475,7 @@ identity.$inject = [];
471475
function valueFn(value) {return function() {return value;};}
472476

473477
function hasCustomToString(obj) {
474-
return isFunction(obj.toString) && obj.toString !== Object.prototype.toString;
478+
return isFunction(obj.toString) && obj.toString !== toString;
475479
}
476480

477481

test/AngularSpec.js

+55
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,42 @@ describe('angular', function() {
10351054
});
10361055
});
10371056

1057+
describe('isArrayLike', function() {
1058+
1059+
it('should return false if passed a number', function() {
1060+
expect(isArrayLike(10)).toBe(false);
1061+
});
1062+
1063+
it('should return true if passed an array', function() {
1064+
expect(isArrayLike([1,2,3,4])).toBe(true);
1065+
});
1066+
1067+
it('should return true if passed an object', function() {
1068+
expect(isArrayLike({0:"test", 1:"bob", 2:"tree", length:3})).toBe(true);
1069+
});
1070+
1071+
it('should return true if passed arguments object', function() {
1072+
function test(a,b,c) {
1073+
expect(isArrayLike(arguments)).toBe(true);
1074+
}
1075+
test(1,2,3);
1076+
});
1077+
1078+
it('should return true if passed a nodelist', function() {
1079+
var nodes = document.body.childNodes;
1080+
expect(isArrayLike(nodes)).toBe(true);
1081+
});
1082+
1083+
it('should return false for objects with `length` but no matching indexable items', function() {
1084+
var obj = {
1085+
a: 'a',
1086+
b:'b',
1087+
length: 10
1088+
};
1089+
expect(isArrayLike(obj)).toBe(false);
1090+
});
1091+
});
1092+
10381093

10391094
describe('forEach', function() {
10401095
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)