diff options
| author | Misko Hevery | 2013-03-19 22:27:27 -0700 | 
|---|---|---|
| committer | Misko Hevery | 2013-03-29 23:01:52 -0700 | 
| commit | 61f2767ce65562257599649d9eaf9da08f321655 (patch) | |
| tree | cf9c6809bbee62d19e04961f53d5c89bab9dd663 /test | |
| parent | 5eb968553a1130461ab8704535691e00eb154ac2 (diff) | |
| download | angular.js-61f2767ce65562257599649d9eaf9da08f321655.tar.bz2 | |
feat(ngRepeat): add support for custom tracking of items
BREAKING CHANGE:
It is considered an error to have two items produce
the same track by key. (This was tolerated before.)
Diffstat (limited to 'test')
| -rw-r--r-- | test/ApiSpecs.js | 37 | ||||
| -rw-r--r-- | test/ng/directive/ngClassSpec.js | 2 | ||||
| -rw-r--r-- | test/ng/directive/ngRepeatSpec.js | 322 | 
3 files changed, 175 insertions, 186 deletions
diff --git a/test/ApiSpecs.js b/test/ApiSpecs.js index 1e52cf44..12de39d0 100644 --- a/test/ApiSpecs.js +++ b/test/ApiSpecs.js @@ -23,42 +23,5 @@ describe('api', function() {        expect(map.get('c')).toBe(undefined);      });    }); - - -  describe('HashQueueMap', function() { -    it('should do basic crud with collections', function() { -      var map = new HashQueueMap(); -      map.push('key', 'a'); -      map.push('key', 'b'); -      expect(map[hashKey('key')]).toEqual(['a', 'b']); -      expect(map.peek('key')).toEqual('a'); -      expect(map[hashKey('key')]).toEqual(['a', 'b']); -      expect(map.shift('key')).toEqual('a'); -      expect(map.peek('key')).toEqual('b'); -      expect(map[hashKey('key')]).toEqual(['b']); -      expect(map.shift('key')).toEqual('b'); -      expect(map.shift('key')).toEqual(undefined); -      expect(map[hashKey('key')]).toEqual(undefined); -    }); - -    it('should support primitive and object keys', function() { -      var obj1 = {}, -          obj2 = {}; - -      var map = new HashQueueMap(); -      map.push(obj1, 'a1'); -      map.push(obj1, 'a2'); -      map.push(obj2, 'b'); -      map.push(1, 'c'); -      map.push(undefined, 'd'); -      map.push(null, 'e'); - -      expect(map[hashKey(obj1)]).toEqual(['a1', 'a2']); -      expect(map[hashKey(obj2)]).toEqual(['b']); -      expect(map[hashKey(1)]).toEqual(['c']); -      expect(map[hashKey(undefined)]).toEqual(['d']); -      expect(map[hashKey(null)]).toEqual(['e']); -    }); -  });  }); diff --git a/test/ng/directive/ngClassSpec.js b/test/ng/directive/ngClassSpec.js index 69afef7a..d4bd76fe 100644 --- a/test/ng/directive/ngClassSpec.js +++ b/test/ng/directive/ngClassSpec.js @@ -249,7 +249,7 @@ describe('ngClass', function() {    it('should update ngClassOdd/Even when model is changed by filtering', inject(function($rootScope, $compile) {      element = $compile('<ul>' + -      '<li ng-repeat="i in items" ' + +      '<li ng-repeat="i in items track by $index" ' +        'ng-class-odd="\'odd\'" ng-class-even="\'even\'"></li>' +        '<ul>')($rootScope);      $rootScope.items = ['a','b','a']; diff --git a/test/ng/directive/ngRepeatSpec.js b/test/ng/directive/ngRepeatSpec.js index 33e4dcfd..44406d6d 100644 --- a/test/ng/directive/ngRepeatSpec.js +++ b/test/ng/directive/ngRepeatSpec.js @@ -1,16 +1,27 @@  'use strict';  describe('ngRepeat', function() { -  var element, $compile, scope; +  var element, $compile, scope, $exceptionHandler; -  beforeEach(inject(function(_$compile_, $rootScope) { +  beforeEach(module(function($exceptionHandlerProvider) { +    $exceptionHandlerProvider.mode('log'); +  })); + +  beforeEach(inject(function(_$compile_, $rootScope, _$exceptionHandler_) {      $compile = _$compile_; +    $exceptionHandler = _$exceptionHandler_;      scope = $rootScope.$new();    })); -  afterEach(function(){ +  afterEach(function() { +    if ($exceptionHandler.errors.length) { +      dump(jasmine.getEnv().currentSpec.getFullName()); +      dump('$exceptionHandler has errors'); +      dump($exceptionHandler.errors); +      expect($exceptionHandler.errors).toBe([]); +    }      dealoc(element);    }); @@ -44,141 +55,177 @@ describe('ngRepeat', function() {    }); -  it('should iterate over an array of primitives', function() { +  it('should iterate over on object/map', function() {      element = $compile(        '<ul>' + -        '<li ng-repeat="item in items">{{item}};</li>' + +        '<li ng-repeat="(key, value) in items">{{key}}:{{value}}|</li>' +        '</ul>')(scope); - -    Array.prototype.extraProperty = "should be ignored"; -    // INIT -    scope.items = [true, true, true]; +    scope.items = {misko:'swe', shyam:'set'};      scope.$digest(); -    expect(element.find('li').length).toEqual(3); -    expect(element.text()).toEqual('true;true;true;'); -    delete Array.prototype.extraProperty; +    expect(element.text()).toEqual('misko:swe|shyam:set|'); +  }); -    scope.items = [false, true, true]; -    scope.$digest(); -    expect(element.find('li').length).toEqual(3); -    expect(element.text()).toEqual('false;true;true;'); -    scope.items = [false, true, false]; -    scope.$digest(); -    expect(element.find('li').length).toEqual(3); -    expect(element.text()).toEqual('false;true;false;'); +  describe('track by', function() { +    it('should track using expression function', function() { +      element = $compile( +          '<ul>' + +              '<li ng-repeat="item in items track by item.id">{{item.name}};</li>' + +              '</ul>')(scope); +      scope.items = [{id: 'misko'}, {id: 'igor'}]; +      scope.$digest(); +      var li0 = element.find('li')[0]; +      var li1 = element.find('li')[1]; -    scope.items = [true]; -    scope.$digest(); -    expect(element.find('li').length).toEqual(1); -    expect(element.text()).toEqual('true;'); +      scope.items.push(scope.items.shift()); +      scope.$digest(); +      expect(element.find('li')[0]).toBe(li1); +      expect(element.find('li')[1]).toBe(li0); +    }); -    scope.items = [true, true, false]; -    scope.$digest(); -    expect(element.find('li').length).toEqual(3); -    expect(element.text()).toEqual('true;true;false;'); -    scope.items = [true, false, false]; -    scope.$digest(); -    expect(element.find('li').length).toEqual(3); -    expect(element.text()).toEqual('true;false;false;'); +    it('should track using build in $id function', function() { +      element = $compile( +          '<ul>' + +              '<li ng-repeat="item in items track by $id(item)">{{item.name}};</li>' + +              '</ul>')(scope); +      scope.items = [{name: 'misko'}, {name: 'igor'}]; +      scope.$digest(); +      var li0 = element.find('li')[0]; +      var li1 = element.find('li')[1]; -    // string -    scope.items = ['a', 'a', 'a']; -    scope.$digest(); -    expect(element.find('li').length).toEqual(3); -    expect(element.text()).toEqual('a;a;a;'); +      scope.items.push(scope.items.shift()); +      scope.$digest(); +      expect(element.find('li')[0]).toBe(li1); +      expect(element.find('li')[1]).toBe(li0); +    }); -    scope.items = ['ab', 'a', 'a']; -    scope.$digest(); -    expect(element.find('li').length).toEqual(3); -    expect(element.text()).toEqual('ab;a;a;'); -    scope.items = ['test']; -    scope.$digest(); -    expect(element.find('li').length).toEqual(1); -    expect(element.text()).toEqual('test;'); +    it('should iterate over an array of primitives', function() { +      element = $compile( +          '<ul>' + +              '<li ng-repeat="item in items track by $index">{{item}};</li>' + +          '</ul>')(scope); -    scope.items = ['same', 'value']; -    scope.$digest(); -    expect(element.find('li').length).toEqual(2); -    expect(element.text()).toEqual('same;value;'); +      Array.prototype.extraProperty = "should be ignored"; +      // INIT +      scope.items = [true, true, true]; +      scope.$digest(); +      expect(element.find('li').length).toEqual(3); +      expect(element.text()).toEqual('true;true;true;'); +      delete Array.prototype.extraProperty; -    // number -    scope.items = [12, 12, 12]; -    scope.$digest(); -    expect(element.find('li').length).toEqual(3); -    expect(element.text()).toEqual('12;12;12;'); +      scope.items = [false, true, true]; +      scope.$digest(); +      expect(element.find('li').length).toEqual(3); +      expect(element.text()).toEqual('false;true;true;'); -    scope.items = [53, 12, 27]; -    scope.$digest(); -    expect(element.find('li').length).toEqual(3); -    expect(element.text()).toEqual('53;12;27;'); +      scope.items = [false, true, false]; +      scope.$digest(); +      expect(element.find('li').length).toEqual(3); +      expect(element.text()).toEqual('false;true;false;'); -    scope.items = [89]; -    scope.$digest(); -    expect(element.find('li').length).toEqual(1); -    expect(element.text()).toEqual('89;'); +      scope.items = [true]; +      scope.$digest(); +      expect(element.find('li').length).toEqual(1); +      expect(element.text()).toEqual('true;'); -    scope.items = [89, 23]; -    scope.$digest(); -    expect(element.find('li').length).toEqual(2); -    expect(element.text()).toEqual('89;23;'); -  }); +      scope.items = [true, true, false]; +      scope.$digest(); +      expect(element.find('li').length).toEqual(3); +      expect(element.text()).toEqual('true;true;false;'); +      scope.items = [true, false, false]; +      scope.$digest(); +      expect(element.find('li').length).toEqual(3); +      expect(element.text()).toEqual('true;false;false;'); -  it('should iterate over on object/map', function() { -    element = $compile( -      '<ul>' + -        '<li ng-repeat="(key, value) in items">{{key}}:{{value}}|</li>' + -      '</ul>')(scope); -    scope.items = {misko:'swe', shyam:'set'}; -    scope.$digest(); -    expect(element.text()).toEqual('misko:swe|shyam:set|'); -  }); +      // string +      scope.items = ['a', 'a', 'a']; +      scope.$digest(); +      expect(element.find('li').length).toEqual(3); +      expect(element.text()).toEqual('a;a;a;'); +      scope.items = ['ab', 'a', 'a']; +      scope.$digest(); +      expect(element.find('li').length).toEqual(3); +      expect(element.text()).toEqual('ab;a;a;'); -  it('should iterate over object with changing primitive property values', function() { -    // test for issue #933 +      scope.items = ['test']; +      scope.$digest(); +      expect(element.find('li').length).toEqual(1); +      expect(element.text()).toEqual('test;'); -    element = $compile( -      '<ul>' + -        '<li ng-repeat="(key, value) in items">' + -          '{{key}}:{{value}};' + -          '<input type="checkbox" ng-model="items[key]">' + -        '</li>' + -      '</ul>')(scope); +      scope.items = ['same', 'value']; +      scope.$digest(); +      expect(element.find('li').length).toEqual(2); +      expect(element.text()).toEqual('same;value;'); -    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;'); +      // number +      scope.items = [12, 12, 12]; +      scope.$digest(); +      expect(element.find('li').length).toEqual(3); +      expect(element.text()).toEqual('12;12;12;'); -    browserTrigger(element.find('input').eq(0), 'click'); +      scope.items = [53, 12, 27]; +      scope.$digest(); +      expect(element.find('li').length).toEqual(3); +      expect(element.text()).toEqual('53;12;27;'); -    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); +      scope.items = [89]; +      scope.$digest(); +      expect(element.find('li').length).toEqual(1); +      expect(element.text()).toEqual('89;'); + +      scope.items = [89, 23]; +      scope.$digest(); +      expect(element.find('li').length).toEqual(2); +      expect(element.text()).toEqual('89;23;'); +    }); -    browserTrigger(element.find('input').eq(0), 'click'); -    expect(element.text()).toEqual('misko:true;shyam:true;zhenbo:true;'); -    expect(element.find('input')[0].checked).toBe(true); -    expect(element.find('input')[1].checked).toBe(true); -    expect(element.find('input')[2].checked).toBe(true); -    browserTrigger(element.find('input').eq(1), 'click'); -    expect(element.text()).toEqual('misko:true;shyam:false;zhenbo:true;'); -    expect(element.find('input')[0].checked).toBe(true); -    expect(element.find('input')[1].checked).toBe(false); -    expect(element.find('input')[2].checked).toBe(true); +    it('should iterate over object with changing primitive property values', function() { +      // test for issue #933 -    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); +      element = $compile( +          '<ul>' + +              '<li ng-repeat="(key, value) in items track by $index">' + +              '{{key}}:{{value}};' + +              '<input type="checkbox" ng-model="items[key]">' + +              '</li>' + +              '</ul>')(scope); + +      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;'); + +      browserTrigger(element.find('input').eq(0), 'click'); + +      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); + +      browserTrigger(element.find('input').eq(0), 'click'); +      expect(element.text()).toEqual('misko:true;shyam:true;zhenbo:true;'); +      expect(element.find('input')[0].checked).toBe(true); +      expect(element.find('input')[1].checked).toBe(true); +      expect(element.find('input')[2].checked).toBe(true); + +      browserTrigger(element.find('input').eq(1), 'click'); +      expect(element.text()).toEqual('misko:true;shyam:false;zhenbo:true;'); +      expect(element.find('input')[0].checked).toBe(true); +      expect(element.find('input')[1].checked).toBe(false); +      expect(element.find('input')[2].checked).toBe(true); + +      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); +    });    }); @@ -199,19 +246,18 @@ describe('ngRepeat', function() {    it('should error on wrong parsing of ngRepeat', function() { -    expect(function() { -      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'."); +    element = jqLite('<ul><li ng-repeat="i dont parse"></li></ul>'); +    $compile(element)(scope); +    expect($exceptionHandler.errors.shift()[0].message). +        toBe("Expected ngRepeat in form of '_item_ in _collection_[ track by _id_]' but got 'i dont parse'.");    });    it("should throw error when left-hand-side of ngRepeat can't be parsed", function() { -    expect(function() {        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'."); +    expect($exceptionHandler.errors.shift()[0].message). +        toBe("'item' in 'item in collection' should be identifier or (key, value) but got 'i dont parse'.");    }); @@ -311,7 +357,7 @@ describe('ngRepeat', function() {    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.$$hashKey = 'xxx';      scope.items.$root = 'yyy';      scope.$digest(); @@ -393,43 +439,23 @@ describe('ngRepeat', function() {      }); -    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]); -      expect(newElements[2]).toEqual(lis[1]); -      expect(newElements[3]).toEqual(lis[2]); - -      lis = newElements; +    it('should throw error on duplicates and recover', function() { +      scope.items = [a, a, a];        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]); +      expect($exceptionHandler.errors.shift().message). +          toEqual('Duplicates in a repeater are not allowed. Repeater: item in items'); +      // recover +      scope.items = [a];        scope.$digest(); -      newElements = element.find('li'); +      var newElements = element.find('li'); +      expect(newElements.length).toEqual(1);        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', function() { -      scope.items = [a, a, a]; -      scope.$digest(); -      lis = element.find('li'); - -      scope.items = [a, a]; +      scope.items = [];        scope.$digest();        var newElements = element.find('li'); -      expect(newElements.length).toEqual(2); -      expect(newElements[0]).toEqual(lis[0]); -      expect(newElements[1]).toEqual(lis[1]); +      expect(newElements.length).toEqual(0);      });  | 
