refactor(collection-repeat): performance

This commit is contained in:
Andrew
2015-02-24 12:13:00 -07:00
parent 1924b9124f
commit bb866da3d7
15 changed files with 4338 additions and 1819 deletions

View File

@@ -1,173 +1,239 @@
describe('collectionRepeat directive', function() {
describe('collectionRepeat', function() {
var dataSource, repeatManager;
var el;
beforeEach(module('ionic', function($provide) {
$provide.value(
'$collectionDataSource',
jasmine.createSpy('$dataSource').andCallFake(function(opts) {
dataSource = {
options: opts,
setData: jasmine.createSpy('setData')
};
return dataSource;
})
);
$provide.value(
'$collectionRepeatManager',
jasmine.createSpy('$repeatManager').andCallFake(function(opts) {
repeatManager = {
options: opts,
resize: jasmine.createSpy('resize')
};
return repeatManager;
})
);
$provide.decorator('$$rAF', function($delegate) {
return function mockRaf(callback) { callback(); };
});
spyOn(ionic, 'animationFrameThrottle').andCallFake(function(cb) {
return function fakeThrottled() {
cb.apply(this, arguments);
};
});
}));
function setup(attrs, scrollViewOpts) {
var el;
var scrollView;
function setup(listData, attrs, scrollViewData) {
var content = angular.element('<content>')
scrollView = angular.extend({
__content: content[0],
__clientHeight: 100,
__clientWidth: 50,
__scrollTop: 0,
__scrollLeft: 0,
__maxScrollTop: 100,
__maxScrollLeft: 50,
options: {
scrollingY: true
},
__callback: function(){},
}, 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) {
var content = $compile('<ion-content>')($rootScope.$new());
$rootScope.$apply();
angular.extend(
content.controller('$ionicScroll').scrollView.options,
scrollViewOpts || {}
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(
'<div collection-repeat="item in list" '+(attrs)+'>{{item}}</div>'
);
el = angular.element('<div ' + (attrs||'') + '></div>');
content.append(el);
$compile(el)(content.scope());
content.append(element);
content.data('$$ionicScrollController', scrollCtrl);
$rootScope.list = list;
$compile(element)($rootScope);
$rootScope.$apply();
content.triggerHandler('scroll.init');
$rootScope.$apply();
});
return el;
return element;
}
describe('errors', function() {
it('should error if scrollView is x and y', function() {
expect(function() {
setup('collection-repeat', {scrollingX:true, scrollingY:true});
}).toThrow(COLLECTION_REPEAT_SCROLLVIEW_XY_ERROR);
});
it('should error if scrollView is x and no width', function() {
expect(function() {
setup('collection-repeat collection-item-height', {
scrollingX:true,
scrollingY:false
});
}).toThrow(COLLECTION_REPEAT_ATTR_WIDTH_ERROR);
});
it('should error if scrollView is y and no height', function() {
expect(function() {
setup('collection-repeat collection-item-width', {
scrollingX:false,
scrollingY:true
});
}).toThrow(COLLECTION_REPEAT_ATTR_HEIGHT_ERROR);
});
it('should error if no repeat expression', function() {
expect(function() {
setup('collection-repeat="bad" collection-item-height="1"');
}).toThrow(COLLECTION_REPEAT_ATTR_REPEAT_ERROR.replace('%', 'bad'));
});
});
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();
}
describe('widthGetter & heightGetter', function() {
it('should work with given amounts parsed to int', function() {
var el = setup('collection-repeat="a in b" collection-item-height="\'5\'" collection-item-width="\'10\'"');
expect(dataSource.options.heightGetter()).toBe(5);
expect(dataSource.options.widthGetter()).toBe(10);
});
it('should default width of y-scroller to 100%', function() {
var el = setup('collection-repeat="a in b" collection-item-height="5"');
el.controller('$ionicScroll').scrollView.__clientWidth = 200;
expect(dataSource.options.widthGetter()).toBe(200);
});
it('should default height of x-scroller to 100%', function() {
var el = setup('collection-repeat="a in b" collection-item-width="5"', {
scrollingX: true,
scrollingY: false
function items() {
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;
});
el.controller('$ionicScroll').scrollView.__clientHeight = 199;
expect(dataSource.options.heightGetter()).toBe(199);
});
it('should work with user-inputted percentage height', function() {
var el = setup('collection-repeat="a in b" collection-item-height="\'23%\'"');
el.controller('$ionicScroll').scrollView.__clientHeight = 300;
expect(dataSource.options.heightGetter()).toEqual(0.23 * 300);
});
it('should work with user-inputted percentage width', function() {
var el = setup('collection-repeat="a in b" collection-item-width="\'23%\'"', {
scrollingX: true,
scrollingY: false
}
function activeItems() {
return items()
.filter(function(item) {
return item.isShown;
})
.sort(function(a, b) {
return a.primaryPos + a.secondaryPos > b.primaryPos + b.secondaryPos ? 1 : -1;
});
el.controller('$ionicScroll').scrollView.__clientWidth = 300;
expect(dataSource.options.widthGetter()).toEqual(0.23 * 300);
}
function activeItemContents() {
return activeItems().map(function(item) {
return item.node.innerHTML.trim();
});
});
}
function activeItemIds() {
return activeItems().map(function(item) {
return item.id;
});
}
it('should error if list is not an array and is truthy', function() {
var el = setup('collection-repeat="item in items" collection-item-height="50"');
it('should error with direction="xy" parent', function() {
expect(function() {
el.scope().$apply('items = "string"');
}).toThrow();
expect(function() {
el.scope().$apply('items = 123');
}).toThrow();
expect(function() {
el.scope().$apply('items = {}');
setup(10, '', {
options: { scrollingX: true, scrollingY: true }
});
}).toThrow();
});
it('should rerender on list change', function() {
var el = setup('collection-repeat="item in items" collection-item-height="50"');
var scrollView = el.controller('$ionicScroll').scrollView;
spyOn(scrollView, 'resize');
dataSource.setData.reset();
repeatManager.resize.reset();
el.scope().$apply('items = [ 1,2,3 ]');
expect(dataSource.setData).toHaveBeenCalledWith(el.scope().items, [], []);
expect(repeatManager.resize.callCount).toBe(1);
expect(scrollView.resize.callCount).toBe(1);
el.scope().$apply('items = null');
expect(dataSource.setData).toHaveBeenCalledWith(null, [], []);
expect(repeatManager.resize.callCount).toBe(2);
expect(scrollView.resize.callCount).toBe(2);
});
it('should rerender on window resize', function() {
var el = setup('collection-repeat="item in items" collection-item-height="50"');
var scrollView = el.controller('$ionicScroll').scrollView;
spyOn(scrollView, 'resize');
dataSource.setData.reset();
repeatManager.resize.reset();
el.scope().items = [1,2,3];
ionic.trigger('resize', { target: window });
expect(dataSource.setData).toHaveBeenCalledWith(el.scope().items, [], []);
expect(repeatManager.resize.callCount).toBe(1);
expect(scrollView.resize.callCount).toBe(1);
});
it('should rerender on scrollCtrl resize', inject(function($timeout) {
var el = setup('collection-repeat="item in items" collection-item-height="50"');
var scrollCtrl = el.controller('$ionicScroll');
repeatManager.resize.reset();
scrollCtrl.resize();
$timeout.flush();
expect(repeatManager.resize.callCount).toBe(1);
it('should error without proper collection-repeat expression', inject(function($compile, $rootScope) {
expect(function() {
$compile('<ion-content>' +
'<div collection-repeat="bad"></div>' +
'</ion-content>')($rootScope);
}).toThrow();
}));
it('$destroy', function() {
var el = setup('collection-repeat="item in items" collection-item-height="50"');
dataSource.destroy = jasmine.createSpy('dataSourceDestroy');
repeatManager.destroy = jasmine.createSpy('repeatManagerDestroy');
spyOn(ionic, 'off');
el.scope().$destroy();
expect(dataSource.destroy).toHaveBeenCalled();
expect(repeatManager.destroy).toHaveBeenCalled();
expect(ionic.off).toHaveBeenCalledWith('resize', jasmine.any(Function), window);
it('should destroy', inject(function($compile, $rootScope) {
var scope = $rootScope.$new();
var content = $compile('<ion-content>' +
'<div collection-repeat="item in items"></div>' +
'</ion-content>')(scope);
$rootScope.$apply();
content.triggerHandler('scroll.init');
scope.$destroy();
}));
describe('horizontal static list', function() {
beforeEach(function() {
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() {
expect(activeItems().length).toBe(3);
expect(activeItemContents()).toEqual(['0','1','2'])
});
it('should switch out as you scroll', function() {
expect(activeItems().length).toBe(3);
expect(activeItemContents()).toEqual(['0','1','2'])
expect(activeItemIds()).toEqual(['item_0','item_1','item_2']);
// 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(['item_1','item_2','item_0']);
// Item 1 gets sent down
scrollTo(61);
expect(activeItems().length).toBe(3);
expect(activeItemContents()).toEqual(['2','3','4'])
expect(activeItemIds()).toEqual(['item_2','item_0','item_1']);
});
it('should start with the same items when resizing', inject(function($window) {
scrollTo(31);
scrollTo(61);
expect(activeItems().length).toBe(3);
expect(activeItemContents()).toEqual(['2','3','4'])
expect(activeItemIds()).toEqual(['item_2','item_0','item_1']);
scrollView.__clientWidth = 50;
scrollView.__clientHeight = 40;
angular.element($window).triggerHandler('resize');
expect(activeItems().length).toBe(2);
expect(activeItemContents()).toEqual(['2','3'])
expect(activeItemIds()).toEqual(['item_2','item_0']);
}));
});
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(['item_0','item_1','item_2','item_3','item_4']);
// 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(['item_1','item_2','item_3','item_4','item_0']);
// Item 1 gets sent down
scrollTo(51);
expect(activeItems().length).toBe(5);
expect(activeItemContents()).toEqual(['2','3','4','5','6'])
expect(activeItemIds()).toEqual(['item_2','item_3','item_4','item_0','item_1']);
// 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(['item_0','item_1','item_2','item_3','item_4']);
});
it('should start with the same items when resizing', inject(function($window) {
setup(10);
scrollTo(26);
scrollTo(51);
expect(activeItems().length).toBe(5);
expect(activeItemContents()).toEqual(['2','3','4','5','6'])
expect(activeItemIds()).toEqual(['item_2','item_3','item_4','item_0','item_1']);
scrollView.__clientWidth = 200;
scrollView.__clientHeight = 40;
angular.element($window).triggerHandler('resize');
expect(activeItems().length).toBe(2);
expect(activeItemContents()).toEqual(['2','3'])
expect(activeItemIds()).toEqual(['item_2','item_3']);
}));
});
});

View File

@@ -1,262 +0,0 @@
describe('$collectionDataSource service', function() {
beforeEach(module('ionic'));
function setup(options, template, dataSourceOptions) {
var dataSource;
inject(function($rootScope, $compile, $collectionDataSource) {
options = angular.extend({
scope: $rootScope.$new(),
transcludeParent: angular.element('<div>'),
keyExpr: 'key',
listExpr: 'list',
heightGetter: function() {
return 1;
},
widthGetter: function() {
return 1;
}
}, options || {});
options.transcludeFn = options.transcludeFn || function(scope, cb) {
cb( $compile(template || '<div>')(scope) );
};
dataSource = new $collectionDataSource(options);
dataSource.dataStartIndex = 0;
dataSource.beforeSiblings = [];
dataSource.afterSiblings = [];
if (dataSourceOptions && dataSourceOptions.afterSiblings) {
dataSource.afterSiblings = dataSourceOptions.afterSiblings;
}
});
return dataSource;
}
it('should have properties', inject(function($collectionDataSource) {
var source = setup({
scope: 1,
transcludeFn: 2,
transcludeParent: 3,
keyExpr: 4,
listExpr: 5,
heightGetter: 7,
widthGetter: 8
});
expect(source.scope).toBe(1);
expect(source.transcludeFn).toBe(2);
expect(source.transcludeParent).toBe(3);
expect(source.keyExpr).toBe(4);
expect(source.listExpr).toBe(5);
expect(source.heightGetter).toBe(7);
expect(source.widthGetter).toBe(8);
}));
it('.calculateDataDimensions()', function() {
function widthGetter(scope, locals) {
return locals.$index + locals.item + 'w';
}
function heightGetter(scope, locals) {
return locals.$index + locals.item + 'h';
}
var source = setup({
keyExpr: 'item',
widthGetter: widthGetter,
heightGetter: heightGetter
});
source.data = ['a', 'b', 'c'];
expect(source.dimensions).toEqual([]);
source.calculateDataDimensions();
expect(source.dimensions[0]).toEqual({
width: '0aw',
height: '0ah'
});
expect(source.dimensions[1]).toEqual({
width: '1bw',
height: '1bh'
});
expect(source.dimensions[2]).toEqual({
width: '2cw',
height: '2ch'
});
});
describe('.createItem()', function() {
it('should return item with new scope and transclude', function() {
var source = setup();
var item = source.createItem();
expect(item.scope.$parent).toBe(source.scope);
expect(item.element).toBeTruthy();
expect(item.element.scope()).toBe(item.scope);
expect(item.element.parent()[0]).toBe(source.transcludeParent[0]);
});
});
describe('.getItem()', function() {
it('should return attachedItems[index] if available', function() {
var source = setup();
var item = {};
source.attachedItems['123'] = item;
expect(source.getItem('123')).toBe(item);
});
it('should return backupItemsArray item if available, and reconnect the item', function() {
var source = setup();
var item = {
scope: {},
};
spyOn(ionic.Utils, 'reconnectScope');
source.backupItemsArray = [item];
expect(source.getItem('123')).toBe(item);
expect(ionic.Utils.reconnectScope).toHaveBeenCalledWith(item.scope);
});
it('should last resort create an item', function() {
var source = setup();
var item = {};
spyOn(source, 'createItem').andReturn(item);
expect(source.getItem('123')).toBe(item);
});
});
describe('.attachItemAtIndex()', function() {
it('should return a value with index values set and put in attachedItems', inject(function($rootScope) {
var source = setup({
keyExpr: 'value'
}, null, {afterSiblings:[{
width: 50,
height: 50,
element: angular.element('<div class="tail"></div>'),
scope: null,
isOutside: true
}]});
source.data = ['a', 'b', 'c'];
source.dimensions = ['a','b','c'];
spyOn(source, 'getItem').andCallFake(function() {
return { scope: $rootScope.$new() };
});
var item1 = source.attachItemAtIndex(0);
expect(item1.scope.value).toEqual('a');
expect(item1.scope.$index).toBe(0);
expect(item1.scope.$first).toBe(true);
expect(item1.scope.$last).toBe(false);
expect(item1.scope.$middle).toBe(false);
expect(item1.scope.$odd).toBe(false);
var item2 = source.attachItemAtIndex(1);
expect(item2.scope.value).toEqual('b');
expect(item2.scope.$index).toBe(1);
expect(item2.scope.$first).toBe(false);
expect(item2.scope.$last).toBe(false);
expect(item2.scope.$middle).toBe(true);
expect(item2.scope.$odd).toBe(true);
var item3 = source.attachItemAtIndex(2);
expect(item3.scope.value).toEqual('c');
expect(item3.scope.$index).toBe(2);
expect(item3.scope.$first).toBe(false);
expect(item3.scope.$last).toBe(true);
expect(item3.scope.$middle).toBe(false);
expect(item3.scope.$odd).toBe(false);
expect(source.attachedItems).toEqual({
0: item1,
1: item2,
2: item3
});
var item4 = source.attachItemAtIndex(3);
expect(item4.width).toEqual(50);
expect(item4.isOutside).toEqual(true);
}));
});
describe('.detachItem()', function() {
it('should detach item and add to backup array if there is room', function() {
var source = setup();
var item = {
element: angular.element('<div>'),
scope: {},
index: 1
};
source.backupItemsArray = [];
source.attachedItems[1] = item;
spyOn(ionic.Utils, 'disconnectScope');
source.detachItem(item);
expect(source.attachedItems).toEqual({});
expect(source.backupItemsArray).toEqual([item]);
expect(ionic.Utils.disconnectScope).toHaveBeenCalledWith(item.scope);
});
it('should remove element from parent and disconnectScope if backupItemsArray is full', function() {
var source = setup();
spyOn(source, 'destroyItem');
source.BACKUP_ITEMS_LENGTH = 0;
var item = { index: 0 };
source.attachedItems[item.index] = item;
source.detachItem(item);
expect(source.destroyItem).toHaveBeenCalledWith(item);
expect(source.attachedItems).toEqual({});
});
});
describe('.destroyItem()', function() {
it('should remove element and destroy scope', function() {
var source = setup();
var element = angular.element('<div>');
var parent = angular.element('<div>').append(element);
var item = {
element: element,
scope: {}
};
var destroySpy = item.scope.$destroy = jasmine.createSpy('$destroy');
expect(element[0].parentNode).toBe(parent[0]);
source.destroyItem(item);
expect(element[0].parentNode).toBeFalsy();
expect(destroySpy).toHaveBeenCalled();
expect(item.scope).toBe(null);
expect(item.element).toBe(null);
});
});
describe('.getLength()', function() {
it('should return 0 by default', function() {
var source = setup();
source.dimensions = null;
expect(source.getLength()).toBe(0);
});
it('should return dimensions length', function() {
var source = setup();
source.dimensions = [1,2,3];
expect(source.getLength()).toBe(3);
source.dimensions = [];
expect(source.getLength()).toBe(0);
});
});
describe('.setData()', function() {
it('should set data and calculateDataDimensions()', function() {
var source = setup();
spyOn(source, 'calculateDataDimensions');
expect(source.data).toEqual([]);
source.setData([1,2,3]);
expect(source.data).toEqual([1,2,3]);
expect(source.calculateDataDimensions).toHaveBeenCalled();
});
it('should default falsy to empty array', function() {
var source = setup();
source.data = 'none';
source.setData(null);
expect(source.data).toEqual([]);
});
});
});

View File

@@ -1,501 +0,0 @@
describe('collectionRepeatManager service', function() {
beforeEach(module('ionic'));
var afThrottle;
beforeEach(function() {
afThrottle = ionic.animationFrameThrottle;
ionic.animationFrameThrottle = function(cb) {
return function() {
cb.apply(this, arguments);
};
};
});
afterEach(function() {
ionic.animationFrameThrottle = afThrottle;
});
function setup(options, dataSourceOptions, scrollViewOptions) {
var manager;
inject(function($collectionDataSource, $rootScope, $compile, $collectionRepeatManager) {
var dataSource = new $collectionDataSource(angular.extend({
scope: $rootScope.$new(),
transcludeParent: angular.element('<div>'),
transcludeFn: function(scope, cb) {
cb($compile('<div></div>')(scope));
},
keyExpr: 'key',
listExpr: 'list',
heightGetter: function() {
return 1;
},
widthGetter: function() {
return 1;
}
}, dataSourceOptions || {}));
var el = angular.element('<div scroll-container>')
.append(angular.element('<div scroll-content>'));
options = angular.extend({
dataSource: dataSource,
element: el,
scrollView: new ionic.views.Scroll(angular.extend({
el: el[0]
}, scrollViewOptions || {})),
}, options || {});
manager = new $collectionRepeatManager(options);
});
return manager;
}
it('should have properties', function() {
var manager = setup();
expect(manager.dataSource).toBeTruthy();
expect(manager.scrollView).toBeTruthy();
expect(manager.element[0]).toBe(manager.scrollView.__container);
});
it('should be isVertical if scrollingY', function() {
var manager = setup();
expect(manager.scrollView.options.scrollingY).toBe(true);
expect(manager.isVertical).toBe(true);
});
it('should be !isVertical if scrollingX', function() {
var manager = setup({}, {}, {
scrollingX: true,
scrollingY: false
});
expect(manager.scrollView.options.scrollingX).toBe(true);
expect(manager.scrollView.options.scrollingY).toBe(false);
expect(manager.isVertical).toBe(false);
});
describe('isVertical', function() {
it('should set getContentHeight', function() {
var manager = setup();
manager.viewportSize = 55;
expect(manager.scrollView.options.getContentHeight()).toBe(55);
});
it('.scrollValue()', function() {
var manager = setup();
manager.scrollView.__scrollTop = 33;
expect(manager.scrollValue()).toBe(33);
});
it('.scrollMaxValue()', function() {
var manager = setup();
manager.scrollView.__maxScrollTop = 123;
expect(manager.scrollMaxValue()).toBe(123);
});
it('.scrollSize()', function() {
var manager = setup();
manager.scrollView.__clientHeight = 12;
expect(manager.scrollSize()).toBe(12);
});
it('.secondaryScrollSize()', function() {
var manager = setup();
manager.scrollView.__clientWidth = 999;
expect(manager.secondaryScrollSize()).toBe(999);
});
it('.transformString()', function() {
var manager = setup();
expect(manager.transformString(3,6)).toBe('translate3d(6px,3px,0)');
});
it('.primaryDimension()', function() {
var manager = setup();
expect(manager.primaryDimension({height: 100})).toBe(100);
});
it('.secondaryDimension()', function() {
var manager = setup();
expect(manager.secondaryDimension({width: 99})).toBe(99);
});
});
describe('!isVertical', function() {
it('should set getContentWidth', function() {
var manager = setup({}, {}, { scrollingX: true, scrollingY: false });
manager.viewportSize = 55;
expect(manager.scrollView.options.getContentWidth()).toBe(55);
});
it('.scrollValue()', function() {
var manager = setup({}, {}, { scrollingX: true, scrollingY: false });
manager.scrollView.__scrollLeft = 33;
expect(manager.scrollValue()).toBe(33);
});
it('.scrollMaxValue()', function() {
var manager = setup({}, {}, { scrollingX: true, scrollingY: false });
manager.scrollView.__maxScrollLeft = 123;
expect(manager.scrollMaxValue()).toBe(123);
});
it('.scrollSize()', function() {
var manager = setup({}, {}, { scrollingX: true, scrollingY: false });
manager.scrollView.__clientWidth = 12;
expect(manager.scrollSize()).toBe(12);
});
it('.secondaryScrollSize()', function() {
var manager = setup({}, {}, { scrollingX: true, scrollingY: false });
manager.scrollView.__clientHeight = 999;
expect(manager.secondaryScrollSize()).toBe(999);
});
it('.transformString()', function() {
var manager = setup({}, {}, { scrollingX: true, scrollingY: false });
expect(manager.transformString(3,6)).toBe('translate3d(3px,6px,0)');
});
it('.primaryDimension()', function() {
var manager = setup({}, {}, { scrollingX: true, scrollingY: false });
expect(manager.primaryDimension({width: 100})).toBe(100);
});
it('.secondaryDimension()', function() {
var manager = setup({}, {}, { scrollingX: true, scrollingY: false });
expect(manager.secondaryDimension({height: 99})).toBe(99);
});
});
describe('.calculateDimensions()', function() {
it('should work with 1 item per space', function() {
var manager = setup();
manager.dataSource.dimensions = [
{ width: 100, height: 20 },
{ width: 100, height: 30 },
{ width: 100, height: 40 },
{ width: 100, height: 50 }
];
manager.secondaryScrollSize = function() {
return 100;
};
var result = manager.calculateDimensions().dimensions;
expect(result[0].primarySize).toBe(20);
expect(result[0].secondarySize).toBe(100);
expect(result[0].primaryPos).toBe(0);
expect(result[0].secondaryPos).toBe(0);
expect(result[1].primarySize).toBe(30);
expect(result[1].secondarySize).toBe(100);
expect(result[1].primaryPos).toBe(20);
expect(result[1].secondaryPos).toBe(0);
expect(result[2].primarySize).toBe(40);
expect(result[2].secondarySize).toBe(100);
expect(result[2].primaryPos).toBe(50);
expect(result[2].secondaryPos).toBe(0);
expect(result[3].primarySize).toBe(50);
expect(result[3].secondarySize).toBe(100);
expect(result[3].primaryPos).toBe(90);
expect(result[3].secondaryPos).toBe(0);
});
it('should work with a grid', function() {
var manager = setup();
manager.dataSource.dimensions = [
{ width: 30, height: 30 },
{ width: 35, height: 30 },
{ width: 40, height: 30 },
{ width: 30, height: 30 },
{ width: 20, height: 30 },
{ width: 35, height: 30 },
];
manager.secondaryScrollSize = function() {
return 90;
};
var result = manager.calculateDimensions().dimensions;
expect(result[0].primarySize).toBe(30);
expect(result[0].secondarySize).toBe(30);
expect(result[0].primaryPos).toBe(0);
expect(result[0].secondaryPos).toBe(0);
expect(result[1].primarySize).toBe(30);
expect(result[1].secondarySize).toBe(35);
expect(result[1].primaryPos).toBe(0);
expect(result[1].secondaryPos).toBe(30);
expect(result[2].primarySize).toBe(30);
expect(result[2].secondarySize).toBe(40);
expect(result[2].primaryPos).toBe(30);
expect(result[2].secondaryPos).toBe(0);
expect(result[3].primarySize).toBe(30);
expect(result[3].secondarySize).toBe(30);
expect(result[3].primaryPos).toBe(30);
expect(result[3].secondaryPos).toBe(40);
expect(result[4].primarySize).toBe(30);
expect(result[4].secondarySize).toBe(20);
expect(result[4].primaryPos).toBe(30);
expect(result[4].secondaryPos).toBe(70);
expect(result[5].primarySize).toBe(30);
expect(result[5].secondarySize).toBe(35);
expect(result[5].primaryPos).toBe(60);
expect(result[5].secondaryPos).toBe(0);
});
});
describe('.resize()', function() {
it('should work without data', function() {
var manager = setup();
spyOn(manager, 'render');
spyOn(manager, 'calculateDimensions').andReturn({
dimensions: [],
beforeSize: 0,
totalSize: 0
});
spyOn(manager, 'setCurrentIndex');
manager.resize();
expect(manager.dimensions).toEqual([]);
expect(manager.viewportSize).toBe(0);
expect(manager.setCurrentIndex).toHaveBeenCalledWith(0);
expect(manager.render).toHaveBeenCalledWith(true);
});
it('should work with data', function() {
var manager = setup();
spyOn(manager, 'calculateDimensions').andReturn({
dimensions: [{
primaryPos: 100, primarySize: 30
}],
beforeSize: 0,
totalSize: 130
});
manager.resize();
expect(manager.dataSource.transcludeParent[0].style.height).toBe('130px');
expect(manager.viewportSize).toBe(130);
});
});
describe('.setCurrentIndex()', function() {
it('without prev or next', function() {
var manager = setup();
spyOn(manager.dataSource, 'getLength').andReturn(0);
manager.setCurrentIndex(0);
expect(manager.currentIndex).toBe(0);
expect(manager.hasPrevIndex).toBe(false);
expect(manager.previousPos).toBeFalsy();
expect(manager.hasNextIndex).toBe(false);
expect(manager.nextPos).toBeFalsy();
});
it('with next', function() {
var manager = setup();
spyOn(manager.dataSource, 'getLength').andReturn(2);
manager.dimensions = [{ primaryPos: 0 ,primarySize: 25}, { primaryPos: 25, primarySize: 35 }];
manager.setCurrentIndex(0);
expect(manager.currentIndex).toBe(0);
expect(manager.hasPrevIndex).toBe(false);
expect(manager.previousPos).toBeFalsy();
expect(manager.hasNextIndex).toBe(true);
expect(manager.nextPos).toBe(25);
});
it('with prev', function() {
var manager = setup();
spyOn(manager.dataSource, 'getLength').andReturn(2);
manager.dimensions = [{ primaryPos: 0 , primarySize: 25 }, { primaryPos: 25, primarySize: 25 }];
manager.setCurrentIndex(1);
expect(manager.currentIndex).toBe(1);
expect(manager.hasPrevIndex).toBe(true);
expect(manager.previousPos).toBe(0);
expect(manager.hasNextIndex).toBe(false);
expect(manager.nextPos).toBeFalsy();
});
it('with next and prev', function() {
var manager = setup();
spyOn(manager.dataSource, 'getLength').andReturn(3);
manager.dimensions = [{ primarySize: 25, primaryPos: 0 }, { primarySize: 25, primaryPos: 25 }, { primarySize: 25, primaryPos: 50 }];
manager.setCurrentIndex(1);
expect(manager.currentIndex).toBe(1);
expect(manager.hasPrevIndex).toBe(true);
expect(manager.previousPos).toBe(0);
expect(manager.hasNextIndex).toBe(true);
expect(manager.nextPos).toBe(50);
});
});
describe('.renderScroll()', function() {
it('should pass the values to __$callback', function() {
var manager = setup();
spyOn(manager.scrollView, '__$callback');
manager.renderScroll(1, 2, 3, 4);
expect(manager.scrollView.__$callback).toHaveBeenCalledWith(1, 2, 3, 4);
});
});
describe('.renderIfNeeded()', function() {
it('should render if >= nextPos', function() {
var manager = setup();
spyOn(manager, 'render');
manager.hasNextIndex = true;
manager.nextPos = 30;
manager.renderIfNeeded(20);
expect(manager.render).not.toHaveBeenCalled();
manager.renderIfNeeded(30);
expect(manager.render).toHaveBeenCalled();
});
it('should render if < previousPos', function() {
var manager = setup();
spyOn(manager, 'render');
manager.hasPrevIndex = true;
manager.previousPos = 50;
manager.renderIfNeeded(60);
expect(manager.render).not.toHaveBeenCalled();
manager.renderIfNeeded(50);
expect(manager.render).not.toHaveBeenCalled();
manager.renderIfNeeded(49);
expect(manager.render).toHaveBeenCalled();
});
});
describe('.getIndexForScrollValue()', function() {
it('should scroll until it finds right position', function() {
var manager = setup();
manager.dimensions = [
{ primaryPos: 0 },
{ primaryPos: 100 },
{ primaryPos: 200 },
{ primaryPos: 300 },
{ primaryPos: 400 },
{ primaryPos: 500 }
];
expect(manager.getIndexForScrollValue(3, 50)).toBe(1);
expect(manager.getIndexForScrollValue(1, 450)).toBe(4);
//bounds
expect(manager.getIndexForScrollValue(0, 1000)).toBe(5);
expect(manager.getIndexForScrollValue(2, -100)).toBe(0);
});
});
describe('.render()', function() {
it('currentIndex >= length should remove all and return', function() {
var manager = setup();
manager.renderedItems = {'a':1, 'b':1};
spyOn(manager, 'removeItem');
spyOn(manager.dataSource, 'getLength').andReturn(0);
manager.currentIndex = 1;
manager.render();
expect(manager.removeItem).toHaveBeenCalledWith('a');
expect(manager.removeItem).toHaveBeenCalledWith('b');
});
it('shouldRedrawAll should remove all', function() {
var manager = setup();
manager.renderedItems = {'a':1, 'b':1};
manager.currentIndex = 0;
spyOn(manager, 'removeItem');
manager.render(true);
expect(manager.removeItem).toHaveBeenCalledWith('a');
expect(manager.removeItem).toHaveBeenCalledWith('b');
});
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 extra row at end
// expect(Object.keys(manager.renderedItems).length).toBe(24);
// for (var i = 0; i < 20; i++) {
// expect(manager.renderedItems[i]).toBe(true);
// }
// expect(manager.renderedItems[20]).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 = 37; //start + 17 + 6
// expect(Object.keys(manager.renderedItems).length).toBe(24);
// 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();
// });
});
describe('.renderItem()', function() {
it('should attachItemAtIndex and set the element transform', function() {
var manager = setup();
var item = {
element: angular.element('<div>')
};
spyOn(item.element, 'css');
spyOn(manager.dataSource, 'attachItemAtIndex').andReturn(item);
manager.renderItem(0, 33, 44);
expect(manager.dataSource.attachItemAtIndex).toHaveBeenCalledWith(0);
expect(item.element.css).toHaveBeenCalledWith(
ionic.CSS.TRANSFORM,
manager.transformString(33, 44)
);
expect(manager.renderedItems[0]).toBe(item);
});
});
describe('.removeItem()', function() {
it('should detachItem', function() {
var manager = setup();
var item = {};
manager.renderedItems[0] = item;
spyOn(manager, 'removeItem').andCallThrough();
spyOn(manager.dataSource, 'detachItem');
manager.removeItem(0);
expect(manager.renderedItems).toEqual({});
});
});
});