Skip to content

feat(material-experimental): add test harness for tabs #16728

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 16, 2019
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
32 changes: 32 additions & 0 deletions src/material-experimental/mdc-tabs/harness/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package(default_visibility = ["//visibility:public"])

load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")

ts_library(
name = "harness",
srcs = glob(
["**/*.ts"],
exclude = ["**/*.spec.ts"],
),
deps = [
"//src/cdk-experimental/testing",
"//src/cdk/coercion",
],
)

ng_test_library(
name = "harness_tests",
srcs = glob(["**/*.spec.ts"]),
deps = [
":harness",
"//src/cdk-experimental/testing",
"//src/cdk-experimental/testing/testbed",
"//src/material/tabs",
"@npm//@angular/platform-browser",
],
)

ng_web_test_suite(
name = "tests",
deps = [":harness_tests"],
)
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 type TabGroupHarnessFilters = {};
148 changes: 148 additions & 0 deletions src/material-experimental/mdc-tabs/harness/tab-group-harness.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import {HarnessLoader} from '@angular/cdk-experimental/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk-experimental/testing/testbed';
import {Component} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {MatTabsModule} from '@angular/material/tabs';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {MatTabGroupHarness} from './tab-group-harness';

let fixture: ComponentFixture<TabGroupHarnessTest>;
let loader: HarnessLoader;
let tabGroupHarness: typeof MatTabGroupHarness;

describe('MatTabGroupHarness', () => {
describe('non-MDC-based', () => {
beforeEach(async () => {
await TestBed
.configureTestingModule({
imports: [MatTabsModule, NoopAnimationsModule],
declarations: [TabGroupHarnessTest],
})
.compileComponents();

fixture = TestBed.createComponent(TabGroupHarnessTest);
fixture.detectChanges();
loader = TestbedHarnessEnvironment.loader(fixture);
tabGroupHarness = MatTabGroupHarness;
});

runTests();
});

describe(
'MDC-based',
() => {
// TODO: run tests for MDC based tab-group once implemented.
});
});

/** Shared tests to run on both the original and MDC-based tab-group's. */
function runTests() {
it('should load harness for tab-group', async () => {
const tabGroups = await loader.getAllHarnesses(tabGroupHarness);
expect(tabGroups.length).toBe(1);
});

it('should be able to get tabs of tab-group', async () => {
const tabGroup = await loader.getHarness(tabGroupHarness);
const tabs = await tabGroup.getTabs();
expect(tabs.length).toBe(3);
});

it('should be able to get label of tabs', async () => {
const tabGroup = await loader.getHarness(tabGroupHarness);
const tabs = await tabGroup.getTabs();
expect(await tabs[0].getLabel()).toBe('First');
expect(await tabs[1].getLabel()).toBe('Second');
expect(await tabs[2].getLabel()).toBe('Third');
});

it('should be able to get aria-label of tabs', async () => {
const tabGroup = await loader.getHarness(tabGroupHarness);
const tabs = await tabGroup.getTabs();
expect(await tabs[0].getAriaLabel()).toBe('First tab');
expect(await tabs[1].getAriaLabel()).toBe('Second tab');
expect(await tabs[2].getAriaLabel()).toBe(null);
});

it('should be able to get aria-labelledby of tabs', async () => {
const tabGroup = await loader.getHarness(tabGroupHarness);
const tabs = await tabGroup.getTabs();
expect(await tabs[0].getAriaLabelledby()).toBe(null);
expect(await tabs[1].getAriaLabelledby()).toBe(null);
expect(await tabs[2].getAriaLabelledby()).toBe('tabLabelId');
});

it('should be able to get content element of active tab', async () => {
const tabGroup = await loader.getHarness(tabGroupHarness);
const tabs = await tabGroup.getTabs();
expect(await (await tabs[0].getContentElement()).text()).toBe('Content 1');
});

it('should be able to get content element of active tab', async () => {
const tabGroup = await loader.getHarness(tabGroupHarness);
const tabs = await tabGroup.getTabs();
expect(await (await tabs[0].getContentElement()).text()).toBe('Content 1');
});

it('should be able to get disabled state of tab', async () => {
const tabGroup = await loader.getHarness(tabGroupHarness);
const tabs = await tabGroup.getTabs();
expect(await tabs[0].isDisabled()).toBe(false);
expect(await tabs[1].isDisabled()).toBe(false);
expect(await tabs[2].isDisabled()).toBe(false);

fixture.componentInstance.isDisabled = true;
fixture.detectChanges();

expect(await tabs[0].isDisabled()).toBe(false);
expect(await tabs[1].isDisabled()).toBe(false);
expect(await tabs[2].isDisabled()).toBe(true);
});

it('should be able to select specific tab', async () => {
const tabGroup = await loader.getHarness(tabGroupHarness);
const tabs = await tabGroup.getTabs();
expect(await tabs[0].isSelected()).toBe(true);
expect(await tabs[1].isSelected()).toBe(false);
expect(await tabs[2].isSelected()).toBe(false);

await tabs[1].select();
expect(await tabs[0].isSelected()).toBe(false);
expect(await tabs[1].isSelected()).toBe(true);
expect(await tabs[2].isSelected()).toBe(false);

// Should not be able to select third tab if disabled.
fixture.componentInstance.isDisabled = true;
fixture.detectChanges();

await tabs[2].select();
expect(await tabs[0].isSelected()).toBe(false);
expect(await tabs[1].isSelected()).toBe(true);
expect(await tabs[2].isSelected()).toBe(false);

// Should be able to select third tab if not disabled.
fixture.componentInstance.isDisabled = false;
fixture.detectChanges();
await tabs[2].select();
expect(await tabs[0].isSelected()).toBe(false);
expect(await tabs[1].isSelected()).toBe(false);
expect(await tabs[2].isSelected()).toBe(true);
});
}

@Component({
template: `
<mat-tab-group>
<mat-tab label="First" aria-label="First tab">Content 1</mat-tab>
<mat-tab label="Second" aria-label="Second tab">Content 2</mat-tab>
<mat-tab label="Third" aria-labelledby="tabLabelId" [disabled]="isDisabled">
<ng-template matTabLabel>Third</ng-template>
Content 3
</mat-tab>
</mat-tab-group>
`
})
class TabGroupHarnessTest {
isDisabled = false;
}
50 changes: 50 additions & 0 deletions src/material-experimental/mdc-tabs/harness/tab-group-harness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* @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-experimental/testing';
import {TabGroupHarnessFilters} from './tab-group-harness-filters';
import {MatTabHarness} from './tab-harness';

/**
* Harness for interacting with a standard mat-tab-group in tests.
* @dynamic
*/
export class MatTabGroupHarness extends ComponentHarness {
static hostSelector = '.mat-tab-group';

/**
* Gets a `HarnessPredicate` that can be used to search for a radio-button with
* specific attributes.
* @param options Options for narrowing the search
* @return a `HarnessPredicate` configured with the given options.
*/
static with(options: TabGroupHarnessFilters = {}): HarnessPredicate<MatTabGroupHarness> {
return new HarnessPredicate(MatTabGroupHarness);
}

private _tabs = this.locatorForAll(MatTabHarness);

/** Gets all tabs of the tab group. */
async getTabs(): Promise<MatTabHarness[]> {
return this._tabs();
}

/** 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.');
}
}


70 changes: 70 additions & 0 deletions src/material-experimental/mdc-tabs/harness/tab-harness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* @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, TestElement} from '@angular/cdk-experimental/testing';

/**
* Harness for interacting with a standard Angular Material tab-label in tests.
* @dynamic
*/
export class MatTabHarness extends ComponentHarness {
static hostSelector = '.mat-tab-label';

private _rootLocatorFactory = this.documentRootLocatorFactory();

/** 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');
}

/**
* Gets the content element of the given tab. Note that the element will be empty
* until the tab is selected. This is an implementation detail of the tab-group
* in order to avoid rendering of non-active tabs.
*/
async getContentElement(): Promise<TestElement> {
return this._rootLocatorFactory.locatorFor(`#${await this._getContentId()}`)();
}

/** 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 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'))!;
}
}