Skip to content

Commit 5e9ca28

Browse files
devversionmmalerba
authored andcommitted
feat(material/grid-list): add test harness (#18001)
1 parent dda6af7 commit 5e9ca28

12 files changed

+470
-4
lines changed

src/material/grid-list/grid-list.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ const MAT_FIT_MODE = 'fit';
3939
styleUrls: ['grid-list.css'],
4040
host: {
4141
'class': 'mat-grid-list',
42+
// Ensures that the "cols" input value is reflected in the DOM. This is
43+
// needed for the grid-list harness.
44+
'[attr.cols]': 'cols',
4245
},
4346
providers: [{
4447
provide: MAT_GRID_LIST,

src/material/grid-list/grid-tile.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ import {MAT_GRID_LIST, MatGridListBase} from './grid-list-base';
2828
exportAs: 'matGridTile',
2929
host: {
3030
'class': 'mat-grid-tile',
31+
// Ensures that the "rowspan" and "colspan" input value is reflected in
32+
// the DOM. This is needed for the grid-tile harness.
33+
'[attr.rowspan]': 'rowspan',
34+
'[attr.colspan]': 'colspan'
3135
},
3236
templateUrl: 'grid-tile.html',
3337
styleUrls: ['grid-list.css'],

src/material/grid-list/public-api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ export * from './grid-list-module';
1010
export * from './grid-list';
1111
export * from './grid-tile';
1212

13+
// Privately exported for the grid-list harness.
14+
export {TileCoordinator as ɵTileCoordinator} from './tile-coordinator';
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package(default_visibility = ["//visibility:public"])
2+
3+
load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
4+
5+
ts_library(
6+
name = "testing",
7+
srcs = glob(
8+
["**/*.ts"],
9+
exclude = ["**/*.spec.ts"],
10+
),
11+
module_name = "@angular/material/grid-list/testing",
12+
deps = [
13+
"//src/cdk/testing",
14+
"//src/material/grid-list",
15+
],
16+
)
17+
18+
filegroup(
19+
name = "source-files",
20+
srcs = glob(["**/*.ts"]),
21+
)
22+
23+
ng_test_library(
24+
name = "harness_tests_lib",
25+
srcs = ["shared.spec.ts"],
26+
deps = [
27+
":testing",
28+
"//src/cdk/testing",
29+
"//src/cdk/testing/private",
30+
"//src/cdk/testing/testbed",
31+
"//src/material/grid-list",
32+
"@npm//@angular/platform-browser",
33+
],
34+
)
35+
36+
ng_test_library(
37+
name = "unit_tests_lib",
38+
srcs = glob(
39+
["**/*.spec.ts"],
40+
exclude = ["shared.spec.ts"],
41+
),
42+
deps = [
43+
":harness_tests_lib",
44+
":testing",
45+
"//src/material/grid-list",
46+
],
47+
)
48+
49+
ng_web_test_suite(
50+
name = "unit_tests",
51+
deps = [":unit_tests_lib"],
52+
)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {BaseHarnessFilters} from '@angular/cdk/testing';
10+
11+
/** A set of criteria that can be used to filter a list of `MatGridListHarness` instances. */
12+
export interface GridListHarnessFilters extends BaseHarnessFilters {}
13+
14+
/** A set of criteria that can be used to filter a list of `MatTileHarness` instances. */
15+
export interface GridTileHarnessFilters extends BaseHarnessFilters {
16+
/** Text the grid-tile header should match. */
17+
headerText?: string|RegExp;
18+
/** Text the grid-tile footer should match. */
19+
footerText?: string|RegExp;
20+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import {MatGridListModule} from '@angular/material/grid-list';
2+
import {MatGridListHarness} from './grid-list-harness';
3+
import {MatGridTileHarness} from './grid-tile-harness';
4+
import {runHarnessTests} from './shared.spec';
5+
6+
describe('Non-MDC-based MatGridListHarness', () => {
7+
runHarnessTests(MatGridListModule, MatGridListHarness, MatGridTileHarness);
8+
});
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing';
10+
import {ɵTileCoordinator as TileCoordinator} from '@angular/material/grid-list';
11+
import {GridListHarnessFilters, GridTileHarnessFilters} from './grid-list-harness-filters';
12+
import {MatGridTileHarness} from './grid-tile-harness';
13+
14+
/** Harness for interacting with a standard `MatGridList` in tests. */
15+
export class MatGridListHarness extends ComponentHarness {
16+
/** The selector for the host element of a `MatGridList` instance. */
17+
static hostSelector = '.mat-grid-list';
18+
19+
/**
20+
* Gets a `HarnessPredicate` that can be used to search for a `MatGridListHarness`
21+
* that meets certain criteria.
22+
* @param options Options for filtering which dialog instances are considered a match.
23+
* @return a `HarnessPredicate` configured with the given options.
24+
*/
25+
static with(options: GridListHarnessFilters = {}): HarnessPredicate<MatGridListHarness> {
26+
return new HarnessPredicate(MatGridListHarness, options);
27+
}
28+
29+
/**
30+
* Tile coordinator that is used by the "MatGridList" for computing
31+
* positions of tiles. We leverage the coordinator to provide an API
32+
* for retrieving tiles based on visual tile positions.
33+
*/
34+
private _tileCoordinator = new TileCoordinator();
35+
36+
/** Gets all tiles of the grid-list. */
37+
async getTiles(filters: GridTileHarnessFilters = {}): Promise<MatGridTileHarness[]> {
38+
return await this.locatorForAll(MatGridTileHarness.with(filters))();
39+
}
40+
41+
/** Gets the amount of columns of the grid-list. */
42+
async getColumns(): Promise<number> {
43+
return Number(await (await this.host()).getAttribute('cols'));
44+
}
45+
46+
/**
47+
* Gets a tile of the grid-list that is located at the given location.
48+
* @param row Zero-based row index.
49+
* @param column Zero-based column index.
50+
*/
51+
async getTileAtPosition({row, column}: {row: number, column: number}):
52+
Promise<MatGridTileHarness> {
53+
const [tileHarnesses, columns] = await Promise.all([this.getTiles(), this.getColumns()]);
54+
const tileSpans = tileHarnesses.map(t => Promise.all([t.getColspan(), t.getRowspan()]));
55+
const tiles = (await Promise.all(tileSpans)).map(([colspan, rowspan]) => ({colspan, rowspan}));
56+
// Update the tile coordinator to reflect the current column amount and
57+
// rendered tiles. We update upon every call of this method since we do not
58+
// know if tiles have been added, removed or updated (in terms of rowspan/colspan).
59+
this._tileCoordinator.update(columns, tiles);
60+
// The tile coordinator respects the colspan and rowspan for calculating the positions
61+
// of tiles, but it does not create multiple position entries if a tile spans over multiple
62+
// columns or rows. We want to provide an API where developers can retrieve a tile based on
63+
// any position that lies within the visual tile boundaries. For example: If a tile spans
64+
// over two columns, then the same tile should be returned for either column indices.
65+
for (let i = 0; i < this._tileCoordinator.positions.length; i++) {
66+
const position = this._tileCoordinator.positions[i];
67+
const {rowspan, colspan} = tiles[i];
68+
// Return the tile harness if the given position visually resolves to the tile.
69+
if (column >= position.col && column <= position.col + colspan - 1 && row >= position.row &&
70+
row <= position.row + rowspan - 1) {
71+
return tileHarnesses[i];
72+
}
73+
}
74+
throw Error('Could not find tile at given position.');
75+
}
76+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing';
10+
import {GridTileHarnessFilters} from './grid-list-harness-filters';
11+
12+
/** Harness for interacting with a standard `MatGridTitle` in tests. */
13+
export class MatGridTileHarness extends ComponentHarness {
14+
/** The selector for the host element of a `MatGridTile` instance. */
15+
static hostSelector = '.mat-grid-tile';
16+
17+
/**
18+
* Gets a `HarnessPredicate` that can be used to search for a `MatGridTileHarness`
19+
* that meets certain criteria.
20+
* @param options Options for filtering which dialog instances are considered a match.
21+
* @return a `HarnessPredicate` configured with the given options.
22+
*/
23+
static with(options: GridTileHarnessFilters = {}): HarnessPredicate<MatGridTileHarness> {
24+
return new HarnessPredicate(MatGridTileHarness, options)
25+
.addOption(
26+
'headerText', options.headerText,
27+
(harness, pattern) => HarnessPredicate.stringMatches(harness.getHeaderText(), pattern))
28+
.addOption(
29+
'footerText', options.footerText,
30+
(harness, pattern) => HarnessPredicate.stringMatches(harness.getFooterText(), pattern));
31+
}
32+
33+
private _header = this.locatorForOptional('.mat-grid-tile-header');
34+
private _footer = this.locatorForOptional('.mat-grid-tile-footer');
35+
private _avatar = this.locatorForOptional('.mat-grid-avatar');
36+
37+
/** Gets the amount of rows that the grid-tile takes up. */
38+
async getRowspan(): Promise<number> {
39+
return Number(await (await this.host()).getAttribute('rowspan'));
40+
}
41+
42+
/** Gets the amount of columns that the grid-tile takes up. */
43+
async getColspan(): Promise<number> {
44+
return Number(await (await this.host()).getAttribute('colspan'));
45+
}
46+
47+
/** Whether the grid-tile has a header. */
48+
async hasHeader(): Promise<boolean> {
49+
return (await this._header()) !== null;
50+
}
51+
52+
/** Whether the grid-tile has a footer. */
53+
async hasFooter(): Promise<boolean> {
54+
return (await this._footer()) !== null;
55+
}
56+
57+
/** Whether the grid-tile has an avatar. */
58+
async hasAvatar(): Promise<boolean> {
59+
return (await this._avatar()) !== null;
60+
}
61+
62+
/** Gets the text of the header if present. */
63+
async getHeaderText(): Promise<string|null> {
64+
// For performance reasons, we do not use "hasHeader" as
65+
// we would then need to query twice for the header.
66+
const headerEl = await this._header();
67+
return headerEl ? headerEl.text() : null;
68+
}
69+
70+
/** Gets the text of the footer if present. */
71+
async getFooterText(): Promise<string|null> {
72+
// For performance reasons, we do not use "hasFooter" as
73+
// we would then need to query twice for the footer.
74+
const headerEl = await this._footer();
75+
return headerEl ? headerEl.text() : null;
76+
}
77+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
export * from './public-api';
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
export * from './grid-tile-harness';
10+
export * from './grid-list-harness';
11+
export * from './grid-list-harness-filters';

0 commit comments

Comments
 (0)