Skip to content

feat(material-experimental/mdc-select): add test harness #20603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/material-experimental/config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ entryPoints = [
"mdc-radio",
"mdc-radio/testing",
"mdc-select",
"mdc-select/testing",
"mdc-sidenav",
"mdc-slide-toggle",
"mdc-slide-toggle/testing",
Expand Down
48 changes: 48 additions & 0 deletions src/material-experimental/mdc-select/testing/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")

package(default_visibility = ["//visibility:public"])

ts_library(
name = "testing",
srcs = glob(
["**/*.ts"],
exclude = ["**/*.spec.ts"],
),
module_name = "@angular/material-experimental/mdc-select/testing",
deps = [
"//src/cdk/coercion",
"//src/cdk/testing",
"//src/material-experimental/mdc-core/testing",
"//src/material/form-field/testing/control",
],
)

filegroup(
name = "source-files",
srcs = glob(["**/*.ts"]),
)

ng_test_library(
name = "unit_tests_lib",
srcs = glob(["**/*.spec.ts"]),
deps = [
":testing",
"//src/material-experimental/mdc-form-field",
"//src/material-experimental/mdc-select",
"//src/material/select/testing:harness_tests_lib",
],
)

ng_web_test_suite(
name = "unit_tests",
static_files = [
"@npm//:node_modules/@material/textfield/dist/mdc.textfield.js",
"@npm//:node_modules/@material/line-ripple/dist/mdc.lineRipple.js",
"@npm//:node_modules/@material/notched-outline/dist/mdc.notchedOutline.js",
"@npm//:node_modules/@material/dom/dist/mdc.dom.js",
],
deps = [
":unit_tests_lib",
"//src/material-experimental:mdc_require_config.js",
],
)
9 changes: 9 additions & 0 deletions src/material-experimental/mdc-select/testing/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

export * from './public-api';
10 changes: 10 additions & 0 deletions src/material-experimental/mdc-select/testing/public-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

export * from './select-harness';
export * from './select-harness-filters';
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {BaseHarnessFilters} from '@angular/cdk/testing';

/** A set of criteria that can be used to filter a list of `MatSelectHarness` instances. */
export interface SelectHarnessFilters extends BaseHarnessFilters {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {MatSelectModule} from '@angular/material-experimental/mdc-select';
import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field';
import {runHarnessTests} from '@angular/material/select/testing/shared.spec';
import {MatSelectHarness} from './index';

describe('MDC-based MatSelectHarness', () => {
runHarnessTests(MatFormFieldModule, MatSelectModule, MatSelectHarness as any);
});
149 changes: 149 additions & 0 deletions src/material-experimental/mdc-select/testing/select-harness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {HarnessPredicate} from '@angular/cdk/testing';
import {MatFormFieldControlHarness} from '@angular/material/form-field/testing/control';
import {
MatOptionHarness,
MatOptgroupHarness,
OptionHarnessFilters,
OptgroupHarnessFilters,
} from '@angular/material-experimental/mdc-core/testing';
import {SelectHarnessFilters} from './select-harness-filters';


/** Harness for interacting with an MDC-based mat-select in tests. */
export class MatSelectHarness extends MatFormFieldControlHarness {
private _documentRootLocator = this.documentRootLocatorFactory();
private _backdrop = this._documentRootLocator.locatorFor('.cdk-overlay-backdrop');
private _trigger = this.locatorFor('.mat-mdc-select-trigger');
private _value = this.locatorFor('.mat-mdc-select-value');

static hostSelector = '.mat-mdc-select';

/**
* Gets a `HarnessPredicate` that can be used to search for a `MatSelectHarness` that meets
* certain criteria.
* @param options Options for filtering which select instances are considered a match.
* @return a `HarnessPredicate` configured with the given options.
*/
static with(options: SelectHarnessFilters = {}): HarnessPredicate<MatSelectHarness> {
return new HarnessPredicate(MatSelectHarness, options);
}

/** Gets a boolean promise indicating if the select is disabled. */
async isDisabled(): Promise<boolean> {
return (await this.host()).hasClass('mat-mdc-select-disabled');
}

/** Gets a boolean promise indicating if the select is valid. */
async isValid(): Promise<boolean> {
return !(await (await this.host()).hasClass('ng-invalid'));
}

/** Gets a boolean promise indicating if the select is required. */
async isRequired(): Promise<boolean> {
return (await this.host()).hasClass('mat-mdc-select-required');
}

/** Gets a boolean promise indicating if the select is empty (no value is selected). */
async isEmpty(): Promise<boolean> {
return (await this.host()).hasClass('mat-mdc-select-empty');
}

/** Gets a boolean promise indicating if the select is in multi-selection mode. */
async isMultiple(): Promise<boolean> {
return (await this.host()).hasClass('mat-mdc-select-multiple');
}

/** Gets a promise for the select's value text. */
async getValueText(): Promise<string> {
return (await this._value()).text();
}

/** Focuses the select and returns a void promise that indicates when the action is complete. */
async focus(): Promise<void> {
return (await this.host()).focus();
}

/** Blurs the select and returns a void promise that indicates when the action is complete. */
async blur(): Promise<void> {
return (await this.host()).blur();
}

/** Whether the select is focused. */
async isFocused(): Promise<boolean> {
return (await this.host()).isFocused();
}

/** Gets the options inside the select panel. */
async getOptions(filter: Omit<OptionHarnessFilters, 'ancestor'> = {}):
Promise<MatOptionHarness[]> {
return this._documentRootLocator.locatorForAll(MatOptionHarness.with({
...filter,
ancestor: await this._getPanelSelector()
}))();
}

/** Gets the groups of options inside the panel. */
async getOptionGroups(filter: Omit<OptgroupHarnessFilters, 'ancestor'> = {}):
Promise<MatOptgroupHarness[]> {
return this._documentRootLocator.locatorForAll(MatOptgroupHarness.with({
...filter,
ancestor: await this._getPanelSelector()
}))();
}

/** Gets whether the select is open. */
async isOpen(): Promise<boolean> {
return !!await this._documentRootLocator.locatorForOptional(await this._getPanelSelector())();
}

/** Opens the select's panel. */
async open(): Promise<void> {
if (!await this.isOpen()) {
return (await this._trigger()).click();
}
}

/**
* Clicks the options that match the passed-in filter. If the select is in multi-selection
* mode all options will be clicked, otherwise the harness will pick the first matching option.
*/
async clickOptions(filter: OptionHarnessFilters = {}): Promise<void> {
await this.open();

const [isMultiple, options] = await Promise.all([this.isMultiple(), this.getOptions(filter)]);

if (options.length === 0) {
throw Error('Select does not have options matching the specified filter');
}

if (isMultiple) {
await Promise.all(options.map(option => option.click()));
} else {
await options[0].click();
}
}

/** Closes the select's panel. */
async close(): Promise<void> {
if (await this.isOpen()) {
// This is the most consistent way that works both in both single and multi-select modes,
// but it assumes that only one overlay is open at a time. We should be able to make it
// a bit more precise after #16645 where we can dispatch an ESCAPE press to the host instead.
return (await this._backdrop()).click();
}
}

/** Gets the selector that should be used to find this select's panel. */
private async _getPanelSelector(): Promise<string> {
const id = await (await this.host()).getAttribute('id');
return `#${id}-panel`;
}
}
1 change: 1 addition & 0 deletions src/material/select/testing/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ ng_test_library(
deps = [
":harness_tests_lib",
":testing",
"//src/material/form-field",
"//src/material/select",
],
)
Expand Down
3 changes: 2 additions & 1 deletion src/material/select/testing/select-harness.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatSelectModule} from '@angular/material/select';
import {MatSelectHarness} from './select-harness';
import {runHarnessTests} from './shared.spec';

describe('Non-MDC-based MatSelectHarness', () => {
runHarnessTests(MatSelectModule, MatSelectHarness);
runHarnessTests(MatFormFieldModule, MatSelectModule, MatSelectHarness);
});
6 changes: 4 additions & 2 deletions src/material/select/testing/shared.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import {MatSelectHarness} from './select-harness';

/** Shared tests to run on both the original and MDC-based select. */
export function runHarnessTests(
selectModule: typeof MatSelectModule, selectHarness: typeof MatSelectHarness) {
formFieldModule: typeof MatFormFieldModule,
selectModule: typeof MatSelectModule,
selectHarness: typeof MatSelectHarness) {
let fixture: ComponentFixture<SelectHarnessTest>;
let loader: HarnessLoader;
let overlayContainer: OverlayContainer;
Expand All @@ -20,7 +22,7 @@ export function runHarnessTests(
await TestBed.configureTestingModule({
imports: [
selectModule,
MatFormFieldModule,
formFieldModule,
NoopAnimationsModule,
ReactiveFormsModule,
],
Expand Down