diff --git a/demos/collection-repeat/index.html b/demos/collection-repeat/index.html index bc5c366762..0d954ee87a 100644 --- a/demos/collection-repeat/index.html +++ b/demos/collection-repeat/index.html @@ -5,8 +5,8 @@ Collection-Repeat: Early Preview - - + + diff --git a/js/angular/service/collectionRepeatDataSource.js b/js/angular/service/collectionRepeatDataSource.js index 39dacf7f90..7f84d5d8b2 100644 --- a/js/angular/service/collectionRepeatDataSource.js +++ b/js/angular/service/collectionRepeatDataSource.js @@ -119,6 +119,7 @@ function($cacheFactory, $parse) { this.transcludeParent[0].appendChild(item.element[0]); } reconnectScope(item.scope); + !item.scope.$$phase && item.scope.$digest(); }, getLength: function() { return this.data && this.data.length || 0; diff --git a/js/angular/service/collectionRepeatManager.js b/js/angular/service/collectionRepeatManager.js index 536e11a54b..eefdd181e6 100644 --- a/js/angular/service/collectionRepeatManager.js +++ b/js/angular/service/collectionRepeatManager.js @@ -188,7 +188,7 @@ function($rootScope, $timeout) { this.renderItem(i, rect.primaryPos - startPos, rect.secondaryPos); i++; } - var bufferEndIndex = i -1; + var bufferEndIndex = i - 1; for (i in this.renderedItems) { if (i < bufferStartIndex || i > bufferEndIndex) { @@ -207,9 +207,6 @@ function($rootScope, $timeout) { primaryPos, secondaryPos, secondaryPos ); this.renderedItems[dataIndex] = item; - if (item.scope && !item.scope.$$phase) { - item.scope.$digest(); - } } else { delete this.renderedItems[dataIndex]; } diff --git a/test/unit/angular/directive/collectionRepeat.unit.js b/test/unit/angular/directive/collectionRepeat.unit.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/unit/angular/service/collectionDataSource.unit.js b/test/unit/angular/service/collectionDataSource.unit.js index a7aa3ab3e6..c555122239 100644 --- a/test/unit/angular/service/collectionDataSource.unit.js +++ b/test/unit/angular/service/collectionDataSource.unit.js @@ -74,7 +74,7 @@ describe('$collectionDataSource service', function() { it('should be $cacheFactory', function() { var cache = {}; - module('ionic', function($provide) { + module('ionic', function($provide) { $provide.value('$cacheFactory', function() { return cache; }); }); var source = setup(); @@ -89,7 +89,7 @@ describe('$collectionDataSource service', function() { source.itemCache.remove('a'); expect(source.itemCache.keys()).toEqual(['b']); }); - + }); it('.destroy() should cleanup dimensions & cache', function() { @@ -210,7 +210,7 @@ describe('$collectionDataSource service', function() { }); describe('.attachItem()', function() { - it('should add element if it has no parent', function() { + it('should add element if it has no parent and digest', inject(function($rootScope) { var source = setup({ transcludeParent: angular.element('
') }); @@ -218,16 +218,18 @@ describe('$collectionDataSource service', function() { spyOn(window, 'reconnectScope'); var item = { element: element, - scope: {} + scope: $rootScope.$new() }; + spyOn(item.scope, '$digest'); spyOn(source.transcludeParent[0], 'appendChild'); source.attachItem(item); expect(source.transcludeParent[0].appendChild).toHaveBeenCalledWith(element[0]); expect(reconnectScope).toHaveBeenCalledWith(item.scope); - }); + expect(item.scope.$digest).toHaveBeenCalled(); + })); - it('should not append element if it has a parent already', function() { + it('should not append element if it has a parent already', inject(function($rootScope) { var element = angular.element('
'); var source = setup({ transcludeParent: angular.element('
') @@ -236,13 +238,15 @@ describe('$collectionDataSource service', function() { spyOn(window, 'reconnectScope'); var item = { element: element, - scope: {} + scope: $rootScope.$new() }; + spyOn(item.scope, '$digest'); source.attachItem(item); spyOn(source.transcludeParent[0], 'appendChild'); expect(source.transcludeParent[0].appendChild).not.toHaveBeenCalled(); expect(reconnectScope).toHaveBeenCalledWith(item.scope); - }); + expect(item.scope.$digest).toHaveBeenCalled(); + })); }); describe('.getLength()', function() { diff --git a/test/unit/angular/service/collectionRepeatManager.unit.js b/test/unit/angular/service/collectionRepeatManager.unit.js index 15ef16d11d..0f22d5f509 100644 --- a/test/unit/angular/service/collectionRepeatManager.unit.js +++ b/test/unit/angular/service/collectionRepeatManager.unit.js @@ -186,7 +186,7 @@ describe('collectionRepeatManager service', function() { { width: 100, height: 40 }, { width: 100, height: 50 } ]; - manager.secondaryScrollSize = function() { + manager.secondaryScrollSize = function() { return 100; }; var result = manager.calculateDimensions(); @@ -271,7 +271,7 @@ describe('collectionRepeatManager service', function() { it('should work with data', function() { var manager = setup(); spyOn(manager, 'calculateDimensions').andReturn([{ - primaryPos: 100, primarySize: 30 + primaryPos: 100, primarySize: 30 }]); manager.resize(); expect(manager.viewportSize).toBe(130); @@ -438,8 +438,87 @@ describe('collectionRepeatManager service', function() { expect(manager.removeItem).toHaveBeenCalledWith('b'); }); - //TODO test .render() logic - + function mockRendering(options) { + var manager = setup({}, { + keyExpr: 'item', + listExpr: 'items', + heightGetter: function() { + return options.itemHeight; + }, + widthGetter: function() { + return options.itemWidth; + } + }); + spyOn(manager, 'scrollSize').andReturn(options.scrollHeight); + spyOn(manager, 'secondaryScrollSize').andReturn(options.scrollWidth); + spyOn(manager, 'renderItem').andCallFake(function(i) { + manager.renderedItems[i] = true; + }); + spyOn(manager, 'removeItem').andCallFake(function(i) { + delete manager.renderedItems[i]; + }); + var data = []; + for (var i = 0; i < 100; i++) { + data.push(i); + } + manager.dataSource.setData(data); + return manager; + } + + it('should render the first items that fit on screen', function() { + var manager = mockRendering({ + itemWidth: 3, + itemHeight: 20, + scrollWidth: 10, + scrollHeight: 100 + }); + manager.resize(); //triggers render + + //it should render (items that fit * items per row) with one extra row at end + expect(Object.keys(manager.renderedItems).length).toBe(18); + for (var i = 0; i < 18; i++) { + expect(manager.renderedItems[i]).toBe(true); + } + expect(manager.renderedItems[18]).toBeUndefined(); + }); + + it('should render items in the middle of the screen', function() { + var manager = mockRendering({ + itemWidth: 3, + itemHeight: 20, + scrollWidth: 10, + scrollHeight: 100 + }); + spyOn(manager, 'scrollValue').andReturn(111); + manager.resize(); + var startIndex = 17; + var bufferStartIndex = 14; //one row of buffer before the start + var bufferEndIndex = 35; //start + 17 + 6 + + expect(Object.keys(manager.renderedItems).length).toBe(22); + for (var i = bufferStartIndex; i <= bufferEndIndex; i++) { + expect(manager.renderedItems[i]).toBe(true); + } + expect(manager.renderedItems[bufferStartIndex - 1]).toBeUndefined(); + expect(manager.renderedItems[bufferEndIndex + 1]).toBeUndefined(); + }); + + it('should remove items outside the range', function() { + var manager = mockRendering({ + itemWidth: 3, + itemHeight: 20, + scrollWidth: 10, + scrollHeight: 100 + }); + manager.resize(); + manager.removeItem.reset(); + manager.renderedItems = { 17: true, 18: true, 19: true }; + //resize() re-renders everything, need to just do a normal rerender + manager.render(); + expect(manager.removeItem.callCount).toBe(2); + expect(manager.removeItem).toHaveBeenCalledWith('18'); + expect(manager.removeItem).toHaveBeenCalledWith('19'); + }); }); describe('.renderItem()', function() {