diff --git a/src/apis.js b/src/apis.js index 098d9cdd6be8..0e94e2a55ce2 100644 --- a/src/apis.js +++ b/src/apis.js @@ -98,12 +98,12 @@ HashQueueMap.prototype = { } } }, - + /** * return the first item without deleting it */ peek: function(key) { - var array = this[key = hashKey(key)]; + var array = this[hashKey(key)]; if (array) { return array[0]; } diff --git a/src/jqLite.js b/src/jqLite.js index 1ba270b671d3..864e842d69f4 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -266,9 +266,9 @@ function JQLiteHasClass(element, selector) { indexOf( " " + selector + " " ) > -1); } -function JQLiteRemoveClass(element, selector) { - if (selector) { - forEach(selector.split(' '), function(cssClass) { +function JQLiteRemoveClass(element, cssClasses) { + if (cssClasses) { + forEach(cssClasses.split(' '), function(cssClass) { element.className = trim( (" " + element.className + " ") .replace(/[\n\t]/g, " ") @@ -278,9 +278,9 @@ function JQLiteRemoveClass(element, selector) { } } -function JQLiteAddClass(element, selector) { - if (selector) { - forEach(selector.split(' '), function(cssClass) { +function JQLiteAddClass(element, cssClasses) { + if (cssClasses) { + forEach(cssClasses.split(' '), function(cssClass) { if (!JQLiteHasClass(element, cssClass)) { element.className = trim(element.className + ' ' + trim(cssClass)); } diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index 8e66d6a2450a..ce7300654054 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -1010,22 +1010,25 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ // model -> value var ctrl = this; - $scope.$watch(ngModelGet, function ngModelWatchAction(value) { - // ignore change from view - if (ctrl.$modelValue === value) return; + $scope.$watch(function ngModelWatch() { + var value = ngModelGet($scope); - var formatters = ctrl.$formatters, - idx = formatters.length; + // if scope model value and ngModel value are out of sync + if (ctrl.$modelValue !== value) { - ctrl.$modelValue = value; - while(idx--) { - value = formatters[idx](value); - } + var formatters = ctrl.$formatters, + idx = formatters.length; - if (ctrl.$viewValue !== value) { - ctrl.$viewValue = value; - ctrl.$render(); + ctrl.$modelValue = value; + while(idx--) { + value = formatters[idx](value); + } + + if (ctrl.$viewValue !== value) { + ctrl.$viewValue = value; + ctrl.$render(); + } } }); }]; diff --git a/src/ng/directive/ngClass.js b/src/ng/directive/ngClass.js index 13b0378a9f13..76b0ed570dda 100644 --- a/src/ng/directive/ngClass.js +++ b/src/ng/directive/ngClass.js @@ -3,25 +3,55 @@ function classDirective(name, selector) { name = 'ngClass' + name; return ngDirective(function(scope, element, attr) { - // Reusable function for re-applying the ngClass - function ngClassWatchAction(newVal, oldVal) { - if (selector === true || scope.$index % 2 === selector) { - if (oldVal && (newVal !== oldVal)) { - if (isObject(oldVal) && !isArray(oldVal)) - oldVal = map(oldVal, function(v, k) { if (v) return k }); - element.removeClass(isArray(oldVal) ? oldVal.join(' ') : oldVal); - } - if (isObject(newVal) && !isArray(newVal)) - newVal = map(newVal, function(v, k) { if (v) return k }); - if (newVal) element.addClass(isArray(newVal) ? newVal.join(' ') : newVal); - } - }; + scope.$watch(attr[name], ngClassWatchAction, true); attr.$observe('class', function(value) { var ngClass = scope.$eval(attr[name]); ngClassWatchAction(ngClass, ngClass); }); + + + if (name !== 'ngClass') { + scope.$watch('$index', function($index, old$index) { + var mod = $index % 2; + if (mod !== old$index % 2) { + if (mod == selector) { + addClass(scope.$eval(attr[name])); + } else { + removeClass(scope.$eval(attr[name])); + } + } + }); + } + + + function ngClassWatchAction(newVal, oldVal) { + if (selector === true || scope.$index % 2 === selector) { + if (oldVal && (newVal !== oldVal)) { + removeClass(oldVal); + } + addClass(newVal); + } + } + + + function removeClass(classVal) { + if (isObject(classVal) && !isArray(classVal)) { + classVal = map(classVal, function(v, k) { if (v) return k }); + } + element.removeClass(isArray(classVal) ? classVal.join(' ') : classVal); + } + + + function addClass(classVal) { + if (isObject(classVal) && !isArray(classVal)) { + classVal = map(classVal, function(v, k) { if (v) return k }); + } + if (classVal) { + element.addClass(isArray(classVal) ? classVal.join(' ') : classVal); + } + } }); } diff --git a/src/ng/directive/ngRepeat.js b/src/ng/directive/ngRepeat.js index 021845fae0ad..893ad442e1b4 100644 --- a/src/ng/directive/ngRepeat.js +++ b/src/ng/directive/ngRepeat.js @@ -88,7 +88,7 @@ var ngRepeatDirective = ngDirective({ // We need an array of these objects since the same object can be returned from the iterator. // We expect this to be a rare case. var lastOrder = new HashQueueMap(); - var indexValues = []; + scope.$watch(function ngRepeatWatch(scope){ var index, length, collection = scope.$eval(rhs), @@ -119,18 +119,7 @@ var ngRepeatDirective = ngDirective({ key = (collection === array) ? index : array[index]; value = collection[key]; - // if collection is array and value is object, it can be shifted to allow for position change - // if collection is array and value is not object, need to first check whether index is same to - // avoid shifting wrong value - // if collection is not array, need to always check index to avoid shifting wrong value - if (lastOrder.peek(value)) { - last = collection === array ? - ((isObject(value)) ? lastOrder.shift(value) : - (index === lastOrder.peek(value).index ? lastOrder.shift(value) : undefined)) : - (index === lastOrder.peek(value).index ? lastOrder.shift(value) : undefined); - } else { - last = undefined; - } + last = lastOrder.shift(value); if (last) { // if we have already seen this object, then we need to reuse the @@ -151,12 +140,6 @@ var ngRepeatDirective = ngDirective({ cursor = last.element; } } else { - if (indexValues.hasOwnProperty(index) && collection !== array) { - var preValue = indexValues[index]; - var v = lastOrder.shift(preValue); - v.element.remove(); - v.scope.$destroy(); - } // new item which we don't know about childScope = scope.$new(); } @@ -178,16 +161,10 @@ var ngRepeatDirective = ngDirective({ index: index }; nextOrder.push(value, last); - indexValues[index] = value; }); } } - var i, l; - for (i = 0, l = indexValues.length - length; i < l; i++) { - indexValues.pop(); - } - //shrink children for (key in lastOrder) { if (lastOrder.hasOwnProperty(key)) { diff --git a/test/ng/directive/ngRepeatSpec.js b/test/ng/directive/ngRepeatSpec.js index 83f23cec370d..6485387427d0 100644 --- a/test/ng/directive/ngRepeatSpec.js +++ b/test/ng/directive/ngRepeatSpec.js @@ -1,7 +1,13 @@ 'use strict'; describe('ngRepeat', function() { - var element; + var element, $compile, scope; + + + beforeEach(inject(function(_$compile_, $rootScope) { + $compile = _$compile_; + scope = $rootScope.$new(); + })); afterEach(function(){ @@ -9,361 +15,372 @@ describe('ngRepeat', function() { }); - it('should ngRepeat over array', inject(function($rootScope, $compile) { + it('should iterate over an array of objects', function() { element = $compile( '')($rootScope); + '
  • {{item.name}};
  • ' + + '')(scope); Array.prototype.extraProperty = "should be ignored"; // INIT - $rootScope.items = ['misko', 'shyam']; - $rootScope.$digest(); + scope.items = [{name: 'misko'}, {name:'shyam'}]; + scope.$digest(); expect(element.find('li').length).toEqual(2); expect(element.text()).toEqual('misko;shyam;'); delete Array.prototype.extraProperty; // GROW - $rootScope.items = ['adam', 'kai', 'brad']; - $rootScope.$digest(); + scope.items.push({name: 'adam'}); + scope.$digest(); expect(element.find('li').length).toEqual(3); - expect(element.text()).toEqual('adam;kai;brad;'); + expect(element.text()).toEqual('misko;shyam;adam;'); // SHRINK - $rootScope.items = ['brad']; - $rootScope.$digest(); + scope.items.pop(); + scope.items.shift(); + scope.$digest(); expect(element.find('li').length).toEqual(1); - expect(element.text()).toEqual('brad;'); - })); + expect(element.text()).toEqual('shyam;'); + }); - it('should ngRepeat over array of primitive correctly', inject(function($rootScope, $compile) { + it('should iterate over an array of primitives', function() { element = $compile( '')($rootScope); + '
  • {{item}};
  • ' + + '')(scope); Array.prototype.extraProperty = "should be ignored"; // INIT - $rootScope.items = [true, true, true]; - $rootScope.$digest(); + 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; - $rootScope.items = [false, true, true]; - $rootScope.$digest(); + scope.items = [false, true, true]; + scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('false;true;true;'); - $rootScope.items = [false, true, false]; - $rootScope.$digest(); + scope.items = [false, true, false]; + scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('false;true;false;'); - $rootScope.items = [true]; - $rootScope.$digest(); + scope.items = [true]; + scope.$digest(); expect(element.find('li').length).toEqual(1); expect(element.text()).toEqual('true;'); - $rootScope.items = [true, true, false]; - $rootScope.$digest(); + scope.items = [true, true, false]; + scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('true;true;false;'); - $rootScope.items = [true, false, false]; - $rootScope.$digest(); + scope.items = [true, false, false]; + scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('true;false;false;'); // string - $rootScope.items = ['a', 'a', 'a']; - $rootScope.$digest(); + scope.items = ['a', 'a', 'a']; + scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('a;a;a;'); - $rootScope.items = ['ab', 'a', 'a']; - $rootScope.$digest(); + scope.items = ['ab', 'a', 'a']; + scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('ab;a;a;'); - $rootScope.items = ['test']; - $rootScope.$digest(); + scope.items = ['test']; + scope.$digest(); expect(element.find('li').length).toEqual(1); expect(element.text()).toEqual('test;'); - - $rootScope.items = ['same', 'value']; - $rootScope.$digest(); + + scope.items = ['same', 'value']; + scope.$digest(); expect(element.find('li').length).toEqual(2); expect(element.text()).toEqual('same;value;'); - - // number - $rootScope.items = [12, 12, 12]; - $rootScope.$digest(); + + // number + scope.items = [12, 12, 12]; + scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('12;12;12;'); - $rootScope.items = [53, 12, 27]; - $rootScope.$digest(); + scope.items = [53, 12, 27]; + scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('53;12;27;'); - $rootScope.items = [89]; - $rootScope.$digest(); + scope.items = [89]; + scope.$digest(); expect(element.find('li').length).toEqual(1); expect(element.text()).toEqual('89;'); - $rootScope.items = [89, 23]; - $rootScope.$digest(); + scope.items = [89, 23]; + scope.$digest(); expect(element.find('li').length).toEqual(2); expect(element.text()).toEqual('89;23;'); - })); + }); - it('should ngRepeat over object', inject(function($rootScope, $compile) { + it('should iterate over on object/map', function() { element = $compile( '')($rootScope); - $rootScope.items = {misko:'swe', shyam:'set'}; - $rootScope.$digest(); - expect(element.text()).toEqual('misko:swe;shyam:set;'); - })); + '
  • {{key}}:{{value}}|
  • ' + + '')(scope); + scope.items = {misko:'swe', shyam:'set'}; + scope.$digest(); + expect(element.text()).toEqual('misko:swe|shyam:set|'); + }); + + + it('should iterate over object with changing primitive property values', function() { + // test for issue #933 - - it('should ngRepeat over object with primitive value correctly', inject(function($rootScope, $compile) { element = $compile( '')($rootScope); - $rootScope.items = {misko:'true', shyam:'true', zhenbo: 'true'}; - $rootScope.$digest(); + '
  • ' + + '{{key}}:{{value}};' + + '' + + '
  • ' + + '')(scope); + //document.body.appendChild(element[0]); + 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;'); - - $rootScope.items = {misko:'false', shyam:'true', zhenbo: 'true'}; - $rootScope.$digest(); - expect(element.find('li').length).toEqual(3); + + browserTrigger(element.find('input').eq(0), 'click'); + expect(element.text()).toEqual('misko:false;shyam:true;zhenbo:true;'); - - $rootScope.items = {misko:'false', shyam:'false', zhenbo: 'false'}; - $rootScope.$digest(); - expect(element.find('li').length).toEqual(3); - expect(element.text()).toEqual('misko:false;shyam:false;zhenbo:false;'); - - $rootScope.items = {misko:'true'}; - $rootScope.$digest(); - expect(element.find('li').length).toEqual(1); - expect(element.text()).toEqual('misko: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); - $rootScope.items = {shyam:'true', zhenbo: 'false'}; - $rootScope.$digest(); - expect(element.find('li').length).toEqual(2); - expect(element.text()).toEqual('shyam:true;zhenbo:false;'); - })); + 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); + }); - 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( '')($rootScope); - $rootScope.items = new Class(); - $rootScope.items.name = 'value'; - $rootScope.$digest(); + '
  • {{key}}:{{value}};
  • ' + + '')(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('')($rootScope); + element = jqLite(''); + $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('')($rootScope); + element = jqLite(''); + $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( '')($rootScope); - $rootScope.items = ['misko', 'shyam', 'frodo']; - $rootScope.$digest(); - expect(element.text()).toEqual('misko0|shyam1|frodo2|'); - })); + '
  • {{item}}:{{$index}}|
  • ' + + '')(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( '')($rootScope); - $rootScope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'}; - $rootScope.$digest(); - expect(element.text()).toEqual('frodo:f0|misko:m1|shyam:s2|'); - })); + '
  • {{key}}:{{val}}:{{$index}}|
  • ' + + '')(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( '')($rootScope); - $rootScope.items = ['misko', 'shyam', 'doug']; - $rootScope.$digest(); + '')(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( '')($rootScope); - $rootScope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'}; - $rootScope.$digest(); + '')(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('')($rootScope); - $rootScope.items = ['a', 'b', 'c']; - $rootScope.items.$$hashkey = 'xxx'; - $rootScope.items.$root = 'yyy'; - $rootScope.$digest(); + it('should ignore $ and $$ properties', function() { + element = $compile('')(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( '')($rootScope); - $rootScope.groups = [['a', 'b'], ['c','d']]; - $rootScope.$digest(); + '')(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('')($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('')(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('')($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('')(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('')($rootScope); - $rootScope.array = ['a', 1, null, undefined, {}]; - $rootScope.$digest(); + it('should iterate over all kinds of types', function() { + element = $compile('')(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( '')($rootScope); + '')(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]); @@ -371,50 +388,66 @@ 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]); - })); + }); + + + it('should reuse elements even when model is composed of primitives', function() { + // rebuilding repeater from scratch can be expensive, we should try to avoid it even for + // model that is composed of primitives. + + scope.items = ['hello', 'cau', 'ahoj']; + scope.$digest(); + lis = element.find('li'); + + scope.items = ['ahoj', 'hello', 'cau']; + scope.$digest(); + var newLis = element.find('li'); + expect(newLis.length).toEqual(3); + expect(newLis[0]).toEqual(lis[2]); + expect(newLis[1]).toEqual(lis[0]); + expect(newLis[2]).toEqual(lis[1]); + }); }); });