Skip to content

feat(material-experimental/mdc-tabs): add test harnesses #20322

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
Aug 18, 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 @@ -34,6 +34,7 @@ entryPoints = [
"mdc-snack-bar",
"mdc-table",
"mdc-tabs",
"mdc-tabs/testing",
"menubar",
"popover-edit",
"selection",
Expand Down
40 changes: 40 additions & 0 deletions src/material-experimental/mdc-tabs/testing/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -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",
],
)
9 changes: 9 additions & 0 deletions src/material-experimental/mdc-tabs/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';
11 changes: 11 additions & 0 deletions src/material-experimental/mdc-tabs/testing/public-api.ts
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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);
});
64 changes: 64 additions & 0 deletions src/material-experimental/mdc-tabs/testing/tab-group-harness.ts
Original file line number Diff line number Diff line change
@@ -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<MatTabGroupHarness> {
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<MatTabHarness[]> {
return this.locatorForAll(MatTabHarness.with(filter))();
}

/** Gets the selected tab of the tab group. */
async getSelectedTab(): Promise<MatTabHarness> {
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<void> {
const tabs = await this.getTabs(filter);
if (!tabs.length) {
throw Error(`Cannot find mat-tab matching filter ${JSON.stringify(filter)}`);
}
await tabs[0].select();
}
}
20 changes: 20 additions & 0 deletions src/material-experimental/mdc-tabs/testing/tab-harness-filters.ts
Original file line number Diff line number Diff line change
@@ -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;
}
83 changes: 83 additions & 0 deletions src/material-experimental/mdc-tabs/testing/tab-harness.ts
Original file line number Diff line number Diff line change
@@ -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<MatTabHarness> {
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<string> {
return (await this.host()).text();
}

/** Gets the aria-label of the tab. */
async getAriaLabel(): Promise<string|null> {
return (await this.host()).getAttribute('aria-label');
}

/** Gets the value of the "aria-labelledby" attribute. */
async getAriaLabelledby(): Promise<string|null> {
return (await this.host()).getAttribute('aria-labelledby');
}

/** Whether the tab is selected. */
async isSelected(): Promise<boolean> {
const hostEl = await this.host();
return (await hostEl.getAttribute('aria-selected')) === 'true';
}

/** Whether the tab is disabled. */
async isDisabled(): Promise<boolean> {
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<void> {
await (await this.host()).click();
}

/** Gets the text content of the tab. */
async getTextContent(): Promise<string> {
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<HarnessLoader> {
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<string> {
const hostEl = await this.host();
// Tabs never have an empty "aria-controls" attribute.
return (await hostEl.getAttribute('aria-controls'))!;
}
}