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

Commit 2c8d87e

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 33c67ce commit 2c8d87e

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
@@ -474,6 +474,7 @@ describe('angular', function() {
474474
});
475475

476476
describe("extend", function() {
477+
477478
it('should not copy the private $$hashKey', function() {
478479
var src,dst;
479480
src = {};
@@ -484,6 +485,24 @@ describe('angular', function() {
484485
});
485486

486487

488+
it('should copy the properties of the source object onto the destination object', function() {
489+
var destination, source;
490+
destination = {};
491+
source = {foo: true};
492+
destination = extend(destination, source);
493+
expect(isDefined(destination.foo)).toBe(true);
494+
});
495+
496+
497+
it('ISSUE #4751 - should copy the length property of an object source to the destination object', function() {
498+
var destination, source;
499+
destination = {};
500+
source = {radius: 30, length: 0};
501+
destination = extend(destination, source);
502+
expect(isDefined(destination.length)).toBe(true);
503+
expect(isDefined(destination.radius)).toBe(true);
504+
});
505+
487506
it('should retain the previous $$hashKey', function() {
488507
var src,dst,h;
489508
src = {};
@@ -1048,6 +1067,42 @@ describe('angular', function() {
10481067
});
10491068
});
10501069

1070+
describe('isArrayLike', function() {
1071+
1072+
it('should return false if passed a number', function() {
1073+
expect(isArrayLike(10)).toBe(false);
1074+
});
1075+
1076+
it('should return true if passed an array', function() {
1077+
expect(isArrayLike([1,2,3,4])).toBe(true);
1078+
});
1079+
1080+
it('should return true if passed an object', function() {
1081+
expect(isArrayLike({0:"test", 1:"bob", 2:"tree", length:3})).toBe(true);
1082+
});
1083+
1084+
it('should return true if passed arguments object', function() {
1085+
function test(a,b,c) {
1086+
expect(isArrayLike(arguments)).toBe(true);
1087+
}
1088+
test(1,2,3);
1089+
});
1090+
1091+
it('should return true if passed a nodelist', function() {
1092+
var nodes = document.body.childNodes;
1093+
expect(isArrayLike(nodes)).toBe(true);
1094+
});
1095+
1096+
it('should return false for objects with `length` but no matching indexable items', function() {
1097+
var obj = {
1098+
a: 'a',
1099+
b:'b',
1100+
length: 10
1101+
};
1102+
expect(isArrayLike(obj)).toBe(false);
1103+
});
1104+
});
1105+
10511106

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