UNPKG

ipsos-components

Version:

Material Design components for Angular

456 lines (393 loc) 15.9 kB
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; } } @Component({ template: ` <mat-table [dataSource]="dataSource"> <ng-container matColumnDef="column_a"> <mat-header-cell *matHeaderCellDef> Column A</mat-header-cell> <mat-cell *matCellDef="let row"> {{row.a}}</mat-cell> </ng-container> <ng-container matColumnDef="column_b"> <mat-header-cell *matHeaderCellDef> Column B</mat-header-cell> <mat-cell *matCellDef="let row"> {{row.b}}</mat-cell> </ng-container> <ng-container matColumnDef="column_c"> <mat-header-cell *matHeaderCellDef> Column C</mat-header-cell> <mat-cell *matCellDef="let row"> {{row.c}}</mat-cell> </ng-container> <ng-container matColumnDef="special_column"> <mat-cell *matCellDef="let row"> fourth_row </mat-cell> </ng-container> <mat-header-row *matHeaderRowDef="columnsToRender"></mat-header-row> <mat-row *matRowDef="let row; columns: columnsToRender"></mat-row> <mat-row *matRowDef="let row; columns: ['special_column']; when: isFourthRow"></mat-row> </mat-table> ` }) class MatTableApp { dataSource: FakeDataSource | null = new FakeDataSource(); columnsToRender = ['column_a', 'column_b', 'column_c']; isFourthRow = (i: number, _rowData: TestData) => i == 3; @ViewChild(MatTable) table: MatTable<TestData>; } @Component({ template: ` <mat-table [dataSource]="dataSource"> <ng-container matColumnDef="column_a"> <mat-header-cell *matHeaderCellDef> Column A</mat-header-cell> <mat-cell *matCellDef="let row"> {{row.a}}</mat-cell> </ng-container> <ng-container matColumnDef="special_column"> <mat-cell *matCellDef="let row"> fourth_row </mat-cell> </ng-container> <mat-header-row *matHeaderRowDef="['column_a']"></mat-header-row> <mat-row *matRowDef="let row; columns: ['column_a']"></mat-row> <mat-row *matRowDef="let row; columns: ['special_column']; when: isFourthRow"></mat-row> </mat-table> ` }) class MatTableWithWhenRowApp { dataSource: FakeDataSource | null = new FakeDataSource(); isFourthRow = (i: number, _rowData: TestData) => i == 3; @ViewChild(MatTable) table: MatTable<TestData>; } @Component({ template: ` <mat-table [dataSource]="dataSource" matSort> <ng-container matColumnDef="column_a"> <mat-header-cell *matHeaderCellDef mat-sort-header="a"> Column A</mat-header-cell> <mat-cell *matCellDef="let row"> {{row.a}}</mat-cell> </ng-container> <ng-container matColumnDef="column_b"> <mat-header-cell *matHeaderCellDef> Column B</mat-header-cell> <mat-cell *matCellDef="let row"> {{row.b}}</mat-cell> </ng-container> <ng-container matColumnDef="column_c"> <mat-header-cell *matHeaderCellDef> Column C</mat-header-cell> <mat-cell *matCellDef="let row"> {{row.c}}</mat-cell> </ng-container> <mat-header-row *matHeaderRowDef="columnsToRender"></mat-header-row> <mat-row *matRowDef="let row; columns: columnsToRender"></mat-row> </mat-table> <mat-paginator [pageSize]="5"></mat-paginator> ` }) class ArrayDataSourceMatTableApp { underlyingDataSource = new FakeDataSource(); dataSource = new MatTableDataSource<TestData>(); columnsToRender = ['column_a', 'column_b', 'column_c']; @ViewChild(MatTable) table: MatTable<TestData>; @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; @ViewChild(MatSortHeader) 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')); } }