import {VirtualScroll} from '../../../../src/components/virtual-scroll/virtual-scroll'; import {VirtualCell, VirtualData, VirtualNode} from '../../../../src/components/virtual-scroll/virtual-util'; import {processRecords, populateNodeData, initReadNodes, getVirtualHeight, adjustRendered} from '../../../../src/components/virtual-scroll/virtual-util'; export function run() { describe('VirtualScroll', () => { beforeEach(() => { records = [0,1,2,3,4,5,6,7,8,9]; cells = []; nodes = []; headerFn = null; footerFn = null; var viewportWidth = 300; data = { viewWidth: viewportWidth, viewHeight: 600, itmWidth: viewportWidth, itmHeight: HEIGHT_ITEM, hdrWidth: viewportWidth, hdrHeight: HEIGHT_HEADER, ftrWidth: viewportWidth, ftrHeight: HEIGHT_FOOTER }; window.getComputedStyle = function(element) { var styles: any = { marginTop: '0px', marginRight: '0px', marginBottom: '0px', marginLeft: '0px' } return styles; }; }); describe('processRecords', () => { it('should load data for 100% width items', () => { records = [0,1,2,3,4] let stopAtHeight = 200; processRecords(stopAtHeight, records, cells, headerFn, footerFn, data); expect(cells.length).toBe(4); expect(cells[0].record).toBe(0); expect(cells[0].row).toBe(0); expect(cells[0].top).toBe(HEIGHT_ITEM * 0); expect(cells[0].height).toBe(HEIGHT_ITEM); expect(cells[0].data).toBeUndefined(); expect(cells[0].tmpl).toBe(TEMPLATE_ITEM); expect(cells[1].row).toBe(1); expect(cells[1].top).toBe(HEIGHT_ITEM * 1); expect(cells[1].height).toBe(HEIGHT_ITEM); expect(cells[2].row).toBe(2); expect(cells[2].top).toBe(HEIGHT_ITEM * 2); expect(cells[2].height).toBe(HEIGHT_ITEM); expect(cells[3].row).toBe(3); expect(cells[3].top).toBe(HEIGHT_ITEM * 3); expect(cells[3].height).toBe(HEIGHT_ITEM); }); it('should load data for 30% width items', () => { records = [0,1,2,3,4]; let stopAtHeight = 1000; data.viewWidth = 300; data.itmWidth = 90; // 30%, 3 per row data.hdrWidth = data.viewWidth; // 100%, 1 per row data.ftrWidth = data.viewWidth; // 100%, 1 per row headerFn = function(record: any) { return (record === 0) ? 'Header' : null; }; footerFn = function(record: any) { return (record === 4) ? 'Footer' : null; }; processRecords(stopAtHeight, records, cells, headerFn, footerFn, data); expect(cells.length).toBe(7); expect(cells[0].row).toBe(0); expect(cells[0].width).toBe(data.viewWidth); expect(cells[0].height).toBe(HEIGHT_HEADER); expect(cells[0].top).toBe(0); expect(cells[0].left).toBe(0); expect(cells[0].tmpl).toBe(TEMPLATE_HEADER); expect(cells[0].data).toBe('Header'); expect(cells[0].record).toBe(0); expect(cells[1].row).toBe(1); expect(cells[1].width).toBe(data.itmWidth); expect(cells[1].height).toBe(HEIGHT_ITEM); expect(cells[1].top).toBe(HEIGHT_HEADER); expect(cells[1].left).toBe(data.itmWidth * 0); expect(cells[1].tmpl).toBe(TEMPLATE_ITEM); expect(cells[1].data).toBeUndefined(); expect(cells[1].record).toBe(0); expect(cells[2].row).toBe(1); expect(cells[2].width).toBe(data.itmWidth); expect(cells[2].height).toBe(HEIGHT_ITEM); expect(cells[2].top).toBe(HEIGHT_HEADER); expect(cells[2].left).toBe(data.itmWidth * 1); expect(cells[2].tmpl).toBe(TEMPLATE_ITEM); expect(cells[2].data).toBeUndefined(); expect(cells[2].record).toBe(1); expect(cells[3].row).toBe(1); expect(cells[3].width).toBe(data.itmWidth); expect(cells[3].height).toBe(HEIGHT_ITEM); expect(cells[3].top).toBe(HEIGHT_HEADER); expect(cells[3].left).toBe(data.itmWidth * 2); expect(cells[3].tmpl).toBe(TEMPLATE_ITEM); expect(cells[3].data).toBeUndefined(); expect(cells[3].record).toBe(2); expect(cells[4].row).toBe(2); expect(cells[4].width).toBe(data.itmWidth); expect(cells[4].height).toBe(HEIGHT_ITEM); expect(cells[4].top).toBe(HEIGHT_HEADER + HEIGHT_ITEM); expect(cells[4].left).toBe(data.itmWidth * 0); expect(cells[4].tmpl).toBe(TEMPLATE_ITEM); expect(cells[4].data).toBeUndefined(); expect(cells[4].record).toBe(3); expect(cells[5].row).toBe(2); expect(cells[5].width).toBe(data.itmWidth); expect(cells[5].height).toBe(HEIGHT_ITEM); expect(cells[5].top).toBe(HEIGHT_HEADER + HEIGHT_ITEM); expect(cells[5].left).toBe(data.itmWidth * 1); expect(cells[5].tmpl).toBe(TEMPLATE_ITEM); expect(cells[5].data).toBeUndefined(); expect(cells[5].record).toBe(4); expect(cells[6].row).toBe(3); expect(cells[6].width).toBe(data.ftrWidth); expect(cells[6].height).toBe(HEIGHT_FOOTER); expect(cells[6].top).toBe(HEIGHT_HEADER + HEIGHT_ITEM + HEIGHT_ITEM); expect(cells[6].left).toBe(0); expect(cells[6].tmpl).toBe(TEMPLATE_FOOTER); expect(cells[6].data).toBe('Footer'); expect(cells[6].record).toBe(4); }); it('should process more data', () => { records = [0,1,2,3,4,5,6,7,8,9]; let stopAtHeight = 100; data.viewWidth = 200; data.itmWidth = 90; // 2 per row data.hdrWidth = data.viewWidth; // 100%, 1 per row headerFn = function(record: any) { return (record === 0) ? 'Header' : null; }; processRecords(stopAtHeight, records, cells, headerFn, footerFn, data); expect(cells.length).toBe(5); expect(cells[0].row).toBe(0); expect(cells[0].top).toBe(0); expect(cells[0].left).toBe(0); expect(cells[0].tmpl).toBe(TEMPLATE_HEADER); expect(cells[0].record).toBe(0); expect(cells[1].row).toBe(1); expect(cells[1].top).toBe(HEIGHT_HEADER); expect(cells[1].left).toBe(data.itmWidth * 0); expect(cells[1].tmpl).toBe(TEMPLATE_ITEM); expect(cells[1].record).toBe(0); stopAtHeight = 150; processRecords(stopAtHeight, records, cells, headerFn, footerFn, data); expect(cells[2].row).toBe(1); expect(cells[2].top).toBe(HEIGHT_HEADER); expect(cells[2].left).toBe(data.itmWidth * 1); expect(cells[2].tmpl).toBe(TEMPLATE_ITEM); expect(cells[2].record).toBe(1); expect(cells.length).toBe(9); expect(cells[3].row).toBe(2); expect(cells[3].top).toBe(HEIGHT_HEADER + HEIGHT_ITEM); expect(cells[3].left).toBe(data.itmWidth * 0); expect(cells[3].tmpl).toBe(TEMPLATE_ITEM); expect(cells[3].record).toBe(2); stopAtHeight = 20000; processRecords(stopAtHeight, records, cells, headerFn, footerFn, data); expect(cells.length).toBe(11); expect(cells[5].row).toBe(3); expect(cells[5].top).toBe(HEIGHT_HEADER + HEIGHT_ITEM + HEIGHT_ITEM); expect(cells[5].left).toBe(data.itmWidth * 0); expect(cells[5].tmpl).toBe(TEMPLATE_ITEM); expect(cells[5].record).toBe(4); }); }); describe('populateNodeData', () => { it('should skip already rendered, and create nodes', () => { cells = [ {row: 0, tmpl: TEMPLATE_ITEM}, {row: 1, tmpl: TEMPLATE_ITEM}, {row: 2, tmpl: TEMPLATE_HEADER}, {row: 3, tmpl: TEMPLATE_ITEM}, {row: 4, tmpl: TEMPLATE_FOOTER}, {row: 5, tmpl: TEMPLATE_ITEM}, {row: 6, tmpl: TEMPLATE_ITEM, reads: 0} ]; nodes = [ {cell: 0, tmpl: TEMPLATE_ITEM, view: getView()}, {cell: 1, tmpl: TEMPLATE_ITEM, view: getView()}, {cell: 2, tmpl: TEMPLATE_HEADER, view: getView()}, {cell: 3, tmpl: TEMPLATE_ITEM, view: getView()}, ]; let startCellIndex = 2; let endCellIndex = 5; populateNodeData(startCellIndex, endCellIndex, data.viewWidth, true, cells, records, nodes, viewContainer, itmTmp, hdrTmp, ftrTmp, false); expect(nodes.length).toBe(5); // first stays unchanged expect(nodes[0].cell).toBe(0); expect(nodes[1].cell).toBe(5); expect(nodes[2].cell).toBe(2); expect(nodes[3].cell).toBe(3); expect(nodes[4].cell).toBe(4); }); it('should create nodes', () => { cells = [ {row: 0, tmpl: TEMPLATE_ITEM}, {row: 1, tmpl: TEMPLATE_ITEM}, {row: 2, tmpl: TEMPLATE_HEADER}, {row: 3, tmpl: TEMPLATE_ITEM}, {row: 4, tmpl: TEMPLATE_FOOTER}, {row: 5, tmpl: TEMPLATE_ITEM}, {row: 6, tmpl: TEMPLATE_ITEM} ]; let startCellIndex = 2; let endCellIndex = 4; populateNodeData(startCellIndex, endCellIndex, data.viewWidth, true, cells, records, nodes, viewContainer, itmTmp, hdrTmp, ftrTmp, true); expect(nodes.length).toBe(6); expect(nodes[0].cell).toBe(2); expect(nodes[1].cell).toBe(3); expect(nodes[2].cell).toBe(4); expect(nodes[0].tmpl).toBe(TEMPLATE_HEADER); expect(nodes[1].tmpl).toBe(TEMPLATE_ITEM); expect(nodes[2].tmpl).toBe(TEMPLATE_FOOTER); }); }); describe('initReadNodes', () => { it('should get all the row heights w/ 30% width rows', () => { let firstTop = 3; nodes = [ {cell: 0, tmpl: TEMPLATE_HEADER, view: getView(data.viewWidth, HEIGHT_HEADER, firstTop, 0)}, {cell: 1, tmpl: TEMPLATE_ITEM, view: getView(90, HEIGHT_ITEM, HEIGHT_HEADER + firstTop, 0)}, {cell: 2, tmpl: TEMPLATE_ITEM, view: getView(90, HEIGHT_ITEM, HEIGHT_HEADER + firstTop, 90)}, {cell: 3, tmpl: TEMPLATE_ITEM, view: getView(90, HEIGHT_ITEM, HEIGHT_HEADER + firstTop, 180)}, {cell: 4, tmpl: TEMPLATE_ITEM, view: getView(90, HEIGHT_ITEM, HEIGHT_HEADER + HEIGHT_ITEM + firstTop, 0)}, {cell: 5, tmpl: TEMPLATE_FOOTER, view: getView(data.viewWidth, HEIGHT_FOOTER, HEIGHT_HEADER + HEIGHT_ITEM + HEIGHT_ITEM + firstTop, 0)}, ]; cells = [ {row: 0, tmpl: TEMPLATE_HEADER, reads: 0}, {row: 0, tmpl: TEMPLATE_ITEM, reads: 0}, {row: 0, tmpl: TEMPLATE_ITEM, reads: 0}, {row: 0, tmpl: TEMPLATE_ITEM, reads: 0}, {row: 0, tmpl: TEMPLATE_ITEM, reads: 0}, {row: 0, tmpl: TEMPLATE_FOOTER, reads: 0}, ]; initReadNodes(nodes, cells, data); expect(cells[0].top).toBe(firstTop); expect(cells[0].left).toBe(0); expect(cells[0].width).toBe(data.viewWidth); expect(cells[0].height).toBe(HEIGHT_HEADER); expect(cells[1].top).toBe(firstTop + HEIGHT_HEADER); expect(cells[1].left).toBe(0); expect(cells[1].width).toBe(90); expect(cells[1].height).toBe(HEIGHT_ITEM); expect(cells[2].top).toBe(firstTop + HEIGHT_HEADER); expect(cells[2].left).toBe(data.itmWidth); expect(cells[2].width).toBe(90); expect(cells[2].height).toBe(HEIGHT_ITEM); expect(cells[3].top).toBe(firstTop + HEIGHT_HEADER); expect(cells[3].left).toBe(data.itmWidth * 2); expect(cells[3].width).toBe(90); expect(cells[3].height).toBe(HEIGHT_ITEM); expect(cells[4].top).toBe(firstTop + HEIGHT_HEADER + HEIGHT_ITEM); expect(cells[4].left).toBe(0); expect(cells[4].width).toBe(90); expect(cells[4].height).toBe(HEIGHT_ITEM); expect(cells[5].top).toBe(firstTop + HEIGHT_HEADER + HEIGHT_ITEM + HEIGHT_ITEM); expect(cells[5].left).toBe(0); expect(cells[5].width).toBe(data.viewWidth); expect(cells[5].height).toBe(HEIGHT_FOOTER); }); it('should get all the row heights w/ all 100% width rows', () => { nodes = [ {cell: 0, tmpl: TEMPLATE_HEADER, view: getView(data.viewWidth, HEIGHT_HEADER, 0, 0)}, {cell: 1, tmpl: TEMPLATE_ITEM, view: getView(data.viewWidth, HEIGHT_ITEM, HEIGHT_HEADER, 0)}, {cell: 2, tmpl: TEMPLATE_ITEM, view: getView(data.viewWidth, HEIGHT_ITEM, HEIGHT_HEADER + HEIGHT_ITEM, 0)}, {cell: 3, tmpl: TEMPLATE_ITEM, view: getView(data.viewWidth, HEIGHT_ITEM, HEIGHT_HEADER + HEIGHT_ITEM + HEIGHT_ITEM, 0)}, {cell: 4, tmpl: TEMPLATE_FOOTER, view: getView(data.viewWidth, HEIGHT_FOOTER, HEIGHT_HEADER + HEIGHT_ITEM + HEIGHT_ITEM + HEIGHT_ITEM, 0)}, ]; cells = [ {row: 0, tmpl: TEMPLATE_HEADER, reads: 0}, {row: 1, tmpl: TEMPLATE_ITEM, reads: 0}, {row: 2, tmpl: TEMPLATE_ITEM, reads: 0}, {row: 3, tmpl: TEMPLATE_ITEM, reads: 0}, {row: 4, tmpl: TEMPLATE_FOOTER, reads: 0}, ]; initReadNodes(nodes, cells, data); expect(cells[0].top).toBe(0); expect(cells[0].height).toBe(HEIGHT_HEADER); expect(cells[0].reads).toBe(1); expect(cells[1].top).toBe(HEIGHT_HEADER); expect(cells[1].height).toBe(HEIGHT_ITEM); expect(cells[2].top).toBe(HEIGHT_HEADER + HEIGHT_ITEM); expect(cells[2].height).toBe(HEIGHT_ITEM); expect(cells[3].top).toBe(HEIGHT_HEADER + HEIGHT_ITEM + HEIGHT_ITEM); expect(cells[3].height).toBe(HEIGHT_ITEM); expect(cells[4].top).toBe(HEIGHT_HEADER + HEIGHT_ITEM + HEIGHT_ITEM + HEIGHT_ITEM); expect(cells[4].height).toBe(HEIGHT_FOOTER); }); }); describe('adjustRendered', () => { it('should adjust when all the way to the top, scrolling down', () => { for (var i = 0; i < 100; i++) { cells.push({top: i, height: 1, row: i}); } data.scrollDiff = 1; data.viewHeight = 20; data.itmHeight = 1; data.renderHeight = 40; data.topViewCell = 0; data.bottomViewCell = 19; adjustRendered(cells, data); expect(data.topCell).toBe(0); expect(data.bottomCell).toBe(38); }); it('should adjust when in the middle, scrolling down', () => { for (var i = 0; i < 100; i++) { cells.push({top: i, height: 1, row: i}); } data.scrollDiff = 1; data.viewHeight = 20; data.itmHeight = 1; data.renderHeight = 40; data.topViewCell = 30; data.bottomViewCell = 49; adjustRendered(cells, data); expect(data.topCell).toBe(27); expect(data.bottomCell).toBe(65); }); it('should adjust when all the way to the bottom, scrolling down', () => { for (var i = 0; i < 100; i++) { cells.push({top: i, height: 1, row: i}); } data.scrollDiff = 1; data.viewHeight = 20; data.itmHeight = 1; data.renderHeight = 40; data.topViewCell = 80; data.bottomViewCell = 99; adjustRendered(cells, data); expect(data.topCell).toBe(77); expect(data.bottomCell).toBe(99); }); it('should adjust when all the way to the bottom, scrolling up', () => { for (var i = 0; i < 100; i++) { cells.push({top: i, height: 1, row: i}); } data.scrollDiff = -1; data.viewHeight = 20; data.itmHeight = 1; data.renderHeight = 40; data.topViewCell = 80; data.bottomViewCell = 99; adjustRendered(cells, data); expect(data.topCell).toBe(61); expect(data.bottomCell).toBe(99); }); }); describe('getVirtualHeight', () => { it('should return known height from last cell/record', () => { let totalRecords = 1000; let lastCell: VirtualCell = { record: 999, tmpl: TEMPLATE_ITEM, row: 800, top: 900, height: 45 }; let virtualHeight = getVirtualHeight(totalRecords, lastCell); expect(virtualHeight).toBe(945); }); it('should guess the height from 1 known cell', () => { let totalRecords = 100; let lastCell: VirtualCell = { record: 0, tmpl: TEMPLATE_ITEM, row: 0, top: 0, height: 50 }; let virtualHeight = getVirtualHeight(totalRecords, lastCell); expect(virtualHeight).toBe(5000); }); it('should guess the height from 1/2 known cells', () => { let totalRecords = 100; let lastCell: VirtualCell = { record: 49, tmpl: TEMPLATE_ITEM, row: 0, top: 2450, height: 50 }; let virtualHeight = getVirtualHeight(totalRecords, lastCell); expect(virtualHeight).toBe(5000); }); it('should guess the height from 99/100 known cells', () => { let totalRecords = 100; let lastCell: VirtualCell = { record: 98, tmpl: TEMPLATE_ITEM, row: 0, top: 4900, height: 50 }; let virtualHeight = getVirtualHeight(totalRecords, lastCell); expect(virtualHeight).toBe(5000); }); }); let records: any[]; let cells: VirtualCell[]; let nodes: VirtualNode[]; let headerFn: Function; let footerFn: Function; let data: VirtualData; let itmTmp: any = {}; let hdrTmp: any = {}; let ftrTmp: any = {}; let viewContainer: any = { createEmbeddedView: function() { return getView(); }, indexOf: function() { return 0; } }; function getView(width?:number, height?: number, top?: number, left?: number): any { return { context: {}, rootNodes: [{ nodeType: 1, offsetWidth: width, offsetHeight: height, offsetTop: top, offsetLeft: left, style: { top: '', left: '' }, classList: { add: function(){}, remove: function(){} }, setAttribute: function(){}, innerHTML: '', }] } } }); } const TEMPLATE_ITEM = 0; const TEMPLATE_HEADER = 1; const TEMPLATE_FOOTER = 2; const HEIGHT_HEADER = 50; const HEIGHT_ITEM = 45; const HEIGHT_FOOTER = 32;