Skip to content

Commit cc35d28

Browse files
committed
feat(table): add test harness
Adds a test harness for `MatTable`, as well as the related row and cell directives.
1 parent 63653bb commit cc35d28

File tree

5 files changed

+89
-143
lines changed

5 files changed

+89
-143
lines changed

src/material/table/testing/cell-harness.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing';
9+
import {
10+
ComponentHarness,
11+
HarnessPredicate,
12+
ComponentHarnessConstructor,
13+
} from '@angular/cdk/testing';
1014
import {CellHarnessFilters} from './table-harness-filters';
1115

1216
/** Harness for interacting with a standard Angular Material table cell. */
@@ -17,9 +21,7 @@ export class MatCellHarness extends ComponentHarness {
1721
* Gets a `HarnessPredicate` that can be used to search for a cell with specific attributes.
1822
*/
1923
static with(options: CellHarnessFilters = {}): HarnessPredicate<MatCellHarness> {
20-
return new HarnessPredicate(MatCellHarness, options)
21-
.addOption('text', options.text,
22-
(harness, text) => HarnessPredicate.stringMatches(harness.getText(), text));
24+
return getCellPredicate(MatCellHarness, options);
2325
}
2426

2527
/** Gets a promise for the cell's text. */
@@ -53,9 +55,7 @@ export class MatHeaderCellHarness extends MatCellHarness {
5355
* Gets a `HarnessPredicate` that can be used to search for a header cell with specific attributes
5456
*/
5557
static with(options: CellHarnessFilters = {}): HarnessPredicate<MatHeaderCellHarness> {
56-
return new HarnessPredicate(MatHeaderCellHarness, options)
57-
.addOption('text', options.text,
58-
(harness, text) => HarnessPredicate.stringMatches(harness.getText(), text));
58+
return getCellPredicate(MatHeaderCellHarness, options);
5959
}
6060
}
6161

@@ -67,8 +67,15 @@ export class MatFooterCellHarness extends MatCellHarness {
6767
* Gets a `HarnessPredicate` that can be used to search for a footer cell with specific attributes
6868
*/
6969
static with(options: CellHarnessFilters = {}): HarnessPredicate<MatFooterCellHarness> {
70-
return new HarnessPredicate(MatFooterCellHarness, options)
71-
.addOption('text', options.text,
72-
(harness, text) => HarnessPredicate.stringMatches(harness.getText(), text));
70+
return getCellPredicate(MatFooterCellHarness, options);
7371
}
7472
}
73+
74+
75+
function getCellPredicate<T extends MatCellHarness>(
76+
type: ComponentHarnessConstructor<T>,
77+
options: CellHarnessFilters): HarnessPredicate<T> {
78+
return new HarnessPredicate(type, options)
79+
.addOption('text', options.text,
80+
(harness, text) => HarnessPredicate.stringMatches(harness.getText(), text));
81+
}

src/material/table/testing/row-harness.ts

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,6 @@ import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing';
1010
import {RowHarnessFilters, CellHarnessFilters} from './table-harness-filters';
1111
import {MatCellHarness, MatHeaderCellHarness, MatFooterCellHarness} from './cell-harness';
1212

13-
/** Data extracted from the cells in a table row. */
14-
export type MatRowHarnessData = {
15-
columnName: string;
16-
text: string;
17-
}[];
18-
1913
/** Harness for interacting with a standard Angular Material table row. */
2014
export class MatRowHarness extends ComponentHarness {
2115
static hostSelector = '.mat-row';
@@ -32,9 +26,9 @@ export class MatRowHarness extends ComponentHarness {
3226
return this.locatorForAll(MatCellHarness.with(filter))();
3327
}
3428

35-
/** Gets the data of the cells in the footer row. */
36-
async getData(filter: CellHarnessFilters = {}): Promise<MatRowHarnessData> {
37-
return getCellData(await this.getCells(filter));
29+
/** Gets the text of the cells in the row. */
30+
async getCellTextByIndex(filter: CellHarnessFilters = {}): Promise<string[]> {
31+
return getCellTextByIndex(this, filter);
3832
}
3933
}
4034

@@ -54,9 +48,9 @@ export class MatHeaderRowHarness extends ComponentHarness {
5448
return this.locatorForAll(MatHeaderCellHarness.with(filter))();
5549
}
5650

57-
/** Gets the data of the cells in the footer row. */
58-
async getData(filter: CellHarnessFilters = {}): Promise<MatRowHarnessData> {
59-
return getCellData(await this.getCells(filter));
51+
/** Gets the text of the cells in the header row. */
52+
async getCellTextByIndex(filter: CellHarnessFilters = {}): Promise<string[]> {
53+
return getCellTextByIndex(this, filter);
6054
}
6155
}
6256

@@ -77,16 +71,15 @@ export class MatFooterRowHarness extends ComponentHarness {
7771
return this.locatorForAll(MatFooterCellHarness.with(filter))();
7872
}
7973

80-
/** Gets the data of the cells in the footer row. */
81-
async getData(filter: CellHarnessFilters = {}): Promise<MatRowHarnessData> {
82-
return getCellData(await this.getCells(filter));
74+
/** Gets the text of the cells in the footer row. */
75+
async getCellTextByIndex(filter: CellHarnessFilters = {}): Promise<string[]> {
76+
return getCellTextByIndex(this, filter);
8377
}
8478
}
8579

86-
/** Extracts the data from the cells in a row. */
87-
async function getCellData(cells: MatCellHarness[]): Promise<MatRowHarnessData> {
88-
return Promise.all(cells.map(async cell => ({
89-
text: await cell.getText(),
90-
columnName: await cell.getColumnName()
91-
})));
80+
async function getCellTextByIndex(harness: {
81+
getCells: (filter?: CellHarnessFilters) => Promise<MatCellHarness[]>
82+
}, filter: CellHarnessFilters): Promise<string[]> {
83+
const cells = await harness.getCells(filter);
84+
return Promise.all(cells.map(cell => cell.getText()));
9285
}

src/material/table/testing/shared.spec.ts

Lines changed: 17 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,11 @@ export function runHarnessTests(
8787
expect(cellTexts).toEqual(['Hydrogen', 'H']);
8888
});
8989

90-
it('should be able to get the table data organized by columns', async () => {
90+
it('should be able to get the table text organized by columns', async () => {
9191
const table = await loader.getHarness(tableHarness);
92-
const data = await table.getColumnsData();
92+
const text = await table.getCellTextByColumnName();
9393

94-
expect(data).toEqual({
94+
expect(text).toEqual({
9595
position: {
9696
headerText: 'No.',
9797
footerText: 'Number of the element',
@@ -121,71 +121,21 @@ export function runHarnessTests(
121121
});
122122
});
123123

124-
it('should be able to get the table data organized by rows', async () => {
124+
it('should be able to get the table text organized by rows', async () => {
125125
const table = await loader.getHarness(tableHarness);
126-
const data = await table.getRowsData();
127-
128-
expect(data).toEqual([
129-
[
130-
{text: '1', columnName: 'position'},
131-
{text: 'Hydrogen', columnName: 'name'},
132-
{text: '1.0079', columnName: 'weight'},
133-
{text: 'H', columnName: 'symbol'}
134-
],
135-
[
136-
{text: '2', columnName: 'position'},
137-
{text: 'Helium', columnName: 'name'},
138-
{text: '4.0026', columnName: 'weight'},
139-
{text: 'He', columnName: 'symbol'}
140-
],
141-
[
142-
{text: '3', columnName: 'position'},
143-
{text: 'Lithium', columnName: 'name'},
144-
{text: '6.941', columnName: 'weight'},
145-
{text: 'Li', columnName: 'symbol'}
146-
],
147-
[
148-
{text: '4', columnName: 'position'},
149-
{text: 'Beryllium', columnName: 'name'},
150-
{text: '9.0122', columnName: 'weight'},
151-
{text: 'Be', columnName: 'symbol'}
152-
],
153-
[
154-
{text: '5', columnName: 'position'},
155-
{text: 'Boron', columnName: 'name'},
156-
{text: '10.811', columnName: 'weight'},
157-
{text: 'B', columnName: 'symbol'}
158-
],
159-
[
160-
{text: '6', columnName: 'position'},
161-
{text: 'Carbon', columnName: 'name'},
162-
{text: '12.0107', columnName: 'weight'},
163-
{text: 'C', columnName: 'symbol'}
164-
],
165-
[
166-
{text: '7', columnName: 'position'},
167-
{text: 'Nitrogen', columnName: 'name'},
168-
{text: '14.0067', columnName: 'weight'},
169-
{text: 'N', columnName: 'symbol'}
170-
],
171-
[
172-
{text: '8', columnName: 'position'},
173-
{text: 'Oxygen', columnName: 'name'},
174-
{text: '15.9994', columnName: 'weight'},
175-
{text: 'O', columnName: 'symbol'}
176-
],
177-
[
178-
{text: '9', columnName: 'position'},
179-
{text: 'Fluorine', columnName: 'name'},
180-
{text: '18.9984', columnName: 'weight'},
181-
{text: 'F', columnName: 'symbol'}
182-
],
183-
[
184-
{text: '10', columnName: 'position'},
185-
{text: 'Neon', columnName: 'name'},
186-
{text: '20.1797', columnName: 'weight'},
187-
{text: 'Ne', columnName: 'symbol'}
188-
]
126+
const text = await table.getCellTextByIndex();
127+
128+
expect(text).toEqual([
129+
['1', 'Hydrogen', '1.0079', 'H'],
130+
['2', 'Helium', '4.0026', 'He'],
131+
['3', 'Lithium', '6.941', 'Li'],
132+
['4', 'Beryllium', '9.0122', 'Be'],
133+
['5', 'Boron', '10.811', 'B'],
134+
['6', 'Carbon', '12.0107', 'C'],
135+
['7', 'Nitrogen', '14.0067', 'N'],
136+
['8', 'Oxygen', '15.9994', 'O'],
137+
['9', 'Fluorine', '18.9984', 'F'],
138+
['10', 'Neon', '20.1797', 'Ne']
189139
]);
190140
});
191141
}

src/material/table/testing/table-harness.ts

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,14 @@ import {TableHarnessFilters, RowHarnessFilters} from './table-harness-filters';
1111
import {MatRowHarness, MatHeaderRowHarness, MatFooterRowHarness} from './row-harness';
1212

1313
/** Data extracted from a table organized by columns. */
14-
export interface MatTableHarnessColumnsData {
14+
export interface MatTableHarnessColumnsText {
1515
[columnName: string]: {
1616
text: string[];
1717
headerText: string;
1818
footerText: string;
1919
};
2020
}
2121

22-
/** Data extracted from a table organized by rows. */
23-
export type MatTableHarnessRowsData = {
24-
columnName: string;
25-
text: string;
26-
}[][];
27-
2822
/** Harness for interacting with a standard mat-table in tests. */
2923
export class MatTableHarness extends ComponentHarness {
3024
static hostSelector = '.mat-table';
@@ -54,41 +48,53 @@ export class MatTableHarness extends ComponentHarness {
5448
return this.locatorForAll(MatFooterRowHarness.with(filter))();
5549
}
5650

57-
/** Gets the data inside the entire table organized by rows. */
58-
async getRowsData(): Promise<MatTableHarnessRowsData> {
51+
/** Gets the text inside the entire table organized by rows. */
52+
async getCellTextByIndex(): Promise<string[][]> {
5953
const rows = await this.getRows();
60-
return Promise.all(rows.map(row => row.getData()));
54+
return Promise.all(rows.map(row => row.getCellTextByIndex()));
6155
}
6256

63-
/** Gets the data inside the entire table organized by columns. */
64-
async getColumnsData(): Promise<MatTableHarnessColumnsData> {
65-
// Tables can have multiple header rows, but we consider the first one as the "main" row.
66-
const headerRow = (await this.getHeaderRows())[0];
67-
const footerRow = (await this.getFooterRows())[0];
68-
const dataRows = await this.getRows();
57+
/** Gets the text inside the entire table organized by columns. */
58+
async getCellTextByColumnName(): Promise<MatTableHarnessColumnsText> {
59+
const [headerRow, footerRow, dataRows] = await Promise.all([
60+
// Tables can have multiple header rows, but we consider the first one as the "main" row.
61+
this.getHeaderRows().then(rows => rows[0]),
62+
this.getFooterRows().then(rows => rows[0]),
63+
this.getRows()
64+
]);
6965

70-
const headerData = headerRow ? await headerRow.getData() : [];
71-
const footerData = footerRow ? await footerRow.getData() : [];
72-
const rowsData = await Promise.all(dataRows.map(row => row.getData()));
73-
const data: MatTableHarnessColumnsData = {};
66+
const text: MatTableHarnessColumnsText = {};
67+
const [headerData, footerData, rowsData] = await Promise.all([
68+
headerRow ? getRowData(headerRow) : [],
69+
footerRow ? getRowData(footerRow) : [],
70+
Promise.all(dataRows.map(row => getRowData(row))),
71+
]);
7472

7573
rowsData.forEach(cells => {
76-
cells.forEach(cell => {
77-
if (!data[cell.columnName]) {
78-
const headerCell = headerData.find(header => header.columnName === cell.columnName);
79-
const footerCell = footerData.find(footer => footer.columnName === cell.columnName);
74+
cells.forEach(([columnName, cellText]) => {
75+
if (!text[columnName]) {
76+
const headerCell =
77+
headerData.find(([headerColumnName]) => headerColumnName === columnName);
78+
const footerCell =
79+
footerData.find(([footerColumnName]) => footerColumnName === columnName);
8080

81-
data[cell.columnName] = {
82-
headerText: headerCell ? headerCell.text : '',
83-
footerText: footerCell ? footerCell.text : '',
81+
text[columnName] = {
82+
headerText: headerCell ? headerCell[1] : '',
83+
footerText: footerCell ? footerCell[1] : '',
8484
text: []
8585
};
8686
}
8787

88-
data[cell.columnName].text.push(cell.text);
88+
text[columnName].text.push(cellText);
8989
});
9090
});
9191

92-
return data;
92+
return text;
9393
}
9494
}
95+
96+
/** Utility to extract the column names and text from all of the cells in a row. */
97+
async function getRowData(row: MatRowHarness | MatHeaderRowHarness | MatFooterRowHarness) {
98+
const cells = await row.getCells();
99+
return Promise.all(cells.map(cell => Promise.all([cell.getColumnName(), cell.getText()])));
100+
}

tools/public_api_guard/material/table/testing.d.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ export declare class MatFooterCellHarness extends MatCellHarness {
1515
}
1616

1717
export declare class MatFooterRowHarness extends ComponentHarness {
18+
getCellTextByIndex(filter?: CellHarnessFilters): Promise<string[]>;
1819
getCells(filter?: CellHarnessFilters): Promise<MatFooterCellHarness[]>;
19-
getData(filter?: CellHarnessFilters): Promise<MatRowHarnessData>;
2020
static hostSelector: string;
2121
static with(options?: RowHarnessFilters): HarnessPredicate<MatFooterRowHarness>;
2222
}
@@ -27,47 +27,37 @@ export declare class MatHeaderCellHarness extends MatCellHarness {
2727
}
2828

2929
export declare class MatHeaderRowHarness extends ComponentHarness {
30+
getCellTextByIndex(filter?: CellHarnessFilters): Promise<string[]>;
3031
getCells(filter?: CellHarnessFilters): Promise<MatHeaderCellHarness[]>;
31-
getData(filter?: CellHarnessFilters): Promise<MatRowHarnessData>;
3232
static hostSelector: string;
3333
static with(options?: RowHarnessFilters): HarnessPredicate<MatHeaderRowHarness>;
3434
}
3535

3636
export declare class MatRowHarness extends ComponentHarness {
37+
getCellTextByIndex(filter?: CellHarnessFilters): Promise<string[]>;
3738
getCells(filter?: CellHarnessFilters): Promise<MatCellHarness[]>;
38-
getData(filter?: CellHarnessFilters): Promise<MatRowHarnessData>;
3939
static hostSelector: string;
4040
static with(options?: RowHarnessFilters): HarnessPredicate<MatRowHarness>;
4141
}
4242

43-
export declare type MatRowHarnessData = {
44-
columnName: string;
45-
text: string;
46-
}[];
47-
4843
export declare class MatTableHarness extends ComponentHarness {
49-
getColumnsData(): Promise<MatTableHarnessColumnsData>;
44+
getCellTextByColumnName(): Promise<MatTableHarnessColumnsText>;
45+
getCellTextByIndex(): Promise<string[][]>;
5046
getFooterRows(filter?: RowHarnessFilters): Promise<MatFooterRowHarness[]>;
5147
getHeaderRows(filter?: RowHarnessFilters): Promise<MatHeaderRowHarness[]>;
5248
getRows(filter?: RowHarnessFilters): Promise<MatRowHarness[]>;
53-
getRowsData(): Promise<MatTableHarnessRowsData>;
5449
static hostSelector: string;
5550
static with(options?: TableHarnessFilters): HarnessPredicate<MatTableHarness>;
5651
}
5752

58-
export interface MatTableHarnessColumnsData {
53+
export interface MatTableHarnessColumnsText {
5954
[columnName: string]: {
6055
text: string[];
6156
headerText: string;
6257
footerText: string;
6358
};
6459
}
6560

66-
export declare type MatTableHarnessRowsData = {
67-
columnName: string;
68-
text: string;
69-
}[][];
70-
7161
export interface RowHarnessFilters extends BaseHarnessFilters {
7262
}
7363

0 commit comments

Comments
 (0)