From cbb1ffd3753f79cc4dba2ee27537e0c4bd1f05d3 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Sat, 15 Aug 2020 00:03:36 +0200 Subject: [PATCH] feat(material-experimental/mdc-tabs): add test harnesses Adds test harnesses for the MDC-based tabs module. --- src/material-experimental/config.bzl | 1 + .../mdc-tabs/testing/BUILD.bazel | 40 +++++++++ .../mdc-tabs/testing/index.ts | 9 ++ .../mdc-tabs/testing/public-api.ts | 11 +++ .../testing/tab-group-harness.spec.ts | 7 ++ .../mdc-tabs/testing/tab-group-harness.ts | 64 ++++++++++++++ .../mdc-tabs/testing/tab-harness-filters.ts | 20 +++++ .../mdc-tabs/testing/tab-harness.ts | 83 +++++++++++++++++++ 8 files changed, 235 insertions(+) create mode 100644 src/material-experimental/mdc-tabs/testing/BUILD.bazel create mode 100644 src/material-experimental/mdc-tabs/testing/index.ts create mode 100644 src/material-experimental/mdc-tabs/testing/public-api.ts create mode 100644 src/material-experimental/mdc-tabs/testing/tab-group-harness.spec.ts create mode 100644 src/material-experimental/mdc-tabs/testing/tab-group-harness.ts create mode 100644 src/material-experimental/mdc-tabs/testing/tab-harness-filters.ts create mode 100644 src/material-experimental/mdc-tabs/testing/tab-harness.ts diff --git a/src/material-experimental/config.bzl b/src/material-experimental/config.bzl index 3e4010824d92..63d653b13578 100644 --- a/src/material-experimental/config.bzl +++ b/src/material-experimental/config.bzl @@ -34,6 +34,7 @@ entryPoints = [ "mdc-snack-bar", "mdc-table", "mdc-tabs", + "mdc-tabs/testing", "menubar", "popover-edit", "selection", diff --git a/src/material-experimental/mdc-tabs/testing/BUILD.bazel b/src/material-experimental/mdc-tabs/testing/BUILD.bazel new file mode 100644 index 000000000000..816e3845b7b4 --- /dev/null +++ b/src/material-experimental/mdc-tabs/testing/BUILD.bazel @@ -0,0 +1,40 @@ +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-tabs/testing", + deps = [ + "//src/cdk/coercion", + "//src/cdk/testing", + ], +) + +filegroup( + name = "source-files", + srcs = glob(["**/*.ts"]), +) + +ng_test_library( + name = "unit_tests_lib", + srcs = glob(["**/*.spec.ts"]), + deps = [ + ":testing", + "//src/material-experimental/mdc-tabs", + "//src/material/tabs/testing:harness_tests_lib", + ], +) + +ng_web_test_suite( + name = "unit_tests", + static_files = ["@npm//:node_modules/@material/tab-indicator/dist/mdc.tabIndicator.js"], + deps = [ + ":unit_tests_lib", + "//src/material-experimental:mdc_require_config.js", + ], +) diff --git a/src/material-experimental/mdc-tabs/testing/index.ts b/src/material-experimental/mdc-tabs/testing/index.ts new file mode 100644 index 000000000000..676ca90f1ffa --- /dev/null +++ b/src/material-experimental/mdc-tabs/testing/index.ts @@ -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'; diff --git a/src/material-experimental/mdc-tabs/testing/public-api.ts b/src/material-experimental/mdc-tabs/testing/public-api.ts new file mode 100644 index 000000000000..8a2c04257d49 --- /dev/null +++ b/src/material-experimental/mdc-tabs/testing/public-api.ts @@ -0,0 +1,11 @@ +/** + * @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 './tab-group-harness'; +export * from './tab-harness'; +export * from './tab-harness-filters'; diff --git a/src/material-experimental/mdc-tabs/testing/tab-group-harness.spec.ts b/src/material-experimental/mdc-tabs/testing/tab-group-harness.spec.ts new file mode 100644 index 000000000000..fe222062e321 --- /dev/null +++ b/src/material-experimental/mdc-tabs/testing/tab-group-harness.spec.ts @@ -0,0 +1,7 @@ +import {MatTabsModule} from '@angular/material-experimental/mdc-tabs'; +import {runHarnessTests} from '@angular/material/tabs/testing/shared.spec'; +import {MatTabGroupHarness} from './tab-group-harness'; + +describe('MDC-based MatTabGroupHarness', () => { + runHarnessTests(MatTabsModule, MatTabGroupHarness as any); +}); diff --git a/src/material-experimental/mdc-tabs/testing/tab-group-harness.ts b/src/material-experimental/mdc-tabs/testing/tab-group-harness.ts new file mode 100644 index 000000000000..c4f4ff86f02d --- /dev/null +++ b/src/material-experimental/mdc-tabs/testing/tab-group-harness.ts @@ -0,0 +1,64 @@ +/** + * @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 {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing'; +import {TabGroupHarnessFilters, TabHarnessFilters} from './tab-harness-filters'; +import {MatTabHarness} from './tab-harness'; + +/** Harness for interacting with an MDC-based mat-tab-group in tests. */ +export class MatTabGroupHarness extends ComponentHarness { + /** The selector for the host element of a `MatTabGroup` instance. */ + static hostSelector = '.mat-mdc-tab-group'; + + /** + * Gets a `HarnessPredicate` that can be used to search for a `MatTabGroupHarness` that meets + * certain criteria. + * @param options Options for filtering which tab group instances are considered a match. + * @return a `HarnessPredicate` configured with the given options. + */ + static with(options: TabGroupHarnessFilters = {}): HarnessPredicate { + return new HarnessPredicate(MatTabGroupHarness, options) + .addOption('selectedTabLabel', options.selectedTabLabel, async (harness, label) => { + const selectedTab = await harness.getSelectedTab(); + return HarnessPredicate.stringMatches(await selectedTab.getLabel(), label); + }); + } + + /** + * Gets the list of tabs in the tab group. + * @param filter Optionally filters which tabs are included. + */ + async getTabs(filter: TabHarnessFilters = {}): Promise { + return this.locatorForAll(MatTabHarness.with(filter))(); + } + + /** Gets the selected tab of the tab group. */ + async getSelectedTab(): Promise { + const tabs = await this.getTabs(); + const isSelected = await Promise.all(tabs.map(t => t.isSelected())); + for (let i = 0; i < tabs.length; i++) { + if (isSelected[i]) { + return tabs[i]; + } + } + throw new Error('No selected tab could be found.'); + } + + /** + * Selects a tab in this tab group. + * @param filter An optional filter to apply to the child tabs. The first tab matching the filter + * will be selected. + */ + async selectTab(filter: TabHarnessFilters = {}): Promise { + const tabs = await this.getTabs(filter); + if (!tabs.length) { + throw Error(`Cannot find mat-tab matching filter ${JSON.stringify(filter)}`); + } + await tabs[0].select(); + } +} diff --git a/src/material-experimental/mdc-tabs/testing/tab-harness-filters.ts b/src/material-experimental/mdc-tabs/testing/tab-harness-filters.ts new file mode 100644 index 000000000000..38773288c0f6 --- /dev/null +++ b/src/material-experimental/mdc-tabs/testing/tab-harness-filters.ts @@ -0,0 +1,20 @@ +/** + * @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 `MatRadioButtonHarness` instances. */ +export interface TabHarnessFilters extends BaseHarnessFilters { + /** Only find instances whose label matches the given value. */ + label?: string | RegExp; +} + +/** A set of criteria that can be used to filter a list of `MatRadioButtonHarness` instances. */ +export interface TabGroupHarnessFilters extends BaseHarnessFilters { + /** Only find instances whose selected tab label matches the given value. */ + selectedTabLabel?: string | RegExp; +} diff --git a/src/material-experimental/mdc-tabs/testing/tab-harness.ts b/src/material-experimental/mdc-tabs/testing/tab-harness.ts new file mode 100644 index 000000000000..d57ed8b96655 --- /dev/null +++ b/src/material-experimental/mdc-tabs/testing/tab-harness.ts @@ -0,0 +1,83 @@ +/** + * @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 {ComponentHarness, HarnessLoader, HarnessPredicate} from '@angular/cdk/testing'; +import {TabHarnessFilters} from './tab-harness-filters'; + +/** Harness for interacting with an MDC_based Angular Material tab in tests. */ +export class MatTabHarness extends ComponentHarness { + /** The selector for the host element of a `MatTab` instance. */ + static hostSelector = '.mat-mdc-tab'; + + /** + * Gets a `HarnessPredicate` that can be used to search for a `MatTabHarness` that meets + * certain criteria. + * @param options Options for filtering which tab instances are considered a match. + * @return a `HarnessPredicate` configured with the given options. + */ + static with(options: TabHarnessFilters = {}): HarnessPredicate { + return new HarnessPredicate(MatTabHarness, options) + .addOption('label', options.label, + (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label)); + } + + /** Gets the label of the tab. */ + async getLabel(): Promise { + return (await this.host()).text(); + } + + /** Gets the aria-label of the tab. */ + async getAriaLabel(): Promise { + return (await this.host()).getAttribute('aria-label'); + } + + /** Gets the value of the "aria-labelledby" attribute. */ + async getAriaLabelledby(): Promise { + return (await this.host()).getAttribute('aria-labelledby'); + } + + /** Whether the tab is selected. */ + async isSelected(): Promise { + const hostEl = await this.host(); + return (await hostEl.getAttribute('aria-selected')) === 'true'; + } + + /** Whether the tab is disabled. */ + async isDisabled(): Promise { + const hostEl = await this.host(); + return (await hostEl.getAttribute('aria-disabled')) === 'true'; + } + + /** Selects the given tab by clicking on the label. Tab cannot be selected if disabled. */ + async select(): Promise { + await (await this.host()).click(); + } + + /** Gets the text content of the tab. */ + async getTextContent(): Promise { + const contentId = await this._getContentId(); + const contentEl = await this.documentRootLocatorFactory().locatorFor(`#${contentId}`)(); + return contentEl.text(); + } + + /** + * Gets a `HarnessLoader` that can be used to load harnesses for components within the tab's + * content area. + */ + async getHarnessLoaderForContent(): Promise { + const contentId = await this._getContentId(); + return this.documentRootLocatorFactory().harnessLoaderFor(`#${contentId}`); + } + + /** Gets the element id for the content of the current tab. */ + private async _getContentId(): Promise { + const hostEl = await this.host(); + // Tabs never have an empty "aria-controls" attribute. + return (await hostEl.getAttribute('aria-controls'))!; + } +}