Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 2a04ccf

Browse files
feat(material-experimental): slide-toggle test harnesses (#16545)
It slides, it toggles, it makes julienne fries.
1 parent 615070a commit 2a04ccf

File tree

5 files changed

+478
-2
lines changed

5 files changed

+478
-2
lines changed

‎src/material-experimental/mdc-slide-toggle/BUILD.bazel‎

Lines changed: 21 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", "ng_test_library", "ng_web_test_suite")
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-slide-toggle",
99
srcs = glob(
1010
["**/*.ts"],
11-
exclude = ["**/*.spec.ts"],
11+
exclude = [
12+
"**/*.spec.ts",
13+
"harness/**",
14+
],
1215
),
1316
assets = [":slide_toggle_scss"] + glob(["**/*.html"]),
1417
module_name = "@angular/material-experimental/mdc-slide-toggle",
@@ -24,6 +27,18 @@ ng_module(
2427
],
2528
)
2629

30+
ts_library(
31+
name = "harness",
32+
srcs = glob(
33+
["harness/**/*.ts"],
34+
exclude = ["**/*.spec.ts"],
35+
),
36+
deps = [
37+
"//src/cdk-experimental/testing",
38+
"//src/cdk/coercion",
39+
],
40+
)
41+
2742
sass_library(
2843
name = "mdc_slide_toggle_scss_lib",
2944
srcs = glob(["**/_*.scss"]),
@@ -53,9 +68,13 @@ ng_test_library(
5368
exclude = ["**/*.e2e.spec.ts"],
5469
),
5570
deps = [
71+
":harness",
5672
":mdc-slide-toggle",
73+
"//src/cdk-experimental/testing",
74+
"//src/cdk-experimental/testing/testbed",
5775
"//src/cdk/bidi",
5876
"//src/cdk/testing",
77+
"//src/material/slide-toggle",
5978
"@npm//@angular/forms",
6079
"@npm//@angular/platform-browser",
6180
],
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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 {SlideToggleHarnessFilters} from './slide-toggle-harness-filters';
12+
13+
14+
/**
15+
* Harness for interacting with a MDC-based mat-slide-toggle in tests.
16+
* @dynamic
17+
*/
18+
export class MatSlideToggleHarness extends ComponentHarness {
19+
static hostSelector = 'mat-slide-toggle';
20+
21+
/**
22+
* Gets a `HarnessPredicate` that can be used to search for a slide-toggle w/ specific attributes.
23+
* @param options Options for narrowing the search:
24+
* - `label` finds a slide-toggle with specific label text.
25+
* @return a `HarnessPredicate` configured with the given options.
26+
*/
27+
static with(options: SlideToggleHarnessFilters = {}): HarnessPredicate<MatSlideToggleHarness> {
28+
return new HarnessPredicate(MatSlideToggleHarness)
29+
.addOption('label', options.label,
30+
(harness, label) => HarnessPredicate.stringMatches(harness.getLabelText(), label));
31+
}
32+
33+
private _label = this.locatorFor('label');
34+
private _input = this.locatorFor('input');
35+
private _inputContainer = this.locatorFor('.mdc-switch');
36+
37+
/** Gets a boolean promise indicating if the slide-toggle is checked. */
38+
async isChecked(): Promise<boolean> {
39+
const checked = (await this._input()).getAttribute('checked');
40+
return coerceBooleanProperty(await checked);
41+
}
42+
43+
/** Gets a boolean promise indicating if the slide-toggle is disabled. */
44+
async isDisabled(): Promise<boolean> {
45+
const disabled = (await this._input()).getAttribute('disabled');
46+
return coerceBooleanProperty(await disabled);
47+
}
48+
49+
/** Gets a boolean promise indicating if the slide-toggle is required. */
50+
async isRequired(): Promise<boolean> {
51+
const required = (await this._input()).getAttribute('required');
52+
return coerceBooleanProperty(await required);
53+
}
54+
55+
/** Gets a boolean promise indicating if the slide-toggle is valid. */
56+
async isValid(): Promise<boolean> {
57+
const invalid = (await this.host()).hasClass('ng-invalid');
58+
return !(await invalid);
59+
}
60+
61+
/** Gets a promise for the slide-toggle's name. */
62+
async getName(): Promise<string | null> {
63+
return (await this._input()).getAttribute('name');
64+
}
65+
66+
/** Gets a promise for the slide-toggle's aria-label. */
67+
async getAriaLabel(): Promise<string | null> {
68+
return (await this._input()).getAttribute('aria-label');
69+
}
70+
71+
/** Gets a promise for the slide-toggle's aria-labelledby. */
72+
async getAriaLabelledby(): Promise<string | null> {
73+
return (await this._input()).getAttribute('aria-labelledby');
74+
}
75+
76+
/** Gets a promise for the slide-toggle's label text. */
77+
async getLabelText(): Promise<string> {
78+
return (await this._label()).text();
79+
}
80+
81+
/** Focuses the slide-toggle and returns a void promise that indicates action completion. */
82+
async foucs(): Promise<void> {
83+
return (await this._input()).focus();
84+
}
85+
86+
/** Blurs the slide-toggle and returns a void promise that indicates action completion. */
87+
async blur(): Promise<void> {
88+
return (await this._input()).blur();
89+
}
90+
91+
/**
92+
* Toggle the checked state of the slide-toggle and returns a void promise that indicates when the
93+
* action is complete.
94+
*
95+
* Note: This attempts to toggle the slide-toggle as a user would, by clicking it.
96+
*/
97+
async toggle(): Promise<void> {
98+
const elToClick = await this.isDisabled() ? this._inputContainer() : this._input();
99+
return (await elToClick).click();
100+
}
101+
102+
/**
103+
* Puts the slide-toggle in a checked state by toggling it if it is currently unchecked, or doing
104+
* nothing if it is already checked. Returns a void promise that indicates when the action is
105+
* complete.
106+
*
107+
* Note: This attempts to check the slide-toggle as a user would, by clicking it.
108+
*/
109+
async check(): Promise<void> {
110+
if (!(await this.isChecked())) {
111+
await this.toggle();
112+
}
113+
}
114+
115+
/**
116+
* Puts the slide-toggle in an unchecked state by toggling it if it is currently checked, or doing
117+
* nothing if it is already unchecked. Returns a void promise that indicates when the action is
118+
* complete.
119+
*
120+
* Note: This attempts to uncheck the slide-toggle as a user would, by clicking it.
121+
*/
122+
async uncheck(): Promise<void> {
123+
if (await this.isChecked()) {
124+
await this.toggle();
125+
}
126+
}
127+
}
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 SlideToggleHarnessFilters = {
10+
label?: string | RegExp
11+
};
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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 {FormControl, ReactiveFormsModule} from '@angular/forms';
6+
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
7+
import {MatSlideToggleModule as MatMdcSlideToggleModule} from '../index';
8+
import {MatSlideToggleHarness} from './slide-toggle-harness';
9+
import {MatSlideToggleHarness as MatMdcSlideToggleHarness} from './mdc-slide-toggle-harness';
10+
11+
12+
let fixture: ComponentFixture<SlideToggleHarnessTest>;
13+
let loader: HarnessLoader;
14+
let slideToggleHarness: typeof MatSlideToggleHarness;
15+
16+
describe('MatSlideToggleHarness', () => {
17+
describe('non-MDC-based', () => {
18+
beforeEach(async () => {
19+
await TestBed.configureTestingModule({
20+
imports: [MatSlideToggleModule, ReactiveFormsModule],
21+
declarations: [SlideToggleHarnessTest],
22+
}).compileComponents();
23+
24+
fixture = TestBed.createComponent(SlideToggleHarnessTest);
25+
fixture.detectChanges();
26+
loader = TestbedHarnessEnvironment.loader(fixture);
27+
slideToggleHarness = MatSlideToggleHarness;
28+
});
29+
30+
runTests();
31+
});
32+
33+
describe('MDC-based', () => {
34+
beforeEach(async () => {
35+
await TestBed.configureTestingModule({
36+
imports: [MatMdcSlideToggleModule, ReactiveFormsModule],
37+
declarations: [SlideToggleHarnessTest],
38+
}).compileComponents();
39+
40+
fixture = TestBed.createComponent(SlideToggleHarnessTest);
41+
fixture.detectChanges();
42+
loader = TestbedHarnessEnvironment.loader(fixture);
43+
// Public APIs are the same as MatSlideToggleHarness, but cast is necessary because of
44+
// different private fields.
45+
slideToggleHarness = MatMdcSlideToggleHarness as any;
46+
});
47+
48+
runTests();
49+
});
50+
});
51+
52+
/** Shared tests to run on both the original and MDC-based slide-toggles. */
53+
function runTests() {
54+
it('should load all slide-toggle harnesses', async () => {
55+
const slideToggles = await loader.getAllHarnesses(slideToggleHarness);
56+
expect(slideToggles.length).toBe(2);
57+
});
58+
59+
it('should load slide-toggle with exact label', async () => {
60+
const slideToggles = await loader.getAllHarnesses(slideToggleHarness.with({label: 'First'}));
61+
expect(slideToggles.length).toBe(1);
62+
expect(await slideToggles[0].getLabelText()).toBe('First');
63+
});
64+
65+
it('should load slide-toggle with regex label match', async () => {
66+
const slideToggles = await loader.getAllHarnesses(slideToggleHarness.with({label: /^s/i}));
67+
expect(slideToggles.length).toBe(1);
68+
expect(await slideToggles[0].getLabelText()).toBe('Second');
69+
});
70+
71+
it('should get checked state', async () => {
72+
const [checkedToggle, uncheckedToggle] = await loader.getAllHarnesses(slideToggleHarness);
73+
expect(await checkedToggle.isChecked()).toBe(true);
74+
expect(await uncheckedToggle.isChecked()).toBe(false);
75+
});
76+
77+
it('should get disabled state', async () => {
78+
const [enabledToggle, disabledToggle] = await loader.getAllHarnesses(slideToggleHarness);
79+
expect(await enabledToggle.isDisabled()).toBe(false);
80+
expect(await disabledToggle.isDisabled()).toBe(true);
81+
});
82+
83+
it('should get required state', async () => {
84+
const [requiredToggle, optionalToggle] = await loader.getAllHarnesses(slideToggleHarness);
85+
expect(await requiredToggle.isRequired()).toBe(true);
86+
expect(await optionalToggle.isRequired()).toBe(false);
87+
});
88+
89+
it('should get valid state', async () => {
90+
const [requiredToggle, optionalToggle] = await loader.getAllHarnesses(slideToggleHarness);
91+
expect(await optionalToggle.isValid()).toBe(true, 'Expected optional toggle to be valid');
92+
expect(await requiredToggle.isValid())
93+
.toBe(true, 'Expected required checked toggle to be valid');
94+
await requiredToggle.uncheck();
95+
expect(await requiredToggle.isValid())
96+
.toBe(false, 'Expected required unchecked toggle to be invalid');
97+
});
98+
99+
it('should get name', async () => {
100+
const slideToggle = await loader.getHarness(slideToggleHarness.with({label: 'First'}));
101+
expect(await slideToggle.getName()).toBe('first-name');
102+
});
103+
104+
it('should get aria-label', async () => {
105+
const slideToggle = await loader.getHarness(slideToggleHarness.with({label: 'First'}));
106+
expect(await slideToggle.getAriaLabel()).toBe('First slide-toggle');
107+
});
108+
109+
it('should get aria-labelledby', async () => {
110+
const slideToggle = await loader.getHarness(slideToggleHarness.with({label: 'Second'}));
111+
expect(await slideToggle.getAriaLabelledby()).toBe('second-label');
112+
});
113+
114+
it('should get label text', async () => {
115+
const [firstToggle, secondToggle] = await loader.getAllHarnesses(slideToggleHarness);
116+
expect(await firstToggle.getLabelText()).toBe('First');
117+
expect(await secondToggle.getLabelText()).toBe('Second');
118+
});
119+
120+
it('should focus slide-toggle', async () => {
121+
const slideToggle = await loader.getHarness(slideToggleHarness.with({label: 'First'}));
122+
expect(getActiveElementTagName()).not.toBe('input');
123+
await slideToggle.foucs();
124+
expect(getActiveElementTagName()).toBe('input');
125+
});
126+
127+
it('should blur slide-toggle', async () => {
128+
const slideToggle = await loader.getHarness(slideToggleHarness.with({label: 'First'}));
129+
await slideToggle.foucs();
130+
expect(getActiveElementTagName()).toBe('input');
131+
await slideToggle.blur();
132+
expect(getActiveElementTagName()).not.toBe('input');
133+
});
134+
135+
it('should toggle slide-toggle', async () => {
136+
fixture.componentInstance.disabled = false;
137+
const [checkedToggle, uncheckedToggle] = await loader.getAllHarnesses(slideToggleHarness);
138+
await checkedToggle.toggle();
139+
await uncheckedToggle.toggle();
140+
expect(await checkedToggle.isChecked()).toBe(false);
141+
expect(await uncheckedToggle.isChecked()).toBe(true);
142+
});
143+
144+
it('should check slide-toggle', async () => {
145+
fixture.componentInstance.disabled = false;
146+
const [checkedToggle, uncheckedToggle] = await loader.getAllHarnesses(slideToggleHarness);
147+
await checkedToggle.check();
148+
await uncheckedToggle.check();
149+
expect(await checkedToggle.isChecked()).toBe(true);
150+
expect(await uncheckedToggle.isChecked()).toBe(true);
151+
});
152+
153+
it('should uncheck slide-toggle', async () => {
154+
fixture.componentInstance.disabled = false;
155+
const [checkedToggle, uncheckedToggle] = await loader.getAllHarnesses(slideToggleHarness);
156+
await checkedToggle.uncheck();
157+
await uncheckedToggle.uncheck();
158+
expect(await checkedToggle.isChecked()).toBe(false);
159+
expect(await uncheckedToggle.isChecked()).toBe(false);
160+
});
161+
162+
it('should not toggle disabled slide-toggle', async () => {
163+
const disabledToggle = await loader.getHarness(slideToggleHarness.with({label: 'Second'}));
164+
expect(await disabledToggle.isChecked()).toBe(false);
165+
await disabledToggle.toggle();
166+
expect(await disabledToggle.isChecked()).toBe(false);
167+
});
168+
}
169+
170+
function getActiveElementTagName() {
171+
return document.activeElement ? document.activeElement.tagName.toLowerCase() : '';
172+
}
173+
174+
@Component({
175+
template: `
176+
<mat-slide-toggle
177+
[formControl]="ctrl"
178+
required
179+
name="first-name"
180+
aria-label="First slide-toggle">
181+
First
182+
</mat-slide-toggle>
183+
<mat-slide-toggle [disabled]="disabled" aria-labelledby="second-label">
184+
Second
185+
</mat-slide-toggle>
186+
<span id="second-label">Second slide-toggle</span>
187+
`
188+
})
189+
class SlideToggleHarnessTest {
190+
ctrl = new FormControl(true);
191+
disabled = true;
192+
}
193+

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /