Skip to content

Commit 6695eca

Browse files
committed
feat(material-experimental): add test harness for tabs
Resolves COMP-193.
1 parent e6afdbb commit 6695eca

File tree

5 files changed

+309
-0
lines changed

5 files changed

+309
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package(default_visibility = ["//visibility:public"])
2+
3+
load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
4+
5+
ts_library(
6+
name = "harness",
7+
srcs = glob(
8+
["**/*.ts"],
9+
exclude = ["**/*.spec.ts"],
10+
),
11+
deps = [
12+
"//src/cdk-experimental/testing",
13+
"//src/cdk/coercion",
14+
],
15+
)
16+
17+
ng_test_library(
18+
name = "harness_tests",
19+
srcs = glob(["**/*.spec.ts"]),
20+
deps = [
21+
":harness",
22+
"//src/cdk-experimental/testing",
23+
"//src/cdk-experimental/testing/testbed",
24+
"//src/material/tabs",
25+
"@npm//@angular/platform-browser",
26+
],
27+
)
28+
29+
ng_web_test_suite(
30+
name = "tests",
31+
deps = [":harness_tests"],
32+
)
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 type TabGroupHarnessFilters = {};
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import {HarnessLoader} from '@angular/cdk-experimental/testing';
2+
import {TestbedHarnessEnvironment} from '@angular/cdk-experimental/testing/testbed';
3+
import {Component} from '@angular/core';
4+
import {ComponentFixture, TestBed} from '@angular/core/testing';
5+
import {MatTabsModule} from '@angular/material/tabs';
6+
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
7+
import {MatTabGroupHarness} from './tab-group-harness';
8+
9+
let fixture: ComponentFixture<TabGroupHarnessTest>;
10+
let loader: HarnessLoader;
11+
let tabGroupHarness: typeof MatTabGroupHarness;
12+
13+
describe('MatTabGroupHarness', () => {
14+
describe('non-MDC-based', () => {
15+
beforeEach(async () => {
16+
await TestBed
17+
.configureTestingModule({
18+
imports: [MatTabsModule, NoopAnimationsModule],
19+
declarations: [TabGroupHarnessTest],
20+
})
21+
.compileComponents();
22+
23+
fixture = TestBed.createComponent(TabGroupHarnessTest);
24+
fixture.detectChanges();
25+
loader = TestbedHarnessEnvironment.loader(fixture);
26+
tabGroupHarness = MatTabGroupHarness;
27+
});
28+
29+
runTests();
30+
});
31+
32+
describe(
33+
'MDC-based',
34+
() => {
35+
// TODO: run tests for MDC based tab-group once implemented.
36+
});
37+
});
38+
39+
/** Shared tests to run on both the original and MDC-based tab-group's. */
40+
function runTests() {
41+
it('should load harness for tab-group', async () => {
42+
const tabGroups = await loader.getAllHarnesses(tabGroupHarness);
43+
expect(tabGroups.length).toBe(1);
44+
});
45+
46+
it('should be able to get tabs of tab-group', async () => {
47+
const tabGroup = await loader.getHarness(tabGroupHarness);
48+
const tabs = await tabGroup.getTabs();
49+
expect(tabs.length).toBe(3);
50+
});
51+
52+
it('should be able to get label of tabs', async () => {
53+
const tabGroup = await loader.getHarness(tabGroupHarness);
54+
const tabs = await tabGroup.getTabs();
55+
expect(await tabs[0].getLabel()).toBe('First');
56+
expect(await tabs[1].getLabel()).toBe('Second');
57+
expect(await tabs[2].getLabel()).toBe('Third');
58+
});
59+
60+
it('should be able to get aria-label of tabs', async () => {
61+
const tabGroup = await loader.getHarness(tabGroupHarness);
62+
const tabs = await tabGroup.getTabs();
63+
expect(await tabs[0].getAriaLabel()).toBe('First tab');
64+
expect(await tabs[1].getAriaLabel()).toBe('Second tab');
65+
expect(await tabs[2].getAriaLabel()).toBe(null);
66+
});
67+
68+
it('should be able to get aria-labelledby of tabs', async () => {
69+
const tabGroup = await loader.getHarness(tabGroupHarness);
70+
const tabs = await tabGroup.getTabs();
71+
expect(await tabs[0].getAriaLabelledby()).toBe(null);
72+
expect(await tabs[1].getAriaLabelledby()).toBe(null);
73+
expect(await tabs[2].getAriaLabelledby()).toBe('tabLabelId');
74+
});
75+
76+
it('should be able to get content element of active tab', async () => {
77+
const tabGroup = await loader.getHarness(tabGroupHarness);
78+
const tabs = await tabGroup.getTabs();
79+
expect(await (await tabs[0].getContentElement()).text()).toBe('Content 1');
80+
});
81+
82+
it('should be able to get content element of active tab', async () => {
83+
const tabGroup = await loader.getHarness(tabGroupHarness);
84+
const tabs = await tabGroup.getTabs();
85+
expect(await (await tabs[0].getContentElement()).text()).toBe('Content 1');
86+
});
87+
88+
it('should be able to get disabled state of tab', async () => {
89+
const tabGroup = await loader.getHarness(tabGroupHarness);
90+
const tabs = await tabGroup.getTabs();
91+
expect(await tabs[0].isDisabled()).toBe(false);
92+
expect(await tabs[1].isDisabled()).toBe(false);
93+
expect(await tabs[2].isDisabled()).toBe(false);
94+
95+
fixture.componentInstance.isDisabled = true;
96+
fixture.detectChanges();
97+
98+
expect(await tabs[0].isDisabled()).toBe(false);
99+
expect(await tabs[1].isDisabled()).toBe(false);
100+
expect(await tabs[2].isDisabled()).toBe(true);
101+
});
102+
103+
it('should be able to select specific tab', async () => {
104+
const tabGroup = await loader.getHarness(tabGroupHarness);
105+
const tabs = await tabGroup.getTabs();
106+
expect(await tabs[0].isSelected()).toBe(true);
107+
expect(await tabs[1].isSelected()).toBe(false);
108+
expect(await tabs[2].isSelected()).toBe(false);
109+
110+
await tabs[1].select();
111+
expect(await tabs[0].isSelected()).toBe(false);
112+
expect(await tabs[1].isSelected()).toBe(true);
113+
expect(await tabs[2].isSelected()).toBe(false);
114+
115+
// Should not be able to select third tab if disabled.
116+
fixture.componentInstance.isDisabled = true;
117+
fixture.detectChanges();
118+
119+
await tabs[2].select();
120+
expect(await tabs[0].isSelected()).toBe(false);
121+
expect(await tabs[1].isSelected()).toBe(true);
122+
expect(await tabs[2].isSelected()).toBe(false);
123+
124+
// Should be able to select third tab if not disabled.
125+
fixture.componentInstance.isDisabled = false;
126+
fixture.detectChanges();
127+
await tabs[2].select();
128+
expect(await tabs[0].isSelected()).toBe(false);
129+
expect(await tabs[1].isSelected()).toBe(false);
130+
expect(await tabs[2].isSelected()).toBe(true);
131+
});
132+
}
133+
134+
@Component({
135+
template: `
136+
<mat-tab-group>
137+
<mat-tab label="First" aria-label="First tab">Content 1</mat-tab>
138+
<mat-tab label="Second" aria-label="Second tab">Content 2</mat-tab>
139+
<mat-tab label="Third" aria-labelledby="tabLabelId" [disabled]="isDisabled">
140+
<ng-template matTabLabel>Third</ng-template>
141+
Content 3
142+
</mat-tab>
143+
</mat-tab-group>
144+
`
145+
})
146+
class TabGroupHarnessTest {
147+
isDisabled = false;
148+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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 {ComponentHarness, HarnessPredicate} from '@angular/cdk-experimental/testing';
10+
import {TabGroupHarnessFilters} from './tab-group-harness-filters';
11+
import {MatTabHarness} from './tab-harness';
12+
13+
/**
14+
* Harness for interacting with a standard mat-tab-group in tests.
15+
* @dynamic
16+
*/
17+
export class MatTabGroupHarness extends ComponentHarness {
18+
static hostSelector = '.mat-tab-group';
19+
20+
/**
21+
* Gets a `HarnessPredicate` that can be used to search for a radio-button with
22+
* specific attributes.
23+
* @param options Options for narrowing the search
24+
* @return a `HarnessPredicate` configured with the given options.
25+
*/
26+
static with(options: TabGroupHarnessFilters = {}): HarnessPredicate<MatTabGroupHarness> {
27+
return new HarnessPredicate(MatTabGroupHarness);
28+
}
29+
30+
private _tabs = this.locatorForAll(MatTabHarness);
31+
32+
/** Gets all tabs of the tab group. */
33+
async getTabs(): Promise<MatTabHarness[]> {
34+
return this._tabs();
35+
}
36+
37+
/** Gets the selected tab of the tab group. */
38+
async getSelectedTab(): Promise<MatTabHarness> {
39+
const tabs = await this.getTabs();
40+
const isSelected = await Promise.all(tabs.map(t => t.isSelected()));
41+
for (let i = 0; i < tabs.length; i++) {
42+
if (isSelected[i]) {
43+
return tabs[i];
44+
}
45+
}
46+
throw new Error('No selected tab could be found.');
47+
}
48+
}
49+
50+
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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 {ComponentHarness, TestElement} from '@angular/cdk-experimental/testing';
10+
11+
/**
12+
* Harness for interacting with a standard Angular Material tab-label in tests.
13+
* @dynamic
14+
*/
15+
export class MatTabHarness extends ComponentHarness {
16+
static hostSelector = '.mat-tab-label';
17+
18+
private _rootLocatorFactory = this.documentRootLocatorFactory();
19+
20+
/** Gets the label of the tab. */
21+
async getLabel(): Promise<string> {
22+
return (await this.host()).text();
23+
}
24+
25+
/** Gets the aria label of the tab. */
26+
async getAriaLabel(): Promise<string|null> {
27+
return (await this.host()).getAttribute('aria-label');
28+
}
29+
30+
/** Gets the value of the "aria-labelledby" attribute. */
31+
async getAriaLabelledby(): Promise<string|null> {
32+
return (await this.host()).getAttribute('aria-labelledby');
33+
}
34+
35+
/**
36+
* Gets the content element of the given tab. Note that the element will be empty
37+
* until the tab is selected. This is an implementation detail of the tab-group
38+
* in order to avoid rendering of non-active tabs.
39+
*/
40+
async getContentElement(): Promise<TestElement> {
41+
return this._rootLocatorFactory.locatorFor(`#${await this._getContentId()}`)();
42+
}
43+
44+
/** Whether the tab is selected. */
45+
async isSelected(): Promise<boolean> {
46+
const hostEl = await this.host();
47+
return (await hostEl.getAttribute('aria-selected')) === 'true';
48+
}
49+
50+
/** Whether the tab is disabled. */
51+
async isDisabled(): Promise<boolean> {
52+
const hostEl = await this.host();
53+
return (await hostEl.getAttribute('aria-disabled')) === 'true';
54+
}
55+
56+
/**
57+
* Selects the given tab by clicking on the label. Tab cannot be
58+
* selected if disabled.
59+
*/
60+
async select(): Promise<void> {
61+
await (await this.host()).click();
62+
}
63+
64+
/** Gets the element id for the content of the current tab. */
65+
private async _getContentId(): Promise<string> {
66+
const hostEl = await this.host();
67+
// Tabs never have an empty "aria-controls" attribute.
68+
return (await hostEl.getAttribute('aria-controls'))!;
69+
}
70+
}

0 commit comments

Comments
 (0)