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

Commit

Permalink
fix(Angular.js): fix isArrayLike for unusual cases
Browse files Browse the repository at this point in the history
Closes #10186
Closes #8000
Closes #4855
Closes #4751
Closes #10272
  • Loading branch information
jackcviers authored and petebacondarwin committed Oct 25, 2015
1 parent 33c67ce commit 2c8d87e
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 10 deletions.
24 changes: 14 additions & 10 deletions src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,27 +191,31 @@ var
msie = document.documentMode;


function isNodeList(obj) {
return typeof obj.length == 'number' &&
typeof obj.item == 'function';
}

/**
* @private
* @param {*} obj
* @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
* String ...)
*/
function isArrayLike(obj) {
if (obj == null || isWindow(obj)) {
return false;
}

// `null`, `undefined` and `window` are not array-like
if (obj == null || isWindow(obj)) return false;

// arrays and strings are array like
if (isArray(obj) || isString(obj)) return true;

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

if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
return true;
}

return isString(obj) || isArray(obj) || length === 0 ||
typeof length === 'number' && length > 0 && (length - 1) in obj;
// node lists and objects with suitable length characteristics are array-like
return (isNumber(length) && length >= 0 && (length - 1) in obj) || isNodeList(obj);
}

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

function hasCustomToString(obj) {
return isFunction(obj.toString) && obj.toString !== Object.prototype.toString;
return isFunction(obj.toString) && obj.toString !== toString;
}


Expand Down
55 changes: 55 additions & 0 deletions test/AngularSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ describe('angular', function() {
});

describe("extend", function() {

it('should not copy the private $$hashKey', function() {
var src,dst;
src = {};
Expand All @@ -484,6 +485,24 @@ describe('angular', function() {
});


it('should copy the properties of the source object onto the destination object', function() {
var destination, source;
destination = {};
source = {foo: true};
destination = extend(destination, source);
expect(isDefined(destination.foo)).toBe(true);
});


it('ISSUE #4751 - should copy the length property of an object source to the destination object', function() {
var destination, source;
destination = {};
source = {radius: 30, length: 0};
destination = extend(destination, source);
expect(isDefined(destination.length)).toBe(true);
expect(isDefined(destination.radius)).toBe(true);
});

it('should retain the previous $$hashKey', function() {
var src,dst,h;
src = {};
Expand Down Expand Up @@ -1048,6 +1067,42 @@ describe('angular', function() {
});
});

describe('isArrayLike', function() {

it('should return false if passed a number', function() {
expect(isArrayLike(10)).toBe(false);
});

it('should return true if passed an array', function() {
expect(isArrayLike([1,2,3,4])).toBe(true);
});

it('should return true if passed an object', function() {
expect(isArrayLike({0:"test", 1:"bob", 2:"tree", length:3})).toBe(true);
});

it('should return true if passed arguments object', function() {
function test(a,b,c) {
expect(isArrayLike(arguments)).toBe(true);
}
test(1,2,3);
});

it('should return true if passed a nodelist', function() {
var nodes = document.body.childNodes;
expect(isArrayLike(nodes)).toBe(true);
});

it('should return false for objects with `length` but no matching indexable items', function() {
var obj = {
a: 'a',
b:'b',
length: 10
};
expect(isArrayLike(obj)).toBe(false);
});
});


describe('forEach', function() {
it('should iterate over *own* object properties', function() {
Expand Down
9 changes: 9 additions & 0 deletions test/ng/directive/ngRepeatSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,15 @@ describe('ngRepeat', function() {
expect(element.text()).toEqual('misko:m:0|shyam:s:1|frodo:f:2|');
});

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

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

0 comments on commit 2c8d87e

Please sign in to comment.