Skip to content

Commit 3854b66

Browse files
authored
feat(material-experimental/mdc-tabs): add test harnesses (#20322)
Adds test harnesses for the MDC-based tabs module.
1 parent c223fa7 commit 3854b66

File tree

8 files changed

+235
-0
lines changed

8 files changed

+235
-0
lines changed

src/material-experimental/config.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ entryPoints = [
3636
"mdc-snack-bar",
3737
"mdc-table",
3838
"mdc-tabs",
39+
"mdc-tabs/testing",
3940
"menubar",
4041
"popover-edit",
4142
"selection",
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
ts_library(
6+
name = "testing",
7+
srcs = glob(
8+
["**/*.ts"],
9+
exclude = ["**/*.spec.ts"],
10+
),
11+
module_name = "@angular/material-experimental/mdc-tabs/testing",
12+
deps = [
13+
"//src/cdk/coercion",
14+
"//src/cdk/testing",
15+
],
16+
)
17+
18+
filegroup(
19+
name = "source-files",
20+
srcs = glob(["**/*.ts"]),
21+
)
22+
23+
ng_test_library(
24+
name = "unit_tests_lib",
25+
srcs = glob(["**/*.spec.ts"]),
26+
deps = [
27+
":testing",
28+
"//src/material-experimental/mdc-tabs",
29+
"//src/material/tabs/testing:harness_tests_lib",
30+
],
31+
)
32+
33+
ng_web_test_suite(
34+
name = "unit_tests",
35+
static_files = ["@npm//:node_modules/@material/tab-indicator/dist/mdc.tabIndicator.js"],
36+
deps = [
37+
":unit_tests_lib",
38+
"//src/material-experimental:mdc_require_config.js",
39+
],
40+
)
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 './tab-group-harness';
10+
export * from './tab-harness';
11+
export * from './tab-harness-filters';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {MatTabsModule} from '@angular/material-experimental/mdc-tabs';
2+
import {runHarnessTests} from '@angular/material/tabs/testing/shared.spec';
3+
import {MatTabGroupHarness} from './tab-group-harness';
4+
5+
describe('MDC-based MatTabGroupHarness', () => {
6+
runHarnessTests(MatTabsModule, MatTabGroupHarness as any);
7+
});
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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 {TabGroupHarnessFilters, TabHarnessFilters} from './tab-harness-filters';
11+
import {MatTabHarness} from './tab-harness';
12+
13+
/** Harness for interacting with an MDC-based mat-tab-group in tests. */
14+
export class MatTabGroupHarness extends ComponentHarness {
15+
/** The selector for the host element of a `MatTabGroup` instance. */
16+
static hostSelector = '.mat-mdc-tab-group';
17+
18+
/**
19+
* Gets a `HarnessPredicate` that can be used to search for a `MatTabGroupHarness` that meets
20+
* certain criteria.
21+
* @param options Options for filtering which tab group instances are considered a match.
22+
* @return a `HarnessPredicate` configured with the given options.
23+
*/
24+
static with(options: TabGroupHarnessFilters = {}): HarnessPredicate<MatTabGroupHarness> {
25+
return new HarnessPredicate(MatTabGroupHarness, options)
26+
.addOption('selectedTabLabel', options.selectedTabLabel, async (harness, label) => {
27+
const selectedTab = await harness.getSelectedTab();
28+
return HarnessPredicate.stringMatches(await selectedTab.getLabel(), label);
29+
});
30+
}
31+
32+
/**
33+
* Gets the list of tabs in the tab group.
34+
* @param filter Optionally filters which tabs are included.
35+
*/
36+
async getTabs(filter: TabHarnessFilters = {}): Promise<MatTabHarness[]> {
37+
return this.locatorForAll(MatTabHarness.with(filter))();
38+
}
39+
40+
/** Gets the selected tab of the tab group. */
41+
async getSelectedTab(): Promise<MatTabHarness> {
42+
const tabs = await this.getTabs();
43+
const isSelected = await Promise.all(tabs.map(t => t.isSelected()));
44+
for (let i = 0; i < tabs.length; i++) {
45+
if (isSelected[i]) {
46+
return tabs[i];
47+
}
48+
}
49+
throw new Error('No selected tab could be found.');
50+
}
51+
52+
/**
53+
* Selects a tab in this tab group.
54+
* @param filter An optional filter to apply to the child tabs. The first tab matching the filter
55+
* will be selected.
56+
*/
57+
async selectTab(filter: TabHarnessFilters = {}): Promise<void> {
58+
const tabs = await this.getTabs(filter);
59+
if (!tabs.length) {
60+
throw Error(`Cannot find mat-tab matching filter ${JSON.stringify(filter)}`);
61+
}
62+
await tabs[0].select();
63+
}
64+
}
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+
import {BaseHarnessFilters} from '@angular/cdk/testing';
9+
10+
/** A set of criteria that can be used to filter a list of `MatRadioButtonHarness` instances. */
11+
export interface TabHarnessFilters extends BaseHarnessFilters {
12+
/** Only find instances whose label matches the given value. */
13+
label?: string | RegExp;
14+
}
15+
16+
/** A set of criteria that can be used to filter a list of `MatRadioButtonHarness` instances. */
17+
export interface TabGroupHarnessFilters extends BaseHarnessFilters {
18+
/** Only find instances whose selected tab label matches the given value. */
19+
selectedTabLabel?: string | RegExp;
20+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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, HarnessLoader, HarnessPredicate} from '@angular/cdk/testing';
10+
import {TabHarnessFilters} from './tab-harness-filters';
11+
12+
/** Harness for interacting with an MDC_based Angular Material tab in tests. */
13+
export class MatTabHarness extends ComponentHarness {
14+
/** The selector for the host element of a `MatTab` instance. */
15+
static hostSelector = '.mat-mdc-tab';
16+
17+
/**
18+
* Gets a `HarnessPredicate` that can be used to search for a `MatTabHarness` that meets
19+
* certain criteria.
20+
* @param options Options for filtering which tab instances are considered a match.
21+
* @return a `HarnessPredicate` configured with the given options.
22+
*/
23+
static with(options: TabHarnessFilters = {}): HarnessPredicate<MatTabHarness> {
24+
return new HarnessPredicate(MatTabHarness, options)
25+
.addOption('label', options.label,
26+
(harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label));
27+
}
28+
29+
/** Gets the label of the tab. */
30+
async getLabel(): Promise<string> {
31+
return (await this.host()).text();
32+
}
33+
34+
/** Gets the aria-label of the tab. */
35+
async getAriaLabel(): Promise<string|null> {
36+
return (await this.host()).getAttribute('aria-label');
37+
}
38+
39+
/** Gets the value of the "aria-labelledby" attribute. */
40+
async getAriaLabelledby(): Promise<string|null> {
41+
return (await this.host()).getAttribute('aria-labelledby');
42+
}
43+
44+
/** Whether the tab is selected. */
45+
async isSelected(): Promise<boolean> {
46+
const hostEl = await this.host();
47+
return (await hostEl.getAttribute('aria-selected')) === 'true';
48+
}
49+
50+
/** Whether the tab is disabled. */
51+
async isDisabled(): Promise<boolean> {
52+
const hostEl = await this.host();
53+
return (await hostEl.getAttribute('aria-disabled')) === 'true';
54+
}
55+
56+
/** Selects the given tab by clicking on the label. Tab cannot be selected if disabled. */
57+
async select(): Promise<void> {
58+
await (await this.host()).click();
59+
}
60+
61+
/** Gets the text content of the tab. */
62+
async getTextContent(): Promise<string> {
63+
const contentId = await this._getContentId();
64+
const contentEl = await this.documentRootLocatorFactory().locatorFor(`#${contentId}`)();
65+
return contentEl.text();
66+
}
67+
68+
/**
69+
* Gets a `HarnessLoader` that can be used to load harnesses for components within the tab's
70+
* content area.
71+
*/
72+
async getHarnessLoaderForContent(): Promise<HarnessLoader> {
73+
const contentId = await this._getContentId();
74+
return this.documentRootLocatorFactory().harnessLoaderFor(`#${contentId}`);
75+
}
76+
77+
/** Gets the element id for the content of the current tab. */
78+
private async _getContentId(): Promise<string> {
79+
const hostEl = await this.host();
80+
// Tabs never have an empty "aria-controls" attribute.
81+
return (await hostEl.getAttribute('aria-controls'))!;
82+
}
83+
}

0 commit comments

Comments
 (0)