Skip to content

Commit 6441810

Browse files
committed
feat(material-experimental/mdc-select): add test harness
Adds a test harness for the MDC-based `mat-select`.
1 parent c21c6f5 commit 6441810

File tree

10 files changed

+244
-3
lines changed

10 files changed

+244
-3
lines changed

src/material-experimental/config.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ entryPoints = [
2828
"mdc-radio",
2929
"mdc-radio/testing",
3030
"mdc-select",
31+
"mdc-select/testing",
3132
"mdc-sidenav",
3233
"mdc-slide-toggle",
3334
"mdc-slide-toggle/testing",
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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-select/testing",
12+
deps = [
13+
"//src/cdk/coercion",
14+
"//src/cdk/testing",
15+
"//src/material-experimental/mdc-core/testing",
16+
"//src/material/form-field/testing/control",
17+
],
18+
)
19+
20+
filegroup(
21+
name = "source-files",
22+
srcs = glob(["**/*.ts"]),
23+
)
24+
25+
ng_test_library(
26+
name = "unit_tests_lib",
27+
srcs = glob(["**/*.spec.ts"]),
28+
deps = [
29+
":testing",
30+
"//src/material-experimental/mdc-form-field",
31+
"//src/material-experimental/mdc-select",
32+
"//src/material/select/testing:harness_tests_lib",
33+
],
34+
)
35+
36+
ng_web_test_suite(
37+
name = "unit_tests",
38+
static_files = [
39+
"@npm//:node_modules/@material/textfield/dist/mdc.textfield.js",
40+
"@npm//:node_modules/@material/line-ripple/dist/mdc.lineRipple.js",
41+
"@npm//:node_modules/@material/notched-outline/dist/mdc.notchedOutline.js",
42+
"@npm//:node_modules/@material/dom/dist/mdc.dom.js",
43+
],
44+
deps = [
45+
":unit_tests_lib",
46+
"//src/material-experimental:mdc_require_config.js",
47+
],
48+
)
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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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 './select-harness';
10+
export * from './select-harness-filters';
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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 `MatSelectHarness` instances. */
12+
export interface SelectHarnessFilters extends BaseHarnessFilters {}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import {MatSelectModule} from '@angular/material-experimental/mdc-select';
2+
import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field';
3+
import {runHarnessTests} from '@angular/material/select/testing/shared.spec';
4+
import {MatSelectHarness} from './index';
5+
6+
describe('MDC-based MatSelectHarness', () => {
7+
runHarnessTests(MatFormFieldModule, MatSelectModule, MatSelectHarness as any);
8+
});
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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 {HarnessPredicate} from '@angular/cdk/testing';
10+
import {MatFormFieldControlHarness} from '@angular/material/form-field/testing/control';
11+
import {
12+
MatOptionHarness,
13+
MatOptgroupHarness,
14+
OptionHarnessFilters,
15+
OptgroupHarnessFilters,
16+
} from '@angular/material-experimental/mdc-core/testing';
17+
import {SelectHarnessFilters} from './select-harness-filters';
18+
19+
20+
/** Harness for interacting with an MDC-based mat-select in tests. */
21+
export class MatSelectHarness extends MatFormFieldControlHarness {
22+
private _documentRootLocator = this.documentRootLocatorFactory();
23+
private _backdrop = this._documentRootLocator.locatorFor('.cdk-overlay-backdrop');
24+
private _trigger = this.locatorFor('.mat-mdc-select-trigger');
25+
private _value = this.locatorFor('.mat-mdc-select-value');
26+
27+
static hostSelector = '.mat-mdc-select';
28+
29+
/**
30+
* Gets a `HarnessPredicate` that can be used to search for a `MatSelectHarness` that meets
31+
* certain criteria.
32+
* @param options Options for filtering which select instances are considered a match.
33+
* @return a `HarnessPredicate` configured with the given options.
34+
*/
35+
static with(options: SelectHarnessFilters = {}): HarnessPredicate<MatSelectHarness> {
36+
return new HarnessPredicate(MatSelectHarness, options);
37+
}
38+
39+
/** Gets a boolean promise indicating if the select is disabled. */
40+
async isDisabled(): Promise<boolean> {
41+
return (await this.host()).hasClass('mat-mdc-select-disabled');
42+
}
43+
44+
/** Gets a boolean promise indicating if the select is valid. */
45+
async isValid(): Promise<boolean> {
46+
return !(await (await this.host()).hasClass('ng-invalid'));
47+
}
48+
49+
/** Gets a boolean promise indicating if the select is required. */
50+
async isRequired(): Promise<boolean> {
51+
return (await this.host()).hasClass('mat-mdc-select-required');
52+
}
53+
54+
/** Gets a boolean promise indicating if the select is empty (no value is selected). */
55+
async isEmpty(): Promise<boolean> {
56+
return (await this.host()).hasClass('mat-mdc-select-empty');
57+
}
58+
59+
/** Gets a boolean promise indicating if the select is in multi-selection mode. */
60+
async isMultiple(): Promise<boolean> {
61+
return (await this.host()).hasClass('mat-mdc-select-multiple');
62+
}
63+
64+
/** Gets a promise for the select's value text. */
65+
async getValueText(): Promise<string> {
66+
return (await this._value()).text();
67+
}
68+
69+
/** Focuses the select and returns a void promise that indicates when the action is complete. */
70+
async focus(): Promise<void> {
71+
return (await this.host()).focus();
72+
}
73+
74+
/** Blurs the select and returns a void promise that indicates when the action is complete. */
75+
async blur(): Promise<void> {
76+
return (await this.host()).blur();
77+
}
78+
79+
/** Whether the select is focused. */
80+
async isFocused(): Promise<boolean> {
81+
return (await this.host()).isFocused();
82+
}
83+
84+
/** Gets the options inside the select panel. */
85+
async getOptions(filter: Omit<OptionHarnessFilters, 'ancestor'> = {}):
86+
Promise<MatOptionHarness[]> {
87+
return this._documentRootLocator.locatorForAll(MatOptionHarness.with({
88+
...filter,
89+
ancestor: await this._getPanelSelector()
90+
}))();
91+
}
92+
93+
/** Gets the groups of options inside the panel. */
94+
async getOptionGroups(filter: Omit<OptgroupHarnessFilters, 'ancestor'> = {}):
95+
Promise<MatOptgroupHarness[]> {
96+
return this._documentRootLocator.locatorForAll(MatOptgroupHarness.with({
97+
...filter,
98+
ancestor: await this._getPanelSelector()
99+
}))();
100+
}
101+
102+
/** Gets whether the select is open. */
103+
async isOpen(): Promise<boolean> {
104+
return !!await this._documentRootLocator.locatorForOptional(await this._getPanelSelector())();
105+
}
106+
107+
/** Opens the select's panel. */
108+
async open(): Promise<void> {
109+
if (!await this.isOpen()) {
110+
return (await this._trigger()).click();
111+
}
112+
}
113+
114+
/**
115+
* Clicks the options that match the passed-in filter. If the select is in multi-selection
116+
* mode all options will be clicked, otherwise the harness will pick the first matching option.
117+
*/
118+
async clickOptions(filter: OptionHarnessFilters = {}): Promise<void> {
119+
await this.open();
120+
121+
const [isMultiple, options] = await Promise.all([this.isMultiple(), this.getOptions(filter)]);
122+
123+
if (options.length === 0) {
124+
throw Error('Select does not have options matching the specified filter');
125+
}
126+
127+
if (isMultiple) {
128+
await Promise.all(options.map(option => option.click()));
129+
} else {
130+
await options[0].click();
131+
}
132+
}
133+
134+
/** Closes the select's panel. */
135+
async close(): Promise<void> {
136+
if (await this.isOpen()) {
137+
// This is the most consistent way that works both in both single and multi-select modes,
138+
// but it assumes that only one overlay is open at a time. We should be able to make it
139+
// a bit more precise after #16645 where we can dispatch an ESCAPE press to the host instead.
140+
return (await this._backdrop()).click();
141+
}
142+
}
143+
144+
/** Gets the selector that should be used to find this select's panel. */
145+
private async _getPanelSelector(): Promise<string> {
146+
const id = await (await this.host()).getAttribute('id');
147+
return `#${id}-panel`;
148+
}
149+
}

src/material/select/testing/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ ng_test_library(
4646
deps = [
4747
":harness_tests_lib",
4848
":testing",
49+
"//src/material/form-field",
4950
"//src/material/select",
5051
],
5152
)
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import {MatFormFieldModule} from '@angular/material/form-field';
12
import {MatSelectModule} from '@angular/material/select';
23
import {MatSelectHarness} from './select-harness';
34
import {runHarnessTests} from './shared.spec';
45

56
describe('Non-MDC-based MatSelectHarness', () => {
6-
runHarnessTests(MatSelectModule, MatSelectHarness);
7+
runHarnessTests(MatFormFieldModule, MatSelectModule, MatSelectHarness);
78
});

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import {MatSelectHarness} from './select-harness';
1111

1212
/** Shared tests to run on both the original and MDC-based select. */
1313
export function runHarnessTests(
14-
selectModule: typeof MatSelectModule, selectHarness: typeof MatSelectHarness) {
14+
formFieldModule: typeof MatFormFieldModule,
15+
selectModule: typeof MatSelectModule,
16+
selectHarness: typeof MatSelectHarness) {
1517
let fixture: ComponentFixture<SelectHarnessTest>;
1618
let loader: HarnessLoader;
1719
let overlayContainer: OverlayContainer;
@@ -20,7 +22,7 @@ export function runHarnessTests(
2022
await TestBed.configureTestingModule({
2123
imports: [
2224
selectModule,
23-
MatFormFieldModule,
25+
formFieldModule,
2426
NoopAnimationsModule,
2527
ReactiveFormsModule,
2628
],

0 commit comments

Comments
 (0)