ipsos-components
Version:
Material Design components for Angular
456 lines (393 loc) • 15.9 kB
text/typescript
import {async, ComponentFixture, fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing';
import {Component, ViewChild} from '@angular/core';
import {DataSource} from '@angular/cdk/collections';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {Observable} from 'rxjs/Observable';
import {MatTableModule} from './index';
import {MatTable} from './table';
import {MatPaginator, MatPaginatorModule} from '../paginator/index';
import {MatSort, MatSortHeader, MatSortModule} from '../sort/index';
import {MatTableDataSource} from './table-data-source';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
describe('MatTable', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatTableModule, MatPaginatorModule, MatSortModule, NoopAnimationsModule],
declarations: [MatTableApp, MatTableWithWhenRowApp, ArrayDataSourceMatTableApp],
}).compileComponents();
}));
describe('with basic data source', () => {
it('should be able to create a table with the right content and without when row', () => {
let fixture = TestBed.createComponent(MatTableApp);
fixture.detectChanges();
const tableElement = fixture.nativeElement.querySelector('.mat-table')!;
const data = fixture.componentInstance.dataSource!.data;
expectTableToMatchContent(tableElement, [
['Column A', 'Column B', 'Column C'],
[data[0].a, data[0].b, data[0].c],
[data[1].a, data[1].b, data[1].c],
[data[2].a, data[2].b, data[2].c],
['fourth_row']
]);
});
it('should create a table with special when row', () => {
let fixture = TestBed.createComponent(MatTableWithWhenRowApp);
fixture.detectChanges();
const tableElement = fixture.nativeElement.querySelector('.mat-table');
expectTableToMatchContent(tableElement, [
['Column A', 'Column B', 'Column C'],
['a_1'],
['a_2'],
['a_3'],
['fourth_row']
]);
});
});
describe('with MatTableDataSource', () => {
let tableElement: HTMLElement;
let fixture: ComponentFixture<ArrayDataSourceMatTableApp>;
let dataSource: MatTableDataSource<TestData>;
let component: ArrayDataSourceMatTableApp;
beforeEach(() => {
fixture = TestBed.createComponent(ArrayDataSourceMatTableApp);
fixture.detectChanges();
tableElement = fixture.nativeElement.querySelector('.mat-table');
component = fixture.componentInstance;
dataSource = fixture.componentInstance.dataSource;
});
it('should create table and display data source contents', () => {
expectTableToMatchContent(tableElement, [
['Column A', 'Column B', 'Column C'],
['a_1', 'b_1', 'c_1'],
['a_2', 'b_2', 'c_2'],
['a_3', 'b_3', 'c_3'],
]);
});
it('changing data should update the table contents', () => {
// Add data
component.underlyingDataSource.addData();
fixture.detectChanges();
expectTableToMatchContent(tableElement, [
['Column A', 'Column B', 'Column C'],
['a_1', 'b_1', 'c_1'],
['a_2', 'b_2', 'c_2'],
['a_3', 'b_3', 'c_3'],
['a_4', 'b_4', 'c_4'],
]);
// Remove data
const modifiedData = dataSource.data.slice();
modifiedData.shift();
dataSource.data = modifiedData;
fixture.detectChanges();
expectTableToMatchContent(tableElement, [
['Column A', 'Column B', 'Column C'],
['a_2', 'b_2', 'c_2'],
['a_3', 'b_3', 'c_3'],
['a_4', 'b_4', 'c_4'],
]);
});
it('should be able to filter the table contents', fakeAsync(() => {
// Change filter to a_1, should match one row
dataSource.filter = 'a_1';
fixture.detectChanges();
expect(dataSource.filteredData.length).toBe(1);
expect(dataSource.filteredData[0]).toBe(dataSource.data[0]);
expectTableToMatchContent(tableElement, [
['Column A', 'Column B', 'Column C'],
['a_1', 'b_1', 'c_1'],
]);
flushMicrotasks(); // Resolve promise that updates paginator's length
expect(dataSource.paginator!.length).toBe(1);
// Change filter to ' A_2 ', should match one row (ignores case and whitespace)
dataSource.filter = ' A_2 ';
fixture.detectChanges();
expect(dataSource.filteredData.length).toBe(1);
expect(dataSource.filteredData[0]).toBe(dataSource.data[1]);
expectTableToMatchContent(tableElement, [
['Column A', 'Column B', 'Column C'],
['a_2', 'b_2', 'c_2'],
]);
// Change filter to empty string, should match all rows
dataSource.filter = '';
fixture.detectChanges();
expect(dataSource.filteredData.length).toBe(3);
expect(dataSource.filteredData[0]).toBe(dataSource.data[0]);
expect(dataSource.filteredData[1]).toBe(dataSource.data[1]);
expect(dataSource.filteredData[2]).toBe(dataSource.data[2]);
expectTableToMatchContent(tableElement, [
['Column A', 'Column B', 'Column C'],
['a_1', 'b_1', 'c_1'],
['a_2', 'b_2', 'c_2'],
['a_3', 'b_3', 'c_3'],
]);
// Change filter function and filter, should match to rows with zebra.
dataSource.filterPredicate = (data, filter) => {
let dataStr;
switch (data.a) {
case 'a_1': dataStr = 'elephant'; break;
case 'a_2': dataStr = 'zebra'; break;
case 'a_3': dataStr = 'monkey'; break;
default: dataStr = '';
}
return dataStr.indexOf(filter) != -1;
};
dataSource.filter = 'zebra';
fixture.detectChanges();
expectTableToMatchContent(tableElement, [
['Column A', 'Column B', 'Column C'],
['a_2', 'b_2', 'c_2'],
]);
}));
it('should be able to sort the table contents', () => {
// Activate column A sort
component.sort.sort(component.sortHeader);
fixture.detectChanges();
expectTableToMatchContent(tableElement, [
['Column A\xa0Sorted by a ascending', 'Column B', 'Column C'],
['a_1', 'b_1', 'c_1'],
['a_2', 'b_2', 'c_2'],
['a_3', 'b_3', 'c_3'],
]);
// Activate column A sort again (reverse direction)
component.sort.sort(component.sortHeader);
fixture.detectChanges();
expectTableToMatchContent(tableElement, [
['Column A\xa0Sorted by a descending', 'Column B', 'Column C'],
['a_3', 'b_3', 'c_3'],
['a_2', 'b_2', 'c_2'],
['a_1', 'b_1', 'c_1'],
]);
// Change sort function to customize how it sorts - first column 1, then 3, then 2
dataSource.sortingDataAccessor = data => {
switch (data.a) {
case 'a_1': return 'elephant';
case 'a_2': return 'zebra';
case 'a_3': return 'monkey';
default: return '';
}
};
component.sort.direction = '';
component.sort.sort(component.sortHeader);
expectTableToMatchContent(tableElement, [
['Column A\xa0Sorted by a descending', 'Column B', 'Column C'],
['a_1', 'b_1', 'c_1'],
['a_3', 'b_3', 'c_3'],
['a_2', 'b_2', 'c_2'],
]);
});
it('should by default correctly sort an empty string', () => {
// Activate column A sort
dataSource.data[0].a = ' ';
component.sort.sort(component.sortHeader);
fixture.detectChanges();
// Expect that empty string row comes before the other values
expectTableToMatchContent(tableElement, [
['Column A\xa0Sorted by a ascending', 'Column B', 'Column C'],
['', 'b_1', 'c_1'],
['a_2', 'b_2', 'c_2'],
['a_3', 'b_3', 'c_3'],
]);
});
it('should be able to page the table contents', fakeAsync(() => {
// Add 100 rows, should only display first 5 since page length is 5
for (let i = 0; i < 100; i++) {
component.underlyingDataSource.addData();
}
fixture.detectChanges();
flushMicrotasks(); // Resolve promise that updates paginator's length
expectTableToMatchContent(tableElement, [
['Column A', 'Column B', 'Column C'],
['a_1', 'b_1', 'c_1'],
['a_2', 'b_2', 'c_2'],
['a_3', 'b_3', 'c_3'],
['a_4', 'b_4', 'c_4'],
['a_5', 'b_5', 'c_5'],
]);
// Navigate to the next page
component.paginator.nextPage();
fixture.detectChanges();
expectTableToMatchContent(tableElement, [
['Column A', 'Column B', 'Column C'],
['a_6', 'b_6', 'c_6'],
['a_7', 'b_7', 'c_7'],
['a_8', 'b_8', 'c_8'],
['a_9', 'b_9', 'c_9'],
['a_10', 'b_10', 'c_10'],
]);
}));
});
});
interface TestData {
a: string;
b: string;
c: string;
}
class FakeDataSource extends DataSource<TestData> {
_dataChange = new BehaviorSubject<TestData[]>([]);
set data(data: TestData[]) { this._dataChange.next(data); }
get data() { return this._dataChange.getValue(); }
constructor() {
super();
for (let i = 0; i < 4; i++) { this.addData(); }
}
connect(): Observable<TestData[]> {
return this._dataChange;
}
disconnect() {}
addData() {
const nextIndex = this.data.length + 1;
let copiedData = this.data.slice();
copiedData.push({
a: `a_${nextIndex}`,
b: `b_${nextIndex}`,
c: `c_${nextIndex}`
});
this.data = copiedData;
}
}
class MatTableApp {
dataSource: FakeDataSource | null = new FakeDataSource();
columnsToRender = ['column_a', 'column_b', 'column_c'];
isFourthRow = (i: number, _rowData: TestData) => i == 3;
table: MatTable<TestData>;
}
class MatTableWithWhenRowApp {
dataSource: FakeDataSource | null = new FakeDataSource();
isFourthRow = (i: number, _rowData: TestData) => i == 3;
table: MatTable<TestData>;
}
class ArrayDataSourceMatTableApp {
underlyingDataSource = new FakeDataSource();
dataSource = new MatTableDataSource<TestData>();
columnsToRender = ['column_a', 'column_b', 'column_c'];
table: MatTable<TestData>;
paginator: MatPaginator;
sort: MatSort;
sortHeader: MatSortHeader;
constructor() {
this.underlyingDataSource.data = [];
// Add three rows of data
this.underlyingDataSource.addData();
this.underlyingDataSource.addData();
this.underlyingDataSource.addData();
this.underlyingDataSource.connect().subscribe(data => {
this.dataSource.data = data;
});
}
ngAfterViewInit() {
// Needs to be set up after the view is initialized since the data source will look at the sort
// and paginator's initial values to know what data should be rendered.
this.dataSource!.sort = this.sort;
this.dataSource!.paginator = this.paginator;
}
}
// Utilities copied from CDKTable's spec
function getElements(element: Element, query: string): Element[] {
return [].slice.call(element.querySelectorAll(query));
}
function getHeaderRow(tableElement: Element): Element {
return tableElement.querySelector('.mat-header-row')!;
}
function getRows(tableElement: Element): Element[] {
return getElements(tableElement, '.mat-row');
}
function getCells(row: Element): Element[] {
return row ? getElements(row, '.mat-cell') : [];
}
function getHeaderCells(tableElement: Element): Element[] {
return getElements(getHeaderRow(tableElement), '.mat-header-cell');
}
function expectTableToMatchContent(tableElement: Element, expectedTableContent: any[]) {
const missedExpectations: string[] = [];
function checkCellContent(cell: Element, expectedTextContent: string) {
const actualTextContent = cell.textContent!.trim();
if (actualTextContent !== expectedTextContent) {
missedExpectations.push(
`Expected cell contents to be ${expectedTextContent} but was ${actualTextContent}`);
}
}
// Check header cells
const expectedHeaderContent = expectedTableContent.shift();
getHeaderCells(tableElement).forEach((cell, index) => {
const expected = expectedHeaderContent ?
expectedHeaderContent[index] :
null;
checkCellContent(cell, expected);
});
// Check data row cells
const rows = getRows(tableElement);
expect(rows.length).toBe(expectedTableContent.length,
`Found ${rows.length} rows but expected ${expectedTableContent.length}`);
rows.forEach((row, rowIndex) => {
getCells(row).forEach((cell, cellIndex) => {
const expected = expectedTableContent.length ?
expectedTableContent[rowIndex][cellIndex] :
null;
checkCellContent(cell, expected);
});
});
if (missedExpectations.length) {
fail(missedExpectations.join('\n'));
}
}