Skip to content

Commit a469c8b

Browse files
committed
feat(material-experimental): add test harness for button
1 parent 1c75040 commit a469c8b

File tree

6 files changed

+345
-3
lines changed

6 files changed

+345
-3
lines changed

src/material-experimental/mdc-button/BUILD.bazel

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package(default_visibility = ["//visibility:public"])
22

33
load("@io_bazel_rules_sass//:defs.bzl", "sass_binary", "sass_library")
4-
load("//tools:defaults.bzl", "ng_e2e_test_library", "ng_module")
4+
load("//tools:defaults.bzl", "ng_e2e_test_library", "ng_module", "ng_test_library", "ng_web_test_suite", "ts_library")
55
load("//src/e2e-app:test_suite.bzl", "e2e_test_suite")
66

77
ng_module(
88
name = "mdc-button",
99
srcs = glob(
1010
["**/*.ts"],
11-
exclude = ["**/*.spec.ts"],
11+
exclude = [
12+
"**/*.spec.ts",
13+
"harness/**",
14+
],
1215
),
1316
assets = [
1417
":button_scss",
@@ -22,6 +25,18 @@ ng_module(
2225
],
2326
)
2427

28+
ts_library(
29+
name = "harness",
30+
srcs = glob(
31+
["harness/**/*.ts"],
32+
exclude = ["**/*.spec.ts"],
33+
),
34+
deps = [
35+
"//src/cdk-experimental/testing",
36+
"//src/cdk/coercion",
37+
],
38+
)
39+
2540
sass_library(
2641
name = "mdc_button_scss_lib",
2742
srcs = glob(["**/_*.scss"]),
@@ -71,6 +86,30 @@ sass_binary(
7186
],
7287
)
7388

89+
ng_test_library(
90+
name = "button_tests_lib",
91+
srcs = [
92+
"harness/button-harness.spec.ts",
93+
],
94+
deps = [
95+
":harness",
96+
":mdc-button",
97+
"//src/cdk-experimental/testing",
98+
"//src/cdk-experimental/testing/testbed",
99+
"//src/cdk/testing",
100+
"//src/material/button",
101+
"@npm//@angular/platform-browser",
102+
],
103+
)
104+
105+
ng_web_test_suite(
106+
name = "unit_tests",
107+
deps = [
108+
":button_tests_lib",
109+
"//src/material-experimental:mdc_require_config.js",
110+
],
111+
)
112+
74113
ng_e2e_test_library(
75114
name = "e2e_test_sources",
76115
srcs = glob(["**/*.e2e.spec.ts"]),

src/material-experimental/mdc-button/button.spec.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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 ButtonHarnessFilters = {
10+
text?: string | RegExp
11+
};
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
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 {MatButtonModule} from '@angular/material/button';
6+
import {MatButtonModule as MatMdcButtonModule} from '../index';
7+
import {MatButtonHarness} from './button-harness';
8+
import {MatButtonHarness as MatMdcButtonHarness} from './mdc-button-harness';
9+
10+
let fixture: ComponentFixture<ButtonHarnessTest>;
11+
let loader: HarnessLoader;
12+
let buttonHarness: typeof MatButtonHarness;
13+
14+
describe('MatButtonHarness', () => {
15+
describe('non-MDC-based', () => {
16+
beforeEach(async () => {
17+
await TestBed.configureTestingModule({
18+
imports: [MatButtonModule],
19+
declarations: [ButtonHarnessTest],
20+
}).compileComponents();
21+
22+
fixture = TestBed.createComponent(ButtonHarnessTest);
23+
fixture.detectChanges();
24+
loader = TestbedHarnessEnvironment.loader(fixture);
25+
buttonHarness = MatButtonHarness;
26+
});
27+
28+
runTests();
29+
});
30+
31+
describe('MDC-based', () => {
32+
beforeEach(async () => {
33+
await TestBed.configureTestingModule({
34+
imports: [MatMdcButtonModule],
35+
declarations: [ButtonHarnessTest],
36+
}).compileComponents();
37+
38+
fixture = TestBed.createComponent(ButtonHarnessTest);
39+
fixture.detectChanges();
40+
loader = TestbedHarnessEnvironment.loader(fixture);
41+
// Public APIs are the same as MatButtonHarness, but cast is necessary because of different
42+
// private fields.
43+
buttonHarness = MatMdcButtonHarness as any;
44+
});
45+
46+
runTests();
47+
});
48+
});
49+
50+
/** Shared tests to run on both the original and MDC-based buttons. */
51+
function runTests() {
52+
it('should load all button harnesses', async () => {
53+
const buttons = await loader.getAllHarnesses(buttonHarness);
54+
expect(buttons.length).toBe(14);
55+
});
56+
57+
it('should load button with exact text', async () => {
58+
const buttons = await loader.getAllHarnesses(buttonHarness.with({text: 'Basic button'}));
59+
expect(buttons.length).toBe(1);
60+
expect(await buttons[0].getText()).toBe('Basic button');
61+
});
62+
63+
it('should load button with regex label match', async () => {
64+
const buttons = await loader.getAllHarnesses(buttonHarness.with({text: /basic/i}));
65+
expect(buttons.length).toBe(2);
66+
expect(await buttons[0].getText()).toBe('Basic button');
67+
expect(await buttons[1].getText()).toBe('Basic anchor');
68+
});
69+
70+
it('should get disabled state', async () => {
71+
// Grab each combination of [enabled, disabled] ⨯ [button, anchor]
72+
const [disabledFlatButton, enabledFlatAnchor] =
73+
await loader.getAllHarnesses(buttonHarness.with({text: /flat/i}));
74+
const [enabledRaisedButton, disabledRaisedAnchor] =
75+
await loader.getAllHarnesses(buttonHarness.with({text: /raised/i}));
76+
77+
expect(await enabledFlatAnchor.isDisabled()).toBe(false);
78+
expect(await disabledFlatButton.isDisabled()).toBe(true);
79+
expect(await enabledRaisedButton.isDisabled()).toBe(false);
80+
expect(await disabledRaisedAnchor.isDisabled()).toBe(true);
81+
});
82+
83+
it('should get name', async () => {
84+
const button = await loader.getHarness(buttonHarness.with({text: 'Basic button'}));
85+
expect(await button.getName()).toBe('basic');
86+
});
87+
88+
it('should get button text', async () => {
89+
const [firstButton, secondButton] = await loader.getAllHarnesses(buttonHarness);
90+
expect(await firstButton.getText()).toBe('Basic button');
91+
expect(await secondButton.getText()).toBe('Flat button');
92+
});
93+
94+
it('should focus and blur a button', async () => {
95+
const button = await loader.getHarness(buttonHarness.with({text: 'Basic button'}));
96+
expect(getActiveElementId()).not.toBe('basic');
97+
await button.foucs();
98+
expect(getActiveElementId()).toBe('basic');
99+
await button.blur();
100+
expect(getActiveElementId()).not.toBe('basic');
101+
});
102+
103+
it('should click a button', async () => {
104+
const button = await loader.getHarness(buttonHarness.with({text: 'Basic button'}));
105+
await button.click();
106+
107+
expect(fixture.componentInstance.clicked).toBe(true);
108+
});
109+
110+
it('should not click a disabled button', async () => {
111+
const button = await loader.getHarness(buttonHarness.with({text: 'Flat button'}));
112+
await button.click();
113+
114+
expect(fixture.componentInstance.clicked).toBe(false);
115+
});
116+
}
117+
118+
function getActiveElementId() {
119+
return document.activeElement ? document.activeElement.id : '';
120+
}
121+
122+
@Component({
123+
// Include one of each type of button selector to ensure that they're all captured by
124+
// the harness's selector.
125+
template: `
126+
<button id="basic" type="button" mat-button name="basic" (click)="clicked = true">
127+
Basic button
128+
</button>
129+
<button id="flat" type="button" mat-flat-button disabled (click)="clicked = true">
130+
Flat button
131+
</button>
132+
<button id="raised" type="button" mat-raised-button>Raised button</button>
133+
<button id="stroked" type="button" mat-stroked-button>Stroked button</button>
134+
<button id="icon" type="button" mat-icon-button>Icon button</button>
135+
<button id="fab" type="button" mat-fab>Fab button</button>
136+
<button id="mini-fab" type="button" mat-mini-fab>Mini Fab button</button>
137+
138+
<a id="anchor-basic" type="button" mat-button>Basic anchor</a>
139+
<a id="anchor-flat" type="button" mat-flat-button>Flat anchor</a>
140+
<a id="anchor-raised" type="button" mat-raised-button disabled>Raised anchor</a>
141+
<a id="anchor-stroked" type="button" mat-stroked-button>Stroked anchor</a>
142+
<a id="anchor-icon" type="button" mat-icon-button>Icon anchor</a>
143+
<a id="anchor-fab" type="button" mat-fab>Fab anchor</a>
144+
<a id="anchor-mini-fab" type="button" mat-mini-fab>Mini Fab anchor</a>
145+
`
146+
})
147+
class ButtonHarnessTest {
148+
disabled = true;
149+
clicked = false;
150+
}
151+
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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 {coerceBooleanProperty} from '@angular/cdk/coercion';
11+
import {ButtonHarnessFilters} from './button-harness-filters';
12+
13+
14+
/**
15+
* Harness for interacting with a standard mat-button in tests.
16+
* @dynamic
17+
*/
18+
export class MatButtonHarness extends ComponentHarness {
19+
static hostSelector = [
20+
'[mat-button]',
21+
'[mat-raised-button]',
22+
'[mat-flat-button]',
23+
'[mat-icon-button]',
24+
'[mat-stroked-button]',
25+
'[mat-fab]',
26+
'[mat-mini-fab]',
27+
].join(',');
28+
29+
/**
30+
* Gets a `HarnessPredicate` that can be used to search for a button with specific attributes.
31+
* @param options Options for narrowing the search:
32+
* - `label` finds a button with specific label text.
33+
* @return a `HarnessPredicate` configured with the given options.
34+
*/
35+
static with(options: ButtonHarnessFilters = {}): HarnessPredicate<MatButtonHarness> {
36+
return new HarnessPredicate(MatButtonHarness)
37+
.addOption('text', options.text,
38+
(harness, text) => HarnessPredicate.stringMatches(harness.getText(), text));
39+
}
40+
41+
/** Clicks the button. */
42+
async click(): Promise<void> {
43+
return (await this.host()).click();
44+
}
45+
46+
/** Gets a boolean promise indicating if the button is disabled. */
47+
async isDisabled(): Promise<boolean> {
48+
const disabled = (await this.host()).getAttribute('disabled');
49+
return coerceBooleanProperty(await disabled);
50+
}
51+
52+
/** Gets a promise for the button's name. */
53+
async getName(): Promise<string | null> {
54+
return (await this.host()).getAttribute('name');
55+
}
56+
57+
/** Gets a promise for the button's label text. */
58+
async getText(): Promise<string> {
59+
return (await this.host()).text();
60+
}
61+
62+
/** Focuses the button and returns a void promise that indicates when the action is complete. */
63+
async foucs(): Promise<void> {
64+
return (await this.host()).focus();
65+
}
66+
67+
/** Blurs the button and returns a void promise that indicates when the action is complete. */
68+
async blur(): Promise<void> {
69+
return (await this.host()).blur();
70+
}
71+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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 {coerceBooleanProperty} from '@angular/cdk/coercion';
11+
import {ButtonHarnessFilters} from './button-harness-filters';
12+
13+
14+
/**
15+
* Harness for interacting with a MDC-based mat-button in tests.
16+
* @dynamic
17+
*/
18+
export class MatButtonHarness extends ComponentHarness {
19+
static hostSelector = [
20+
'[mat-button]',
21+
'[mat-raised-button]',
22+
'[mat-flat-button]',
23+
'[mat-icon-button]',
24+
'[mat-stroked-button]',
25+
'[mat-fab]',
26+
'[mat-mini-fab]',
27+
].join(',');
28+
29+
/**
30+
* Gets a `HarnessPredicate` that can be used to search for a button with specific attributes.
31+
* @param options Options for narrowing the search:
32+
* - `label` finds a button with specific label text.
33+
* @return a `HarnessPredicate` configured with the given options.
34+
*/
35+
static with(options: ButtonHarnessFilters = {}): HarnessPredicate<MatButtonHarness> {
36+
return new HarnessPredicate(MatButtonHarness)
37+
.addOption('text', options.text,
38+
(harness, text) => HarnessPredicate.stringMatches(harness.getText(), text));
39+
}
40+
41+
/** Clicks the button. */
42+
async click(): Promise<void> {
43+
return (await this.host()).click();
44+
}
45+
46+
/** Gets a boolean promise indicating if the button is disabled. */
47+
async isDisabled(): Promise<boolean> {
48+
const disabled = (await this.host()).getAttribute('disabled');
49+
return coerceBooleanProperty(await disabled);
50+
}
51+
52+
/** Gets a promise for the button's name. */
53+
async getName(): Promise<string | null> {
54+
return (await this.host()).getAttribute('name');
55+
}
56+
57+
/** Gets a promise for the button's label text. */
58+
async getText(): Promise<string> {
59+
return (await this.host()).text();
60+
}
61+
62+
/** Focuses the button and returns a void promise that indicates when the action is complete. */
63+
async foucs(): Promise<void> {
64+
return (await this.host()).focus();
65+
}
66+
67+
/** Blurs the button and returns a void promise that indicates when the action is complete. */
68+
async blur(): Promise<void> {
69+
return (await this.host()).blur();
70+
}
71+
}

0 commit comments

Comments
 (0)