aboutsummaryrefslogtreecommitdiffstats
path: root/test/ng/directive
diff options
context:
space:
mode:
authorIgor Minar2012-11-23 16:08:47 +0100
committerIgor Minar2012-11-26 20:39:10 +0100
commit6e2c38f54dc012dc919cdd7f7ab9b73dc1e99c66 (patch)
tree96c03d2b961feb5d3b9978538a1ac7e437a0184c /test/ng/directive
parent00e7e3141819e14ced3279fa5dfb8eba4ff89e5f (diff)
downloadangular.js-6e2c38f54dc012dc919cdd7f7ab9b73dc1e99c66.tar.bz2
test(ngRepeat): clean up and improve tests
Diffstat (limited to 'test/ng/directive')
-rw-r--r--test/ng/directive/ngRepeatSpec.js
'use strict';
// NOTE:  The usage of window and document instead of $window and $document here is
// deliberate.  This service depends on the specific behavior of anchor nodes created by the
// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and
// cause us to break tests.  In addition, when the browser resolves a URL for XHR, it
// doesn't know about mocked locations and resolves URLs to the real document - which is
// exactly the behavior needed here.  There is little value is mocking these out for this
// service.
var urlParsingNode = document.createElement("a");
var originUrl = urlResolve(window.location.href, true);

/**
 *
 * Implementation Notes for non-IE browsers
 * ----------------------------------------
 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM,
 * results both in the normalizing and parsing of the URL.  Normalizing means that a relative
 * URL will be resolved into an absolute URL in the context of the application document.
 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related
 * properties are all populated to reflect the normalized URL.  This approach has wide
 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc.  See
 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
 *
 * Implementation Notes for IE
 * ---------------------------
 * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other
 * browsers.  However, the parsed components will not be set if the URL assigned did not specify
 * them.  (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.)  We
 * work around that by performing the parsing in a 2nd step by taking a previously normalized
 * URL (e.g. by assining to a.href) and assigning it a.href again.  This correctly populates the
 * properties such as protocol, hostname, port, etc.
 *
 * IE7 does not normalize the URL when assigned to an anchor node.  (Apparently, it does, if one
 * uses the inner HTML approach to assign the URL as part of an HTML snippet -
 * http://stackoverflow.com/a/472729)  However, setting img[src] does normalize the URL.
 * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception.
 * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that
 * method and IE < 8 is unsupported.
 *
 * References:
 *   http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
 *   http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
 *   http://url.spec.whatwg.org/#urlutils
 *   https://github.com/angular/angular.js/pull/2902
 *   http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
 *
 * @function
 * @param {string} url The URL to be parsed.
 * @description Normalizes and parses a URL.
 * @returns {object} Returns the normalized URL as a dictionary.
 *
 *   | member name   | Description    |
 *   |---------------|----------------|
 *   | href          | A normalized version of the provided URL if it was not an absolute URL |
 *   | protocol      | The protocol including the trailing colon                              |
 *   | host          | The host and port (if the port is non-default) of the normalizedUrl    |
 *   | search        | The search params, minus the question mark                             |
 *   | hash          | The hash string, minus the hash symbol
 *   | hostname      | The hostname
 *   | port          | The port, without ":"
 *   | pathname      | The pathname, beginning with "/"
 *
 */
function urlResolve(url) {
  var href = url;
  if (msie) {
    // Normalize before parse.  Refer Implementation Notes on why this is
    // done in two steps on IE.
    urlParsingNode.setAttribute("href", href);
    href = urlParsingNode.href;
  }

  urlParsingNode.setAttribute('href', href);

  // $$urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
  return {
    href: urlParsingNode.href,
    protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
    host: urlParsingNode.host,
    search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
    hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
    hostname: urlParsingNode.hostname,
    port: urlParsingNode.port,
    pathname: urlParsingNode.pathname && urlParsingNode.pathname.charAt(0) === '/' ?
        urlParsingNode.pathname : '/' + urlParsingNode.pathname
  };
}


/**
 * Parse a request URL and determine whether this is a same-origin request as the application document.
 *
 * @param {string|object} requestUrl The url of the request as a string that will be resolved
 * or a parsed URL object.
 * @returns {boolean} Whether the request is for the same origin as the application document.
 */
function urlIsSameOrigin(requestUrl) {
  var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl;
  return (parsed.protocol === originUrl.protocol &&
          parsed.host === originUrl.host);
}
v>
- $rootScope.items = {misko: true, shyam: true, zhenbo:true};
- $rootScope.$digest();
+ scope.items = {misko: true, shyam: true, zhenbo:true};
+ scope.$digest();
expect(element.find('li').length).toEqual(3);
expect(element.text()).toEqual('misko:true;shyam:true;zhenbo:true;');
@@ -166,216 +173,214 @@ describe('ngRepeat', function() {
expect(element.find('input')[1].checked).toBe(false);
expect(element.find('input')[2].checked).toBe(true);
- $rootScope.items = {misko: false, shyam: true, zhenbo: true};
- $rootScope.$digest();
+ scope.items = {misko: false, shyam: true, zhenbo: true};
+ scope.$digest();
expect(element.text()).toEqual('misko:false;shyam:true;zhenbo:true;');
expect(element.find('input')[0].checked).toBe(false);
expect(element.find('input')[1].checked).toBe(true);
expect(element.find('input')[2].checked).toBe(true);
- }));
+ });
- it('should not ngRepeat over parent properties', inject(function($rootScope, $compile) {
+ it('should not ngRepeat over parent properties', function() {
var Class = function() {};
Class.prototype.abc = function() {};
Class.prototype.value = 'abc';
element = $compile(
'<ul>' +
- '<li ng-repeat="(key, value) in items" ng-bind="key + \':\' + value + \';\' "></li>' +
- '</ul>')($rootScope);
- $rootScope.items = new Class();
- $rootScope.items.name = 'value';
- $rootScope.$digest();
+ '<li ng-repeat="(key, value) in items">{{key}}:{{value}};</li>' +
+ '</ul>')(scope);
+ scope.items = new Class();
+ scope.items.name = 'value';
+ scope.$digest();
expect(element.text()).toEqual('name:value;');
- }));
+ });
- it('should error on wrong parsing of ngRepeat', inject(function($rootScope, $compile) {
+ it('should error on wrong parsing of ngRepeat', function() {
expect(function() {
- element = $compile('<ul><li ng-repeat="i dont parse"></li></ul>')($rootScope);
+ element = jqLite('<ul><li ng-repeat="i dont parse"></li></ul>');
+ $compile(element)(scope);
}).toThrow("Expected ngRepeat in form of '_item_ in _collection_' but got 'i dont parse'.");
- }));
+ });
- it("should throw error when left-hand-side of ngRepeat can't be parsed", inject(
- function($rootScope, $compile) {
+ it("should throw error when left-hand-side of ngRepeat can't be parsed", function() {
expect(function() {
- element = $compile('<ul><li ng-repeat="i dont parse in foo"></li></ul>')($rootScope);
+ element = jqLite('<ul><li ng-repeat="i dont parse in foo"></li></ul>');
+ $compile(element)(scope);
}).toThrow("'item' in 'item in collection' should be identifier or (key, value) but got " +
"'i dont parse'.");
- }));
+ });
it('should expose iterator offset as $index when iterating over arrays',
- inject(function($rootScope, $compile) {
+ function() {
element = $compile(
'<ul>' +
- '<li ng-repeat="item in items" ng-bind="item + $index + \'|\'"></li>' +
- '</ul>')($rootScope);
- $rootScope.items = ['misko', 'shyam', 'frodo'];
- $rootScope.$digest();
- expect(element.text()).toEqual('misko0|shyam1|frodo2|');
- }));
+ '<li ng-repeat="item in items">{{item}}:{{$index}}|</li>' +
+ '</ul>')(scope);
+ scope.items = ['misko', 'shyam', 'frodo'];
+ scope.$digest();
+ expect(element.text()).toEqual('misko:0|shyam:1|frodo:2|');
+ });
- it('should expose iterator offset as $index when iterating over objects',
- inject(function($rootScope, $compile) {
+ it('should expose iterator offset as $index when iterating over objects', function() {
element = $compile(
'<ul>' +
- '<li ng-repeat="(key, val) in items" ng-bind="key + \':\' + val + $index + \'|\'"></li>' +
- '</ul>')($rootScope);
- $rootScope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'};
- $rootScope.$digest();
- expect(element.text()).toEqual('frodo:f0|misko:m1|shyam:s2|');
- }));
+ '<li ng-repeat="(key, val) in items">{{key}}:{{val}}:{{$index}}|</li>' +
+ '</ul>')(scope);
+ scope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'};
+ scope.$digest();
+ expect(element.text()).toEqual('frodo:f:0|misko:m:1|shyam:s:2|');
+ });
it('should expose iterator position as $first, $middle and $last when iterating over arrays',
- inject(function($rootScope, $compile) {
+ function() {
element = $compile(
'<ul>' +
'<li ng-repeat="item in items">{{item}}:{{$first}}-{{$middle}}-{{$last}}|</li>' +
- '</ul>')($rootScope);
- $rootScope.items = ['misko', 'shyam', 'doug'];
- $rootScope.$digest();
+ '</ul>')(scope);
+ scope.items = ['misko', 'shyam', 'doug'];
+ scope.$digest();
expect(element.text()).
toEqual('misko:true-false-false|shyam:false-true-false|doug:false-false-true|');
- $rootScope.items.push('frodo');
- $rootScope.$digest();
+ scope.items.push('frodo');
+ scope.$digest();
expect(element.text()).
toEqual('misko:true-false-false|' +
'shyam:false-true-false|' +
'doug:false-true-false|' +
'frodo:false-false-true|');
- $rootScope.items.pop();
- $rootScope.items.pop();
- $rootScope.$digest();
+ scope.items.pop();
+ scope.items.pop();
+ scope.$digest();
expect(element.text()).toEqual('misko:true-false-false|shyam:false-false-true|');
- $rootScope.items.pop();
- $rootScope.$digest();
+ scope.items.pop();
+ scope.$digest();
expect(element.text()).toEqual('misko:true-false-true|');
- }));
+ });
it('should expose iterator position as $first, $middle and $last when iterating over objects',
- inject(function($rootScope, $compile) {
+ function() {
element = $compile(
'<ul>' +
'<li ng-repeat="(key, val) in items">{{key}}:{{val}}:{{$first}}-{{$middle}}-{{$last}}|</li>' +
- '</ul>')($rootScope);
- $rootScope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'};
- $rootScope.$digest();
+ '</ul>')(scope);
+ scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'};
+ scope.$digest();
expect(element.text()).
toEqual('doug:d:true-false-false|' +
'frodo:f:false-true-false|' +
'misko:m:false-true-false|' +
'shyam:s:false-false-true|');
- delete $rootScope.items.doug;
- delete $rootScope.items.frodo;
- $rootScope.$digest();
+ delete scope.items.doug;
+ delete scope.items.frodo;
+ scope.$digest();
expect(element.text()).toEqual('misko:m:true-false-false|shyam:s:false-false-true|');
- delete $rootScope.items.shyam;
- $rootScope.$digest();
+ delete scope.items.shyam;
+ scope.$digest();
expect(element.text()).toEqual('misko:m:true-false-true|');
- }));
+ });
- it('should ignore $ and $$ properties', inject(function($rootScope, $compile) {
- element = $compile('<ul><li ng-repeat="i in items">{{i}}|</li></ul>')($rootScope);
- $rootScope.items = ['a', 'b', 'c'];
- $rootScope.items.$$hashkey = 'xxx';
- $rootScope.items.$root = 'yyy';
- $rootScope.$digest();
+ it('should ignore $ and $$ properties', function() {
+ element = $compile('<ul><li ng-repeat="i in items">{{i}}|</li></ul>')(scope);
+ scope.items = ['a', 'b', 'c'];
+ scope.items.$$hashkey = 'xxx';
+ scope.items.$root = 'yyy';
+ scope.$digest();
expect(element.text()).toEqual('a|b|c|');
- }));
+ });
- it('should repeat over nested arrays', inject(function($rootScope, $compile) {
+ it('should repeat over nested arrays', function() {
element = $compile(
'<ul>' +
'<li ng-repeat="subgroup in groups">' +
'<div ng-repeat="group in subgroup">{{group}}|</div>X' +
'</li>' +
- '</ul>')($rootScope);
- $rootScope.groups = [['a', 'b'], ['c','d']];
- $rootScope.$digest();
+ '</ul>')(scope);
+ scope.groups = [['a', 'b'], ['c','d']];
+ scope.$digest();
expect(element.text()).toEqual('a|b|Xc|d|X');
- }));
+ });
- it('should ignore non-array element properties when iterating over an array',
- inject(function($rootScope, $compile) {
- element = $compile('<ul><li ng-repeat="item in array">{{item}}|</li></ul>')($rootScope);
- $rootScope.array = ['a', 'b', 'c'];
- $rootScope.array.foo = '23';
- $rootScope.array.bar = function() {};
- $rootScope.$digest();
+ it('should ignore non-array element properties when iterating over an array', function() {
+ element = $compile('<ul><li ng-repeat="item in array">{{item}}|</li></ul>')(scope);
+ scope.array = ['a', 'b', 'c'];
+ scope.array.foo = '23';
+ scope.array.bar = function() {};
+ scope.$digest();
expect(element.text()).toBe('a|b|c|');
- }));
+ });
- it('should iterate over non-existent elements of a sparse array',
- inject(function($rootScope, $compile) {
- element = $compile('<ul><li ng-repeat="item in array">{{item}}|</li></ul>')($rootScope);
- $rootScope.array = ['a', 'b'];
- $rootScope.array[4] = 'c';
- $rootScope.array[6] = 'd';
- $rootScope.$digest();
+ it('should iterate over non-existent elements of a sparse array', function() {
+ element = $compile('<ul><li ng-repeat="item in array">{{item}}|</li></ul>')(scope);
+ scope.array = ['a', 'b'];
+ scope.array[4] = 'c';
+ scope.array[6] = 'd';
+ scope.$digest();
expect(element.text()).toBe('a|b|||c||d|');
- }));
+ });
- it('should iterate over all kinds of types', inject(function($rootScope, $compile) {
- element = $compile('<ul><li ng-repeat="item in array">{{item}}|</li></ul>')($rootScope);
- $rootScope.array = ['a', 1, null, undefined, {}];
- $rootScope.$digest();
+ it('should iterate over all kinds of types', function() {
+ element = $compile('<ul><li ng-repeat="item in array">{{item}}|</li></ul>')(scope);
+ scope.array = ['a', 1, null, undefined, {}];
+ scope.$digest();
expect(element.text()).toMatch(/a\|1\|\|\|\{\s*\}\|/);
- }));
+ });
describe('stability', function() {
var a, b, c, d, lis;
- beforeEach(inject(function($rootScope, $compile) {
+ beforeEach(function() {
element = $compile(
'<ul>' +
'<li ng-repeat="item in items">{{key}}:{{val}}|></li>' +
- '</ul>')($rootScope);
+ '</ul>')(scope);
a = {};
b = {};
c = {};
d = {};
- $rootScope.items = [a, b, c];
- $rootScope.$digest();
+ scope.items = [a, b, c];
+ scope.$digest();
lis = element.find('li');
- }));
+ });
- it('should preserve the order of elements', inject(function($rootScope) {
- $rootScope.items = [a, c, d];
- $rootScope.$digest();
+ it('should preserve the order of elements', function() {
+ scope.items = [a, c, d];
+ scope.$digest();
var newElements = element.find('li');
expect(newElements[0]).toEqual(lis[0]);
expect(newElements[1]).toEqual(lis[2]);
expect(newElements[2]).not.toEqual(lis[1]);
- }));
+ });
- it('should support duplicates', inject(function($rootScope) {
- $rootScope.items = [a, a, b, c];
- $rootScope.$digest();
+ it('should support duplicates', function() {
+ scope.items = [a, a, b, c];
+ scope.$digest();
var newElements = element.find('li');
expect(newElements[0]).toEqual(lis[0]);
expect(newElements[1]).not.toEqual(lis[0]);
@@ -383,50 +388,48 @@ describe('ngRepeat', function() {
expect(newElements[3]).toEqual(lis[2]);
lis = newElements;
- $rootScope.$digest();
+ scope.$digest();
newElements = element.find('li');
expect(newElements[0]).toEqual(lis[0]);
expect(newElements[1]).toEqual(lis[1]);
expect(newElements[2]).toEqual(lis[2]);
expect(newElements[3]).toEqual(lis[3]);
- $rootScope.$digest();
+ scope.$digest();
newElements = element.find('li');
expect(newElements[0]).toEqual(lis[0]);
expect(newElements[1]).toEqual(lis[1]);
expect(newElements[2]).toEqual(lis[2]);
expect(newElements[3]).toEqual(lis[3]);
- }));
+ });
- it('should remove last item when one duplicate instance is removed',
- inject(function($rootScope) {
- $rootScope.items = [a, a, a];
- $rootScope.$digest();
+ it('should remove last item when one duplicate instance is removed', function() {
+ scope.items = [a, a, a];
+ scope.$digest();
lis = element.find('li');
- $rootScope.items = [a, a];
- $rootScope.$digest();
+ scope.items = [a, a];
+ scope.$digest();
var newElements = element.find('li');
expect(newElements.length).toEqual(2);
expect(newElements[0]).toEqual(lis[0]);
expect(newElements[1]).toEqual(lis[1]);
- }));
+ });
- it('should reverse items when the collection is reversed',
- inject(function($rootScope) {
- $rootScope.items = [a, b, c];
- $rootScope.$digest();
+ it('should reverse items when the collection is reversed', function() {
+ scope.items = [a, b, c];
+ scope.$digest();
lis = element.find('li');
- $rootScope.items = [c, b, a];
- $rootScope.$digest();
+ scope.items = [c, b, a];
+ scope.$digest();
var newElements = element.find('li');
expect(newElements.length).toEqual(3);
expect(newElements[0]).toEqual(lis[2]);
expect(newElements[1]).toEqual(lis[1]);
expect(newElements[2]).toEqual(lis[0]);
- }));
+ });
});
});