describe('collectionRepeat', function() { var el; beforeEach(module('ionic', function($provide) { $provide.decorator('$$rAF', function($delegate) { return function mockRaf(callback) { callback(); }; }); spyOn(ionic, 'animationFrameThrottle').andCallFake(function(cb) { return function fakeThrottled() { cb.apply(this, arguments); }; }); spyOn(ionic, 'debounce').andCallFake(function(cb) { return cb; }); })); var scrollView; var repeaterScope; function setup(listData, attrs, scrollViewData) { var content = angular.element('') scrollView = angular.extend({ __content: content[0], __clientHeight: 100, __clientWidth: 50, __scrollTop: 0, __scrollLeft: 0, __maxScrollTop: 100, __maxScrollLeft: 50, options: { scrollingY: true }, __callback: angular.noop, resize: angular.noop, setDimensions: angular.noop }, scrollViewData || {}); var scrollCtrl = { scrollView: scrollView, $element: content }; var list = []; if (angular.isNumber(listData)) { for (var i = 0; i < listData; i++) list.push(i); } else if (angular.isArray(listData)) { list = listData; } var element; inject(function($compile, $rootScope, $timeout) { repeaterScope = $rootScope.$new(); attrs = attrs || ''; if (!/item-height/.test(attrs)) attrs += ' item-height="25px"'; if (!/item-render-buffer/.test(attrs)) attrs += ' item-render-buffer="0"'; element = angular.element( '
{{item}}
' ); content.append(element); content.data('$$ionicScrollController', scrollCtrl); $rootScope.list = list; $compile(element)(repeaterScope); $rootScope.$apply(); content.triggerHandler('scroll.init'); $timeout.flush(); $rootScope.$apply(); }); return element; } afterEach(function() { repeaterScope && repeaterScope.$destroy(); repeaterScope = null; }); function scrollTo(n) { if (scrollView.options.scrollingY) { scrollView.__scrollTop = n; scrollView.__maxScrollTop = scrollView.options.getContentHeight() - scrollView.__clientHeight; } else { scrollView.__scrollLeft = n; scrollView.__maxScrollLeft = scrollView.options.getContentWidth() - scrollView.__clientWidth; } scrollView.__callback(); } function getItems() { return [].slice.call(scrollView.__content.querySelectorAll('[collection-repeat]')) .map(function(node) { return angular.element(node).data('$$collectionRepeatItem') }) // make sure we didn't get anything that doesn't actually have the data .filter(function(item) { return !!item; }); } function activeItems() { var items = getItems().filter(function(item) { return item.isShown; }); //1. Group items by their primary position (row), //2. Sort those groups by secondary position (column), //3. Concat them all together. var activeItems = {}; items.forEach(function(item) { (activeItems[item.primaryPos] || (activeItems[item.primaryPos] = [])).push(item); }); var result = []; Object.keys(activeItems) .sort(function(pos1, pos2) { return (+pos1) > (+pos2) ? 1 : -1; }) .forEach(function(primaryPos) { var sortedRow = activeItems[primaryPos].sort(function(a,b) { return a.secondaryPos > b.secondaryPos ? 1 : -1; }); result = result.concat(sortedRow); }); return result; } function activeItemContents() { return activeItems().map(function(item) { return item.node.innerHTML.trim(); }); } function activeItemIds() { return activeItems().map(function(item) { return item.id; }); } function activeItemDimensions() { return activeItems().map(function(item) { return scrollView.options.scrollingX ? ( 'x:' + item.primaryPos + ',y:' + item.secondaryPos + ',w:' + item.primarySize + ',h:' + item.secondarySize ) : ( 'x:' + item.secondaryPos + ',y:' + item.primaryPos + ',w:' + item.secondarySize + ',h:' + item.primarySize ); }); } it('should error with direction="xy" parent', function() { expect(function() { setup(10, '', { options: { scrollingX: true, scrollingY: true } }); }).toThrow(); }); it('should error without proper collection-repeat expression', inject(function($compile, $rootScope) { expect(function() { $compile('' + '
' + '
')($rootScope); }).toThrow(); })); it('should destroy and restore normal scrollView behavior', inject(function($compile, $rootScope) { var scope = $rootScope.$new(); var content = $compile('')(scope); var scrollView = content.data('$$ionicScrollController').scrollView; var originalCallback = scrollView.__callback; var originalGetContentHeight = scrollView.options.getContentHeight; var repeater = angular.element( '
' ); content.append(repeater); $compile(repeater)(content.scope()); $rootScope.$apply(); content.triggerHandler('scroll.init'); $rootScope.$apply(); expect(scrollView.__callback).not.toBe(originalCallback); expect(scrollView.options.getContentHeight).not.toBe(originalGetContentHeight); scope.$destroy(); expect(scrollView.__callback).toBe(originalCallback); expect(scrollView.options.getContentHeight).toBe(originalGetContentHeight); })); describe('automatic dimensions', function() { it('should use computed width/height', inject(function($window) { spyOn($window, 'getComputedStyle').andReturn({ height: '50px', width: '50px' }); setup(5, 'item-height="" item-width=""', { __clientHeight: 75, __clientWidth: 100 }); expect(activeItems().length).toBe(4); expect(activeItemDimensions()).toEqual([ 'x:0,y:0,w:50,h:50', 'x:50,y:0,w:50,h:50', 'x:0,y:50,w:50,h:50', 'x:50,y:50,w:50,h:50' ]); })); it('should error if computed height is 0', inject(function($window) { spyOn($window, 'getComputedStyle').andReturn({ height: '0px', width: '100px' }); expect(function() { setup(5, 'item-height="" item-width=""'); }).toThrow(); })); it('should error if computed width is 0', inject(function($window) { spyOn($window, 'getComputedStyle').andReturn({ height: '100px', width: '0px' }); expect(function() { setup(5, 'item-height="" item-width=""'); }).toThrow(); })); }); describe('horizontal static list', function() { function setupHorizontal() { setup(10, 'item-height="100%" item-width="30"', { options: { scrollingX: true, scrollingY: false }, __clientWidth: 80, __clientHeight: 25 }); } it('should show initial screen of items', function() { setupHorizontal(); expect(activeItems().length).toBe(3); expect(activeItemContents()).toEqual(['0','1','2']) }); it('should switch out as you scroll', function() { setupHorizontal(); expect(activeItems().length).toBe(3); expect(activeItemContents()).toEqual(['0','1','2']) expect(activeItemIds()).toEqual(['item0','item1','item2']); // Item 0 gets sent down to the bottom after scrolling past it scrollTo(31); expect(activeItems().length).toBe(3); expect(activeItemContents()).toEqual(['1','2','3']) expect(activeItemIds()).toEqual(['item1','item2','item0']); // Item 1 gets sent down scrollTo(61); expect(activeItems().length).toBe(3); expect(activeItemContents()).toEqual(['2','3','4']) expect(activeItemIds()).toEqual(['item2','item0','item1']); }); it('should start with the same items when resizing', inject(function($window) { setupHorizontal(); scrollTo(31); scrollTo(61); expect(activeItems().length).toBe(3); expect(activeItemContents()).toEqual(['2','3','4']) expect(activeItemIds()).toEqual(['item2','item0','item1']); scrollView.__clientWidth = 50; scrollView.__clientHeight = 40; angular.element($window).triggerHandler('resize'); expect(activeItems().length).toBe(2); expect(activeItemContents()).toEqual(['2','3']) expect(activeItemIds()).toEqual(['item2','item0']); })); }); describe('vertical static list', function() { beforeEach(function() { setup(10); }); it('should show initial screen of items', function() { expect(activeItems().length).toBe(5); expect(activeItemContents()).toEqual(['0','1','2','3','4']) }); it('should switch out as you scroll', function() { expect(activeItems().length).toBe(5); expect(activeItemContents()).toEqual(['0','1','2','3','4']) expect(activeItemIds()).toEqual(['item0','item1','item2','item3','item4']); // Item 0 gets sent down to the bottom after scrolling past it scrollTo(26); expect(activeItems().length).toBe(5); expect(activeItemContents()).toEqual(['1','2','3','4','5']) expect(activeItemIds()).toEqual(['item1','item2','item3','item4','item0']); // Item 1 gets sent down scrollTo(51); expect(activeItems().length).toBe(5); expect(activeItemContents()).toEqual(['2','3','4','5','6']) expect(activeItemIds()).toEqual(['item2','item3','item4','item0','item1']); // scroll to bottom incrementally // items are traded our until it's the first case again scrollTo(76); scrollTo(101); scrollTo(126); expect(activeItems().length).toBe(5); expect(activeItemContents()).toEqual(['5','6','7','8','9']) expect(activeItemIds()).toEqual(['item0','item1','item2','item3','item4']); }); it('should start with the same items when resizing', inject(function($window) { scrollTo(26); scrollTo(51); expect(activeItems().length).toBe(5); expect(activeItemContents()).toEqual(['2','3','4','5','6']) expect(activeItemIds()).toEqual(['item2','item3','item4','item0','item1']); scrollView.__clientWidth = 200; scrollView.__clientHeight = 40; angular.element($window).triggerHandler('resize'); expect(activeItems().length).toBe(2); expect(activeItemContents()).toEqual(['2','3']) expect(activeItemIds()).toEqual(['item2','item3']); })); }); describe('vertical static grid', function() { beforeEach(function() { setup(10, 'item-width="33%" item-height="25"', { __clientWidth: 120, __clientHeight: 30 }); }); it('should show initial screen of items', function() { expect(activeItems().length).toBe(3 * 2); expect(activeItemContents()).toEqual(['0','1','2','3','4','5']) }); it('should switch out as you scroll', function() { expect(activeItems().length).toBe(6); expect(activeItemContents()).toEqual(['0','1','2','3','4','5']) expect(activeItemIds().sort()).toEqual(['item0','item1','item2','item3','item4','item5']); scrollTo(26); expect(activeItems().length).toBe(6); expect(activeItemContents()).toEqual(['3','4','5','6','7','8']) expect(activeItemIds().sort()).toEqual(['item0','item1','item2','item3','item4','item5']); scrollTo(51); expect(activeItems().length).toBe(4); expect(activeItemContents()).toEqual(['6','7','8','9']) expect(activeItemIds().sort()).toEqual(['item0','item1','item2','item5']); }); it('should start with the same items when resizing', inject(function($window) { scrollTo(26); expect(activeItems().length).toBe(6); expect(activeItemContents()).toEqual(['3','4','5','6','7','8']) expect(activeItemIds().sort()).toEqual(['item0','item1','item2','item3','item4','item5']); scrollView.__clientWidth = 200; scrollView.__clientHeight = 20; angular.element($window).triggerHandler('resize'); expect(activeItems().length).toBe(3); expect(activeItemContents()).toEqual(['3','4','5']) expect(activeItemIds().sort()).toEqual(['item3','item4','item5']); })); }); describe('vertical dynamic grid', function() { beforeEach(function() { // Odd rows 25 height, even rows 50 height setup(10, 'item-width="(16 * (1+($index % 5))) + \'%\'" ' + 'item-height="($index % 2) ? 25 : 50"', { __clientWidth: 100, __clientHeight: 60 }); }); it('should show initial screen of items', function() { // row 0, index 0: 50 height, 3 items (widths 16%, 32%, 48%) // row 1, index 3: 25 height, 2 items (widths 64%) expect(activeItems().length).toBe(4); expect(activeItemContents()).toEqual(['0','1','2','3']) var dim = activeItemDimensions(); //Row 0 expect(dim[0]).toBe('x:0,y:0,w:16,h:50'); expect(dim[1]).toBe('x:16,y:0,w:32,h:50'); expect(dim[2]).toBe('x:48,y:0,w:48,h:50'); // Row 1 expect(dim[3]).toBe('x:0,y:50,w:64,h:25'); }); it('should switch out as you scroll', function() { // Scroll past row 0, height 50 scrollTo(51); // row 1, index 3: 25 height, 1 item (width 64%) // row 2, index 4: 50 height, 2 items (width 80%, 16%) expect(activeItems().length).toBe(3); expect(activeItemContents()).toEqual(['3','4','5']) expect(activeItemIds().sort()).toEqual(['item1','item2','item3']); var dim = activeItemDimensions(); //Row 1 expect(dim[0]).toBe('x:0,y:50,w:64,h:25'); //Row 2 expect(dim[1]).toBe('x:0,y:75,w:80,h:50'); expect(dim[2]).toBe('x:80,y:75,w:16,h:50'); // row 3, index 6: 50 height, 2 items (width 32%, 48%) //Scroll past row 2 and row 3 to the end scrollTo(176); // row 4, index 8: 50 height, 1 item (width 64%) // row 5, index 9: 25 height, 2 items (width 80%, 16%) expect(activeItems().length).toBe(2); expect(activeItemContents()).toEqual(['8', '9']) var dim = activeItemDimensions(); //Row 3 expect(dim[0]).toBe('x:0,y:175,w:64,h:50'); //Row 4 expect(dim[1]).toBe('x:0,y:225,w:80,h:25'); }); it('should start with the same items when resizing', inject(function($window) { // Scroll past row 0, height 50 scrollTo(51); // row 1, index 3: 25 height, 1 item (width 64%) // row 2, index 4: 50 height, 2 items (width 80%, 16%) expect(activeItems().length).toBe(3); expect(activeItemContents()).toEqual(['3','4','5']) expect(activeItemIds().sort()).toEqual(['item1','item2','item3']); scrollView.__clientWidth = 50; scrollView.__clientHeight = 50; angular.element($window).triggerHandler('resize'); expect(activeItems().length).toBe(3); expect(activeItemContents()).toEqual(['3','4','5']) expect(activeItemIds().sort()).toEqual(['item1','item2','item3']); })); }); });