igniteui-angular-sovn
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
1,110 lines (924 loc) • 75.2 kB
text/typescript
import { AsyncPipe, NgClass, NgFor, NgForOfContext, NgIf } from '@angular/common';
import {
AfterViewInit,
ChangeDetectorRef,
Component,
Directive,
Injectable,
IterableDiffers,
NgZone,
OnInit,
QueryList,
TemplateRef,
ViewChild,
ViewChildren,
ViewContainerRef,
DebugElement
} from '@angular/core';
import { TestBed, ComponentFixture, waitForAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { BehaviorSubject, Observable } from 'rxjs';
import { IForOfState, IgxForOfDirective } from './for_of.directive';
import { UIInteractions, wait } from '../../test-utils/ui-interactions.spec';
import { configureTestSuite } from '../../test-utils/configure-suite';
import { IgxForOfScrollSyncService } from './for_of.sync.service';
import { TestNgZone } from '../../test-utils/helper-utils.spec';
import { PlatformUtil } from '../../core/utils';
describe('IgxForOf directive -', () => {
const INACTIVE_VIRT_CONTAINER = 'igx-display-container--inactive';
const DISPLAY_CONTAINER = 'igx-display-container';
const VERTICAL_SCROLLER = 'igx-virtual-helper';
let displayContainer: HTMLElement;
let verticalScroller: HTMLElement;
let horizontalScroller: HTMLElement;
let dg: DataGenerator;
beforeAll(() => {
dg = new DataGenerator();
});
describe('empty virtual component', () => {
configureTestSuite();
beforeAll(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [EmptyVirtualComponent]
}).compileComponents();
}));
it('should initialize empty directive', () => {
const fix = TestBed.createComponent(EmptyVirtualComponent);
fix.detectChanges();
displayContainer = fix.nativeElement.querySelector(DISPLAY_CONTAINER);
expect(displayContainer).not.toBeNull();
});
});
describe('horizontal virtual component', () => {
let fix: ComponentFixture<HorizontalVirtualComponent>;
configureTestSuite();
beforeAll(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [HorizontalVirtualComponent],
providers: [{ provide: NgZone, useFactory: () => new TestNgZone() }]
}).compileComponents();
}));
beforeEach(() => {
fix = TestBed.createComponent(HorizontalVirtualComponent);
dg.generateData(300, 5, fix.componentInstance);
fix.componentRef.hostView.detectChanges();
fix.detectChanges();
displayContainer = fix.nativeElement.querySelector(DISPLAY_CONTAINER);
verticalScroller = fix.nativeElement.querySelector(VERTICAL_SCROLLER);
horizontalScroller = fix.nativeElement.querySelector('igx-horizontal-virtual-helper');
});
it('should initialize directive with horizontal virtualization', () => {
expect(displayContainer).not.toBeNull();
expect(verticalScroller).toBeNull();
expect(horizontalScroller).not.toBeNull();
fix.componentInstance.scrollLeft(150);
fix.detectChanges();
const firstRecChildren = displayContainer.children;
for (let i = 0; i < firstRecChildren.length; i++) {
expect(firstRecChildren[i].textContent)
.toBe(fix.componentInstance.data[0][i + 1].toString());
}
const secondRecChildren = fix.nativeElement.querySelectorAll(DISPLAY_CONTAINER)[1].children;
for (let i = 0; i < secondRecChildren.length; i++) {
expect(secondRecChildren[i].textContent)
.toBe(fix.componentInstance.data[1][i + 1].toString());
}
});
it('should always fill available space for last chunk size calculation - horizontal virtualization', () => {
fix.componentInstance.width = '1900px';
fix.componentInstance.cols = [
{ field: '1', width: 100 },
{ field: '2', width: 1800 },
{ field: '3', width: 200 },
{ field: '4', width: 200 },
{ field: '5', width: 300 },
{ field: '6', width: 100 },
{ field: '7', width: 100 },
{ field: '8', width: 100 },
{ field: '9', width: 150 },
{ field: '10', width: 150 }
];
fix.componentRef.hostView.detectChanges();
fix.detectChanges();
const firstRecChildren = displayContainer.children;
let chunkSize = firstRecChildren.length;
expect(chunkSize).toEqual(9);
fix.componentInstance.width = '1900px';
fix.componentInstance.cols = [
{ field: '1', width: 1800 },
{ field: '2', width: 100 },
{ field: '3', width: 200 },
{ field: '4', width: 200 },
{ field: '5', width: 300 },
{ field: '6', width: 100 },
{ field: '7', width: 100 },
{ field: '8', width: 100 },
{ field: '9', width: 150 },
{ field: '10', width: 150 }
];
fix.componentRef.hostView.detectChanges();
fix.detectChanges();
chunkSize = firstRecChildren.length;
expect(chunkSize).toEqual(10);
});
it('should update horizontal scroll offsets if igxForOf changes. ', () => {
fix.componentInstance.width = '500px';
fix.componentInstance.cols = [
{ field: '1', width: 100 },
{ field: '2', width: 200 },
{ field: '3', width: 200 },
{ field: '4', width: 200 },
{ field: '5', width: 300 }
];
fix.componentRef.hostView.detectChanges();
fix.detectChanges();
fix.componentInstance.scrollLeft(50);
fix.detectChanges();
expect(parseInt(displayContainer.style.left, 10)).toEqual(-50);
fix.componentInstance.cols = [{ field: '1', width: 100 }];
fix.detectChanges();
expect(parseInt(displayContainer.style.left, 10)).toEqual(0);
});
it('should allow scroll in rtl direction.', () => {
fix.debugElement.nativeElement.dir = 'rtl';
fix.detectChanges();
fix.componentInstance.width = '500px';
fix.componentInstance.cols = [
{ field: '1', width: 100 },
{ field: '2', width: 200 },
{ field: '3', width: 200 },
{ field: '4', width: 200 },
{ field: '5', width: 300 }
];
fix.componentRef.hostView.detectChanges();
fix.detectChanges();
fix.componentInstance.scrollLeft(-50);
fix.detectChanges();
expect(parseInt(displayContainer.style.left, 10)).toEqual(50);
fix.componentInstance.scrollLeft(-250);
fix.detectChanges();
const state = fix.componentInstance.childVirtDirs.toArray()[0].state;
expect(state.startIndex).toBe(1);
});
});
describe('vertical virtual component', () => {
let fix: ComponentFixture<VerticalVirtualComponent>;
configureTestSuite();
beforeAll(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [VerticalVirtualNoDataComponent, VerticalVirtualComponent],
providers: [{ provide: NgZone, useFactory: () => new TestNgZone() }]
}).compileComponents();
}));
beforeEach(() => {
fix = TestBed.createComponent(VerticalVirtualComponent);
fix.componentInstance.data = dg.generateVerticalData(fix.componentInstance.cols);
fix.componentRef.hostView.detectChanges();
fix.detectChanges();
displayContainer = fix.nativeElement.querySelector(DISPLAY_CONTAINER);
verticalScroller = fix.nativeElement.querySelector(VERTICAL_SCROLLER);
horizontalScroller = fix.nativeElement.querySelector('igx-horizontal-virtual-helper');
});
it('should initialize directive with vertical virtualization', async () => {
expect(displayContainer).not.toBeNull();
expect(verticalScroller).not.toBeNull();
expect(horizontalScroller).toBeNull();
/* The height of the row is set to 50px so scrolling by 100px should render the third record */
fix.componentInstance.scrollTop(100);
fix.detectChanges();
const firstRecChildren = displayContainer.children[0].children;
let i = 0;
const thirdRecord = fix.componentInstance.data[2];
for (const item in thirdRecord) {
if (thirdRecord.hasOwnProperty(item)) {
expect(thirdRecord[item].toString())
.toBe(firstRecChildren[i++].textContent);
}
}
});
it('should update vertical scroll offsets if igxForOf changes. ', () => {
fix.componentInstance.scrollTop(5);
fix.detectChanges();
expect(parseInt(displayContainer.style.top, 10)).toEqual(-5);
spyOn(fix.componentInstance.parentVirtDir.chunkLoad, 'emit');
fix.componentInstance.data = [{ 1: 1, 2: 2, 3: 3, 4: 4 }];
fix.detectChanges();
expect(parseInt(displayContainer.style.top, 10)).toEqual(0);
expect(fix.componentInstance.parentVirtDir.chunkLoad.emit).toHaveBeenCalledTimes(1);
});
it('should apply the changes when itemSize is changed.', () => {
const firstRecChildren = displayContainer.children[0].children;
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < firstRecChildren.length; i++) {
expect(firstRecChildren[i].clientHeight)
.toBe(parseInt(fix.componentInstance.parentVirtDir.igxForItemSize, 10));
}
fix.componentInstance.itemSize = '100px';
fix.detectChanges();
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < firstRecChildren.length; i++) {
expect(firstRecChildren[i].clientHeight)
.toBe(parseInt(fix.componentInstance.parentVirtDir.igxForItemSize, 10));
}
});
it('should not throw error when itemSize is changed while data is null/undefined.', () => {
let errorMessage = '';
fix.componentInstance.data = null;
fix.detectChanges();
try {
fix.componentInstance.itemSize = '100px';
fix.detectChanges();
} catch (ex) {
errorMessage = ex.message;
}
expect(errorMessage).toBe('');
});
it('should always fill available space for last chunk size calculation - vertical virtualization', async () => {
fix.componentInstance.height = '1900px';
const virtualContainer = fix.componentInstance.parentVirtDir;
virtualContainer.igxForSizePropName = 'height';
fix.componentInstance.data = [
{ 1: '1', height: '100px' },
{ 1: '2', height: '1800px' },
{ 1: '3', height: '200px' },
{ 1: '4', height: '200px' },
{ 1: '5', height: '300px' },
{ 1: '6', height: '100px' },
{ 1: '7', height: '100px' },
{ 1: '8', height: '100px' },
{ 1: '9', height: '150px' },
{ 1: '10', height: '150px' }
];
fix.detectChanges();
await wait();
let chunkSize = (virtualContainer as any)._calcMaxChunkSize();
expect(chunkSize).toEqual(9);
fix.componentInstance.height = '1900px';
fix.componentInstance.data = [
{ 1: '1', height: '1800px' },
{ 1: '2', height: '100px' },
{ 1: '3', height: '200px' },
{ 1: '4', height: '200px' },
{ 1: '5', height: '300px' },
{ 1: '6', height: '100px' },
{ 1: '7', height: '100px' },
{ 1: '8', height: '100px' },
{ 1: '9', height: '150px' },
{ 1: '10', height: '150px' }
];
fix.detectChanges();
await wait();
chunkSize = (virtualContainer as any)._calcMaxChunkSize();
expect(chunkSize).toEqual(10);
});
it('should take item margins into account when calculating the size cache', async () => {
fix.componentInstance.height = '600px';
fix.componentInstance.itemSize = '100px';
const virtualContainer = fix.componentInstance.parentVirtDir;
virtualContainer.igxForSizePropName = 'height';
fix.componentInstance.data = [
{ 1: '1', height: '100px', margin: '30px' },
{ 1: '2', height: '100px', margin: '0px' },
{ 1: '3', height: '100px', margin: '0px' },
{ 1: '4', height: '100px', margin: '0px' },
{ 1: '5', height: '100px', margin: '0px' },
{ 1: '6', height: '100px', margin: '0px' },
{ 1: '7', height: '100px', margin: '0px' },
{ 1: '8', height: '100px', margin: '30px' },
{ 1: '9', height: '100px', margin: '30px' },
{ 1: '10', height: '100px', margin: '30px' }
];
fix.detectChanges();
await wait(200);
const cache = (fix.componentInstance.parentVirtDir as any).heightCache;
expect(cache).toEqual([130, 100, 100, 100, 100, 100, 100, 100, 100, 100]);
fix.componentInstance.scrollTop(400);
fix.detectChanges();
await wait(200);
expect(cache).toEqual([130, 100, 100, 100, 100, 100, 100, 130, 130, 130]);
});
});
describe('vertical virtual component no data', () => {
let fix: ComponentFixture<VerticalVirtualComponent>;
configureTestSuite();
beforeAll(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [VerticalVirtualNoDataComponent, VerticalVirtualComponent],
providers: [{ provide: NgZone, useFactory: () => new TestNgZone() }]
}).compileComponents();
}));
beforeEach(() => {
fix = TestBed.createComponent(VerticalVirtualNoDataComponent);
});
it('should allow initially undefined value for igxForOf and then detect changes correctly once the value is updated', () => {
expect(() => {
fix.detectChanges();
}).not.toThrow();
displayContainer = fix.nativeElement.querySelector(DISPLAY_CONTAINER);
verticalScroller = fix.nativeElement.querySelector(VERTICAL_SCROLLER);
expect(displayContainer).not.toBeNull();
expect(verticalScroller).not.toBeNull();
fix.componentInstance.height = '400px';
fix.detectChanges();
fix.componentInstance.height = '500px';
fix.detectChanges();
let rowsRendered = displayContainer.querySelectorAll('div');
expect(rowsRendered.length).toBe(0);
fix.componentInstance.data = dg.generateVerticalData(fix.componentInstance.cols);
fix.detectChanges();
rowsRendered = displayContainer.querySelectorAll('div');
expect(rowsRendered.length).not.toBe(0);
});
});
describe('vertical and horizontal virtual component', () => {
let fix: ComponentFixture<VirtualComponent>;
configureTestSuite();
beforeAll(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [VirtualComponent],
providers: [{ provide: NgZone, useFactory: () => new TestNgZone() }]
}).compileComponents();
}));
beforeEach(() => {
fix = TestBed.createComponent(VirtualComponent);
dg.generateData300x50000(fix.componentInstance);
fix.detectChanges();
displayContainer = fix.nativeElement.querySelector(DISPLAY_CONTAINER);
verticalScroller = fix.nativeElement.querySelector(VERTICAL_SCROLLER);
horizontalScroller = fix.nativeElement.querySelector('igx-horizontal-virtual-helper');
expect(displayContainer).not.toBeNull();
expect(verticalScroller).not.toBeNull();
expect(horizontalScroller).not.toBeNull();
});
it('should initialize directive with vertical and horizontal virtualization', () => {
/* The height of the row is set to 50px so scrolling by 100px should render the third record */
fix.componentInstance.scrollTop(100);
const firstInnerDisplayContainer = displayContainer.children[0].querySelector(DISPLAY_CONTAINER);
expect(firstInnerDisplayContainer).not.toBeNull();
fix.detectChanges();
const firstRecChildren = firstInnerDisplayContainer.children;
for (let i = 0; i < firstRecChildren.length; i++) {
expect(firstInnerDisplayContainer.children[i].textContent)
.toBe(fix.componentInstance.data[2][i].toString());
}
});
it('should allow scrolling at certain amount down and then to the top renders correct rows and cols', () => {
fix.componentInstance.scrollTop(5000);
fix.detectChanges();
fix.componentInstance.scrollTop(0);
fix.detectChanges();
const firstInnerDisplayContainer = displayContainer.children[0].querySelector(DISPLAY_CONTAINER);
expect(firstInnerDisplayContainer).not.toBeNull();
const firstRecChildren = firstInnerDisplayContainer.children;
for (let i = 0; i < firstRecChildren.length; i++) {
expect(firstInnerDisplayContainer.children[i].textContent)
.toBe(fix.componentInstance.data[0][i].toString());
}
});
it('should scroll to bottom and correct rows and columns should be rendered', () => {
fix.componentInstance.scrollTop(2500000);
const rows = displayContainer.children;
const lastInnerDisplayContainer = rows[rows.length - 1].querySelector(DISPLAY_CONTAINER);
expect(lastInnerDisplayContainer).not.toBeNull();
fix.detectChanges();
const lastRecChildren = lastInnerDisplayContainer.children;
const data = fix.componentInstance.data;
for (let i = 0; i < lastRecChildren.length; i++) {
expect(lastInnerDisplayContainer.children[i].textContent)
.toBe(data[data.length - 1][i].toString());
}
});
it('should scroll to wheel event correctly', async () => {
fix.componentInstance.parentVirtDir.dc.instance._scrollInertia.smoothingDuration = 0;
/* 120 is default mousewheel on Chrome, scroll 2 records down */
await UIInteractions.simulateWheelEvent(displayContainer, 0, - 1 * 2 * 120);
fix.detectChanges();
await wait();
const firstInnerDisplayContainer = displayContainer.children[0].querySelector(DISPLAY_CONTAINER);
expect(firstInnerDisplayContainer).not.toBeNull();
const firstRecChildren = firstInnerDisplayContainer.children;
for (let i = 0; i < firstRecChildren.length; i++) {
expect(firstInnerDisplayContainer.children[i].textContent)
.toBe(fix.componentInstance.data[2][i].toString());
}
});
it('should scroll to the far right and last column should be visible', () => {
// scroll to the last right pos
fix.componentInstance.scrollLeft(90000);
fix.detectChanges();
const rowChildren = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
for (let i = 0; i < rowChildren.length; i++) {
expect(rowChildren[i].children.length).toBe(7);
expect(rowChildren[i].children[5].textContent)
.toBe(fix.componentInstance.data[i][298].toString());
expect(rowChildren[i].children[6].textContent)
.toBe(fix.componentInstance.data[i][299].toString());
}
});
it('should detect width change and update initially rendered columns', () => {
let rows = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
expect(rows.length).toBe(9);
for (let i = 0; i < rows.length; i++) {
expect(rows[i].children.length).toBe(7);
expect(rows[i].children[3].textContent)
.toBe(fix.componentInstance.data[i][3].toString());
}
// scroll to the last right pos
fix.componentInstance.width = '1200px';
fix.detectChanges();
rows = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
expect(rows.length).toBe(9);
for (let i = 0; i < rows.length; i++) {
expect(rows[i].children.length).toBe(9);
expect(rows[i].children[4].textContent)
.toBe(fix.componentInstance.data[i][4].toString());
}
});
it('should detect height change and update initially rendered rows', () => {
let rows = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
expect(rows.length).toBe(9);
for (let i = 0; i < rows.length; i++) {
expect(rows[i].children.length).toBe(7);
expect(rows[i].children[2].textContent)
.toBe(fix.componentInstance.data[i][2].toString());
}
// scroll to the last right pos
fix.componentInstance.height = '700px';
fix.detectChanges();
rows = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
expect(rows.length).toBe(15);
for (let i = 0; i < rows.length; i++) {
expect(rows[i].children.length).toBe(7);
expect(rows[i].children[2].textContent)
.toBe(fix.componentInstance.data[i][2].toString());
}
});
it('should not render vertical scrollbar when number of rows change to 5', () => {
let rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
expect(displayContainer).not.toBeNull();
expect(verticalScroller).not.toBeNull();
expect(horizontalScroller).not.toBeNull();
expect(fix.componentInstance.isVerticalScrollbarVisible()).toBe(true);
expect(fix.componentInstance.isHorizontalScrollbarVisible()).toBe(true);
expect(rowsRendered.length).toBe(9);
/** Step 1. Lower the amount of rows to 5. The vertical scrollbar then should not be rendered */
dg.generateData(300, 5, fix.componentInstance);
fix.detectChanges();
fix.componentInstance.scrollTop(verticalScroller.scrollTop);
fix.detectChanges();
rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
expect(fix.componentInstance.isVerticalScrollbarVisible()).toBe(false);
expect(fix.componentInstance.isHorizontalScrollbarVisible()).toBe(true);
expect(rowsRendered.length).toBe(5);
/** Step 2. Scroll to the left. There should be no errors then and everything should be still the same */
fix.componentInstance.scrollLeft(1000);
fix.detectChanges();
rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
expect(fix.componentInstance.isVerticalScrollbarVisible()).toBe(false);
expect(fix.componentInstance.isHorizontalScrollbarVisible()).toBe(true);
expect(rowsRendered.length).toBe(5);
/** Step 3. Increase the amount of rows back and vertical scrollbar should be rendered back */
dg.generateData300x50000(fix.componentInstance);
fix.detectChanges();
// We trigger scrollTop with the current scroll position because otherwise the scroll events are not fired during a test.
fix.componentInstance.scrollTop(verticalScroller.scrollTop);
fix.detectChanges();
rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
expect(horizontalScroller.scrollLeft).toBe(1000);
expect(fix.componentInstance.isVerticalScrollbarVisible()).toBe(true);
expect(fix.componentInstance.isHorizontalScrollbarVisible()).toBe(true);
expect(rowsRendered.length).toBe(9);
});
it('should not render vertical scrollbars when number of rows change to 0 after scrolling down', () => {
let rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
expect(displayContainer).not.toBeNull();
expect(verticalScroller).not.toBeNull();
expect(horizontalScroller).not.toBeNull();
expect(fix.componentInstance.isVerticalScrollbarVisible()).toBe(true);
expect(fix.componentInstance.isHorizontalScrollbarVisible()).toBe(true);
expect(rowsRendered.length).toBe(9);
dg.generateData300x50000(fix.componentInstance);
fix.detectChanges();
/** Step 1. Scroll to the bottom. */
fix.componentInstance.scrollTop(100000);
fix.detectChanges();
/** Step 2. Lower the amount of rows to 5. The vertical scrollbar then should not be rendered */
fix.componentInstance.data = [];
fix.detectChanges();
rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
expect(fix.componentInstance.isVerticalScrollbarVisible()).toBe(false);
expect(fix.componentInstance.isHorizontalScrollbarVisible()).toBe(true);
expect(rowsRendered.length).toBe(0);
/**
* Step 3. Set the amount of rows back and vertical scrollbar should be rendered back then.
* It should reset the scroll position.
*/
dg.generateData300x50000(fix.componentInstance);
fix.detectChanges();
rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
expect(verticalScroller.scrollTop).toBe(0);
expect(fix.componentInstance.isVerticalScrollbarVisible()).toBe(true);
expect(fix.componentInstance.isHorizontalScrollbarVisible()).toBe(true);
expect(rowsRendered.length).toBe(9);
});
it('should not render vertical scrollbar when number of rows change to 0 after scrolling right', async () => {
let rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
let colsRendered = rowsRendered[0].children;
expect(fix.componentInstance.isVerticalScrollbarVisible()).toBe(true);
expect(fix.componentInstance.isHorizontalScrollbarVisible()).toBe(true);
expect(rowsRendered.length).toBe(9);
expect(colsRendered.length).toBe(7);
/** Step 1. Scroll to the right. */
fix.componentInstance.scrollLeft(1000);
fix.detectChanges();
await wait();
rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
for (let i = 0; i < rowsRendered.length; i++) {
// Check only the second col, no need for the others
expect(rowsRendered[i].children[1].textContent)
.toBe(fix.componentInstance.data[i][5].toString());
}
/** Step 2. Lower the amount of cols to 0 so there would be no horizontal scrollbar */
fix.componentInstance.data = [];
fix.detectChanges();
rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
expect(fix.componentInstance.isVerticalScrollbarVisible()).toBe(false);
expect(rowsRendered.length).toBe(0);
/** Step 3. Set the data back to and it should render both scrollbars. It should reset the scroll position */
dg.generateData300x50000(fix.componentInstance);
fix.detectChanges();
await wait();
rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
colsRendered = rowsRendered[0].children;
expect(fix.componentInstance.isVerticalScrollbarVisible()).toBe(true);
expect(fix.componentInstance.isHorizontalScrollbarVisible()).toBe(true);
expect(rowsRendered.length).toBe(9);
for (let i = 0; i < rowsRendered.length; i++) {
// Check only the second col, no need for the others
expect(rowsRendered[i].children[1].textContent)
.toBe(fix.componentInstance.data[i][5].toString());
}
});
it('should not render horizontal scrollbars when number of cols change to 3', () => {
let rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
let colsRendered = rowsRendered[0].children;
expect(fix.componentInstance.isVerticalScrollbarVisible()).toBe(true);
expect(fix.componentInstance.isHorizontalScrollbarVisible()).toBe(true);
expect(rowsRendered.length).toBe(9);
expect(colsRendered.length).toBe(7);
/** Step 1. Lower the amount of cols to 3 so there would be no horizontal scrollbar */
dg.generateData(3, 50000, fix.componentInstance);
fix.detectChanges();
// We trigger scrollTop with the current scroll position because otherwise the scroll events are not fired during a test.
fix.componentInstance.scrollTop(verticalScroller.scrollTop);
fix.detectChanges();
rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
colsRendered = rowsRendered[0].children;
expect(fix.componentInstance.isVerticalScrollbarVisible()).toBe(true);
expect(fix.componentInstance.isHorizontalScrollbarVisible()).toBe(false);
expect(rowsRendered.length).toBe(9);
expect(colsRendered.length).toBe(3);
/** Step 2. Scroll down. There should be no errors then and everything should be still the same */
fix.componentInstance.scrollTop(1000);
fix.detectChanges();
// We trigger scrollTop with the current scroll position because otherwise the scroll events are not fired during a test.
fix.componentInstance.scrollTop(verticalScroller.scrollTop);
fix.detectChanges();
rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
colsRendered = rowsRendered[0].children;
expect(fix.componentInstance.isVerticalScrollbarVisible()).toBe(true);
expect(fix.componentInstance.isHorizontalScrollbarVisible()).toBe(false);
expect(rowsRendered.length).toBe(9);
expect(colsRendered.length).toBe(3);
/** Step 3. Set the data back to have 300 columns and the horizontal scrollbar should render now. */
dg.generateData300x50000(fix.componentInstance);
fix.detectChanges();
// We trigger scrollTop with the current scroll position because otherwise the scroll events are not fired during a test.
fix.componentInstance.scrollTop(verticalScroller.scrollTop);
fix.detectChanges();
rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
colsRendered = rowsRendered[0].children;
expect(verticalScroller.scrollTop).toBe(1000);
expect(fix.componentInstance.isVerticalScrollbarVisible()).toBe(true);
expect(fix.componentInstance.isHorizontalScrollbarVisible()).toBe(true);
expect(rowsRendered.length).toBe(9);
expect(colsRendered.length).toBe(7);
});
it('should scroll down when using touch events', async () => {
let rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
for (let i = 0; i < rowsRendered.length; i++) {
// Check only the second col, no need for the others
expect(rowsRendered[i].children[1].textContent)
.toBe(fix.componentInstance.data[i][1].toString());
}
const dcElem = fix.componentInstance.parentVirtDir.dc.instance._viewContainer.element.nativeElement;
UIInteractions.simulateTouchStartEvent(dcElem, 200, 200);
UIInteractions.simulateTouchMoveEvent(dcElem, 200, -300);
fix.detectChanges();
await wait();
rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
for (let i = 0; i < rowsRendered.length; i++) {
// Check only the second col, no need for the others
expect(rowsRendered[i].children[1].textContent)
.toBe(fix.componentInstance.data[10 + i][1].toString());
}
});
xit('should apply inertia when swiping via touch interaction.', async () => {
const dcElem = fix.componentInstance.parentVirtDir.dc.instance._viewContainer.element.nativeElement;
// spyOn(fix.componentInstance.parentVirtDir, 'onScroll');
await UIInteractions.simulateTouchStartEvent(
dcElem,
0,
-150
);
await wait(1);
await UIInteractions.simulateTouchMoveEvent(dcElem, 0, -180);
await UIInteractions.simulateTouchEndEvent(dcElem, 0, -200);
fix.detectChanges();
// wait for inertia to complete
await wait(1500);
fix.detectChanges();
const scrStepArray = fix.componentInstance.parentVirtDir.scrStepArray;
expect(scrStepArray.length).toBeGreaterThan(55);
// check if inertia first accelerates then decelerate
const first = scrStepArray[0];
const mid = scrStepArray[10];
const end = scrStepArray[60];
expect(first).toBeLessThan(mid);
expect(end).toBeLessThan(mid);
});
it('should scroll left when using touch events', () => {
let rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
for (let i = 0; i < rowsRendered.length; i++) {
// Check only the second col, no need for the others
expect(rowsRendered[i].children[1].textContent)
.toBe(fix.componentInstance.data[i][1].toString());
}
const dcElem = fix.componentInstance.childVirtDirs.first.dc.instance._viewContainer.element.nativeElement;
UIInteractions.simulateTouchStartEvent(dcElem, 200, 200);
UIInteractions.simulateTouchMoveEvent(dcElem, -800, 0);
// Trigger onScroll
fix.componentInstance.scrollLeft(horizontalScroller.scrollLeft);
fix.detectChanges();
rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
for (let i = 0; i < rowsRendered.length; i++) {
// Check only the second col, no need for the others
expect(rowsRendered[i].children[1].textContent)
.toBe(fix.componentInstance.data[i][5].toString());
}
});
it('should load next row and remove first row when using scrollNext method', () => {
let rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
for (let i = 0; i < rowsRendered.length; i++) {
// Check only the second col, no need for the others
expect(rowsRendered[i].children[1].textContent)
.toBe(fix.componentInstance.data[i][1].toString());
}
fix.componentInstance.parentVirtDir.scrollNext();
fix.componentInstance.scrollTop(verticalScroller.scrollTop); // Trigger onScroll manually.
fix.detectChanges();
rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
for (let i = 0; i < rowsRendered.length; i++) {
// Check only the second col, no need for the others
expect(rowsRendered[i].children[1].textContent)
.toBe(fix.componentInstance.data[1 + i][1].toString());
}
});
it('should load previous row and remove last row when using scrollPrev method', () => {
/** Step 1. Scroll down 500px first so we then have what to load previously */
fix.componentInstance.scrollTop(500);
fix.detectChanges();
let rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
for (let i = 0; i < rowsRendered.length; i++) {
// Check only the second col, no need for the others
expect(rowsRendered[i].children[1].textContent)
.toBe(fix.componentInstance.data[10 + i][1].toString());
}
/** Step 2. Execute scrollPrev to load previous row */
fix.componentInstance.parentVirtDir.scrollPrev();
fix.componentInstance.scrollTop(verticalScroller.scrollTop); // Trigger onScroll manually.
fix.detectChanges();
rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
for (let i = 0; i < rowsRendered.length; i++) {
// Check only the second col, no need for the others
expect(rowsRendered[i].children[1].textContent)
.toBe(fix.componentInstance.data[9 + i][1].toString());
}
});
it('should not wrap around with scrollNext and scrollPrev', async () => {
const forOf = fix.componentInstance.parentVirtDir;
forOf.scrollPrev();
fix.detectChanges();
await wait(200);
expect(forOf.state.startIndex).toEqual(0);
forOf.scrollTo(forOf.igxForOf.length - 1);
fix.detectChanges();
await wait(200);
expect(forOf.state.startIndex).toEqual(forOf.igxForOf.length - forOf.state.chunkSize);
forOf.scrollNext();
fix.detectChanges();
await wait(200);
expect(forOf.state.startIndex).toEqual(forOf.igxForOf.length - forOf.state.chunkSize);
});
it('should prevent scrollTo() when called with numbers outside the scope of the data records.', () => {
fix.componentInstance.parentVirtDir.scrollTo(-1);
expect(fix.componentInstance.parentVirtDir.state.startIndex).toBe(0);
fix.componentInstance.parentVirtDir.scrollTo(fix.componentInstance.data.length + 1);
expect(fix.componentInstance.parentVirtDir.state.startIndex).toBe(0);
});
it('should set correct left offset when scrolling to right, clearing data and then setting new data', async () => {
/** Scroll left 1500px */
fix.componentInstance.scrollLeft(1500);
fix.detectChanges();
await wait();
/** Timeout for scroll event to trigger during test */
let firstRowDisplayContainer = fix.nativeElement.querySelectorAll(DISPLAY_CONTAINER)[1];
expect(firstRowDisplayContainer.style.left).toEqual('-82px');
dg.generateData(300, 0, fix.componentInstance);
fix.detectChanges();
dg.generateData300x50000(fix.componentInstance);
fix.detectChanges();
await wait();
/** Offset should be equal to the offset before so there is no misalignment */
firstRowDisplayContainer = fix.nativeElement.querySelectorAll(DISPLAY_CONTAINER)[1];
expect(firstRowDisplayContainer.style.left).toEqual('-82px');
});
it('should correctly scroll to the last element when using the scrollTo method', () => {
spyOn(fix.componentInstance.parentVirtDir.chunkLoad, 'emit');
/** Scroll to the last 49999 row. */
fix.componentInstance.parentVirtDir.scrollTo(49999);
fix.componentInstance.scrollTop(verticalScroller.scrollTop);
fix.detectChanges();
expect(fix.componentInstance.parentVirtDir.chunkLoad.emit).toHaveBeenCalledTimes(1);
const rowsRendered = displayContainer.querySelectorAll(DISPLAY_CONTAINER);
for (let i = 0; i < 8; i++) {
expect(rowsRendered[i].children[1].textContent)
.toBe(fix.componentInstance.data[49991 + i][1].toString());
}
});
it('should return correct value for getItemCountInView API. ', async () => {
/** Scroll left 1500px and top 105px */
fix.componentInstance.scrollLeft(1500);
fix.componentInstance.scrollTop(105);
fix.detectChanges();
fix.componentInstance.parentVirtDir.cdr.detectChanges();
await wait();
expect(fix.componentInstance.parentVirtDir.getItemCountInView()).toBe(7);
const hDirective = fix.componentInstance.childVirtDirs.toArray()[0];
expect(hDirective.getItemCountInView()).toBe(2);
});
it('should emit the chunkPreload/chunkLoad only when startIndex or chunkSize have changed.', async () => {
const verticalDir = fix.componentInstance.parentVirtDir;
const chunkLoadSpy = spyOn<any>(verticalDir.chunkLoad, 'emit').and.callThrough();
const chunkPreLoadSpy = spyOn<any>(verticalDir.chunkPreload, 'emit').and.callThrough();
// scroll so that start index does not change.
fix.componentInstance.scrollTop(1);
fix.detectChanges();
await wait();
expect(chunkLoadSpy).toHaveBeenCalledTimes(0);
expect(chunkPreLoadSpy).toHaveBeenCalledTimes(0);
// scroll so that start index changes.
fix.componentInstance.scrollTop(100);
fix.detectChanges();
await wait();
expect(chunkLoadSpy).toHaveBeenCalledTimes(1);
expect(chunkPreLoadSpy).toHaveBeenCalledTimes(1);
// change size so that chunk size does not change
fix.componentInstance.height = '399px';
fix.detectChanges();
await wait();
expect(chunkLoadSpy).toHaveBeenCalledTimes(1);
// change size so that chunk size changes
fix.componentInstance.height = '1500px';
fix.detectChanges();
await wait();
expect(chunkLoadSpy).toHaveBeenCalledTimes(2);
});
});
describe('variable size component', () => {
configureTestSuite();
beforeAll(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [VirtualVariableSizeComponent]
}).compileComponents();
}));
it('should update display container classes when content state changes from virtualized to non-virtualized.', () => {
const fix = TestBed.createComponent(VirtualVariableSizeComponent);
fix.detectChanges();
let displayContainerDebugEl: DebugElement = fix.debugElement.query(By.css(DISPLAY_CONTAINER));
// No size and no data - display container should be inactive
expect(displayContainerDebugEl.classes[INACTIVE_VIRT_CONTAINER]).toBeTruthy();
// set size
fix.componentInstance.height = '500px';
fix.detectChanges();
displayContainerDebugEl = fix.debugElement.query(By.css(DISPLAY_CONTAINER));
// Has size but no data - display container should be inactive
expect(displayContainerDebugEl.classes[INACTIVE_VIRT_CONTAINER]).toBeTruthy();
// set data with 1 rec.
fix.componentInstance.data = fix.componentInstance.generateData(1);
fix.detectChanges();
displayContainerDebugEl = fix.debugElement.query(By.css(DISPLAY_CONTAINER));
// Has size but not enough data to be virtualized - display container should be inactive
expect(displayContainerDebugEl.classes[INACTIVE_VIRT_CONTAINER]).toBeTruthy();
// set data with 1000 recs.
fix.componentInstance.data = fix.componentInstance.generateData(1000);
fix.detectChanges();
displayContainerDebugEl = fix.debugElement.query(By.css(DISPLAY_CONTAINER));
// Has size and enough data to be virtualized - display container should be active.
expect(displayContainerDebugEl.classes[INACTIVE_VIRT_CONTAINER]).toBeFalsy();
});
});
describe('remote virtual component', () => {
configureTestSuite();
beforeAll(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [RemoteVirtualizationComponent]
}).compileComponents();
}));
it('should allow remote virtualization', async () => {
const fix = TestBed.createComponent(RemoteVirtualizationComponent);
fix.componentRef.hostView.detectChanges();
fix.detectChanges();
displayContainer = fix.nativeElement.querySelector(DISPLAY_CONTAINER);
verticalScroller = fix.nativeElement.querySelector(VERTICAL_SCROLLER);
// verify data is loaded
let rowsRendered = displayContainer.children;
let data = fix.componentInstance.data.source.getValue();
for (let i = 0; i < rowsRendered.length; i++) {
expect(rowsRendered[i].textContent.trim())
.toBe(data[i].toString());
}
// scroll down
verticalScroller.scrollTop = 10000;
fix.detectChanges();
await wait();
// verify data is loaded
rowsRendered = displayContainer.children;
data = fix.componentInstance.data.source.getValue();
for (let i = fix.componentInstance.parentVirtDir.state.startIndex; i < rowsRendered.length; i++) {
expect(rowsRendered[i].textContent.trim())
.toBe(data[i].toString());
}
});
});
describe('remote virtual component with specified igxForTotalItemCount', () => {
configureTestSuite();
beforeAll(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [RemoteVirtCountComponent]
}).compileComponents();
}));
it('should apply remote virtualization correctly', async () => {
const fix = TestBed.createComponent(RemoteVirtCountComponent);
fix.componentRef.hostView.detectChanges();
fix.detectChanges();
displayContainer = fix.nativeElement.querySelector(DISPLAY_CONTAINER);
verticalScroller = fix.nativeElement.querySelector(VERTICAL_SCROLLER);
// verify data is loaded
let rowsRendered = displayContainer.children;
let data = fix.componentInstance.data.source.getValue();
for (let i = 0; i < rowsRendered.length; i++) {
expect(rowsRendered[i].textContent.trim())
.toBe(data[i].toString());
}
// scroll down
verticalScroller.scrollTop = 10000;
fix.detectChanges();
await wait();
// verify data is loaded
rowsRendered = displayContainer.children;
data = fix.componentInstance.data.source.getValue();
for (let i = fix.componentInstance.parentVirtDir.state.startIndex; i < rowsRendered.length; i++) {
expect(rowsRendered[i].textContent.trim())
.toBe(data[i].toString());
}
});
});
describe('no width and height component', () => {
configureTestSuite();
beforeAll(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [NoWidthAndHeightComponent]
}).compileComponents();
}));
it('should use itemSize when no width or height are provided', () => {
const fix = TestBed.createComponent(NoWidthAndHeightComponent);
fix.componentRef.hostView.detectChanges();
fix.detectChanges();
const children = fix.componentInstance.childVirtDirs;
const instance = fix.componentInstance;
const expectedElementsLength = (parseInt(instance.width, 10) / instance.itemSize) + 1;
expect(children.length).toEqual(expectedElementsLength);
});
});
describe('even odd first last functions', () => {
configureTestSuite();
beforeAll(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [LocalVariablesComponent]
}).compileComponents();
}));
it('should differentiate even odd items', () => {
const fix = TestBed.createComponent(LocalVariablesComponent);
fix.detectChanges();
const allItems: DebugElement[] = fix.debugElement.queryAll(By.css(DISPLAY_CON