mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(material-experimental): slide-toggle test harnesses (#16545)
It slides, it toggles, it makes julienne fries.
This commit is contained in:
@@ -1,14 +1,17 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("@io_bazel_rules_sass//:defs.bzl", "sass_binary", "sass_library")
|
||||
load("//tools:defaults.bzl", "ng_e2e_test_library", "ng_module", "ng_test_library", "ng_web_test_suite")
|
||||
load("//tools:defaults.bzl", "ng_e2e_test_library", "ng_module", "ng_test_library", "ng_web_test_suite", "ts_library")
|
||||
load("//src/e2e-app:test_suite.bzl", "e2e_test_suite")
|
||||
|
||||
ng_module(
|
||||
name = "mdc-slide-toggle",
|
||||
srcs = glob(
|
||||
["**/*.ts"],
|
||||
exclude = ["**/*.spec.ts"],
|
||||
exclude = [
|
||||
"**/*.spec.ts",
|
||||
"harness/**",
|
||||
],
|
||||
),
|
||||
assets = [":slide_toggle_scss"] + glob(["**/*.html"]),
|
||||
module_name = "@angular/material-experimental/mdc-slide-toggle",
|
||||
@@ -24,6 +27,18 @@ ng_module(
|
||||
],
|
||||
)
|
||||
|
||||
ts_library(
|
||||
name = "harness",
|
||||
srcs = glob(
|
||||
["harness/**/*.ts"],
|
||||
exclude = ["**/*.spec.ts"],
|
||||
),
|
||||
deps = [
|
||||
"//src/cdk-experimental/testing",
|
||||
"//src/cdk/coercion",
|
||||
],
|
||||
)
|
||||
|
||||
sass_library(
|
||||
name = "mdc_slide_toggle_scss_lib",
|
||||
srcs = glob(["**/_*.scss"]),
|
||||
@@ -53,9 +68,13 @@ ng_test_library(
|
||||
exclude = ["**/*.e2e.spec.ts"],
|
||||
),
|
||||
deps = [
|
||||
":harness",
|
||||
":mdc-slide-toggle",
|
||||
"//src/cdk-experimental/testing",
|
||||
"//src/cdk-experimental/testing/testbed",
|
||||
"//src/cdk/bidi",
|
||||
"//src/cdk/testing",
|
||||
"//src/material/slide-toggle",
|
||||
"@npm//@angular/forms",
|
||||
"@npm//@angular/platform-browser",
|
||||
],
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* @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 {coerceBooleanProperty} from '@angular/cdk/coercion';
|
||||
import {SlideToggleHarnessFilters} from './slide-toggle-harness-filters';
|
||||
|
||||
|
||||
/**
|
||||
* Harness for interacting with a MDC-based mat-slide-toggle in tests.
|
||||
* @dynamic
|
||||
*/
|
||||
export class MatSlideToggleHarness extends ComponentHarness {
|
||||
static hostSelector = 'mat-slide-toggle';
|
||||
|
||||
/**
|
||||
* Gets a `HarnessPredicate` that can be used to search for a slide-toggle w/ specific attributes.
|
||||
* @param options Options for narrowing the search:
|
||||
* - `label` finds a slide-toggle with specific label text.
|
||||
* @return a `HarnessPredicate` configured with the given options.
|
||||
*/
|
||||
static with(options: SlideToggleHarnessFilters = {}): HarnessPredicate<MatSlideToggleHarness> {
|
||||
return new HarnessPredicate(MatSlideToggleHarness)
|
||||
.addOption('label', options.label,
|
||||
(harness, label) => HarnessPredicate.stringMatches(harness.getLabelText(), label));
|
||||
}
|
||||
|
||||
private _label = this.locatorFor('label');
|
||||
private _input = this.locatorFor('input');
|
||||
private _inputContainer = this.locatorFor('.mdc-switch');
|
||||
|
||||
/** Gets a boolean promise indicating if the slide-toggle is checked. */
|
||||
async isChecked(): Promise<boolean> {
|
||||
const checked = (await this._input()).getAttribute('checked');
|
||||
return coerceBooleanProperty(await checked);
|
||||
}
|
||||
|
||||
/** Gets a boolean promise indicating if the slide-toggle is disabled. */
|
||||
async isDisabled(): Promise<boolean> {
|
||||
const disabled = (await this._input()).getAttribute('disabled');
|
||||
return coerceBooleanProperty(await disabled);
|
||||
}
|
||||
|
||||
/** Gets a boolean promise indicating if the slide-toggle is required. */
|
||||
async isRequired(): Promise<boolean> {
|
||||
const required = (await this._input()).getAttribute('required');
|
||||
return coerceBooleanProperty(await required);
|
||||
}
|
||||
|
||||
/** Gets a boolean promise indicating if the slide-toggle is valid. */
|
||||
async isValid(): Promise<boolean> {
|
||||
const invalid = (await this.host()).hasClass('ng-invalid');
|
||||
return !(await invalid);
|
||||
}
|
||||
|
||||
/** Gets a promise for the slide-toggle's name. */
|
||||
async getName(): Promise<string | null> {
|
||||
return (await this._input()).getAttribute('name');
|
||||
}
|
||||
|
||||
/** Gets a promise for the slide-toggle's aria-label. */
|
||||
async getAriaLabel(): Promise<string | null> {
|
||||
return (await this._input()).getAttribute('aria-label');
|
||||
}
|
||||
|
||||
/** Gets a promise for the slide-toggle's aria-labelledby. */
|
||||
async getAriaLabelledby(): Promise<string | null> {
|
||||
return (await this._input()).getAttribute('aria-labelledby');
|
||||
}
|
||||
|
||||
/** Gets a promise for the slide-toggle's label text. */
|
||||
async getLabelText(): Promise<string> {
|
||||
return (await this._label()).text();
|
||||
}
|
||||
|
||||
/** Focuses the slide-toggle and returns a void promise that indicates action completion. */
|
||||
async foucs(): Promise<void> {
|
||||
return (await this._input()).focus();
|
||||
}
|
||||
|
||||
/** Blurs the slide-toggle and returns a void promise that indicates action completion. */
|
||||
async blur(): Promise<void> {
|
||||
return (await this._input()).blur();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the checked state of the slide-toggle and returns a void promise that indicates when the
|
||||
* action is complete.
|
||||
*
|
||||
* Note: This attempts to toggle the slide-toggle as a user would, by clicking it.
|
||||
*/
|
||||
async toggle(): Promise<void> {
|
||||
const elToClick = await this.isDisabled() ? this._inputContainer() : this._input();
|
||||
return (await elToClick).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the slide-toggle in a checked state by toggling it if it is currently unchecked, or doing
|
||||
* nothing if it is already checked. Returns a void promise that indicates when the action is
|
||||
* complete.
|
||||
*
|
||||
* Note: This attempts to check the slide-toggle as a user would, by clicking it.
|
||||
*/
|
||||
async check(): Promise<void> {
|
||||
if (!(await this.isChecked())) {
|
||||
await this.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the slide-toggle in an unchecked state by toggling it if it is currently checked, or doing
|
||||
* nothing if it is already unchecked. Returns a void promise that indicates when the action is
|
||||
* complete.
|
||||
*
|
||||
* Note: This attempts to uncheck the slide-toggle as a user would, by clicking it.
|
||||
*/
|
||||
async uncheck(): Promise<void> {
|
||||
if (await this.isChecked()) {
|
||||
await this.toggle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 type SlideToggleHarnessFilters = {
|
||||
label?: string | RegExp
|
||||
};
|
||||
@@ -0,0 +1,193 @@
|
||||
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 {FormControl, ReactiveFormsModule} from '@angular/forms';
|
||||
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
|
||||
import {MatSlideToggleModule as MatMdcSlideToggleModule} from '../index';
|
||||
import {MatSlideToggleHarness} from './slide-toggle-harness';
|
||||
import {MatSlideToggleHarness as MatMdcSlideToggleHarness} from './mdc-slide-toggle-harness';
|
||||
|
||||
|
||||
let fixture: ComponentFixture<SlideToggleHarnessTest>;
|
||||
let loader: HarnessLoader;
|
||||
let slideToggleHarness: typeof MatSlideToggleHarness;
|
||||
|
||||
describe('MatSlideToggleHarness', () => {
|
||||
describe('non-MDC-based', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MatSlideToggleModule, ReactiveFormsModule],
|
||||
declarations: [SlideToggleHarnessTest],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SlideToggleHarnessTest);
|
||||
fixture.detectChanges();
|
||||
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||
slideToggleHarness = MatSlideToggleHarness;
|
||||
});
|
||||
|
||||
runTests();
|
||||
});
|
||||
|
||||
describe('MDC-based', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MatMdcSlideToggleModule, ReactiveFormsModule],
|
||||
declarations: [SlideToggleHarnessTest],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SlideToggleHarnessTest);
|
||||
fixture.detectChanges();
|
||||
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||
// Public APIs are the same as MatSlideToggleHarness, but cast is necessary because of
|
||||
// different private fields.
|
||||
slideToggleHarness = MatMdcSlideToggleHarness as any;
|
||||
});
|
||||
|
||||
runTests();
|
||||
});
|
||||
});
|
||||
|
||||
/** Shared tests to run on both the original and MDC-based slide-toggles. */
|
||||
function runTests() {
|
||||
it('should load all slide-toggle harnesses', async () => {
|
||||
const slideToggles = await loader.getAllHarnesses(slideToggleHarness);
|
||||
expect(slideToggles.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should load slide-toggle with exact label', async () => {
|
||||
const slideToggles = await loader.getAllHarnesses(slideToggleHarness.with({label: 'First'}));
|
||||
expect(slideToggles.length).toBe(1);
|
||||
expect(await slideToggles[0].getLabelText()).toBe('First');
|
||||
});
|
||||
|
||||
it('should load slide-toggle with regex label match', async () => {
|
||||
const slideToggles = await loader.getAllHarnesses(slideToggleHarness.with({label: /^s/i}));
|
||||
expect(slideToggles.length).toBe(1);
|
||||
expect(await slideToggles[0].getLabelText()).toBe('Second');
|
||||
});
|
||||
|
||||
it('should get checked state', async () => {
|
||||
const [checkedToggle, uncheckedToggle] = await loader.getAllHarnesses(slideToggleHarness);
|
||||
expect(await checkedToggle.isChecked()).toBe(true);
|
||||
expect(await uncheckedToggle.isChecked()).toBe(false);
|
||||
});
|
||||
|
||||
it('should get disabled state', async () => {
|
||||
const [enabledToggle, disabledToggle] = await loader.getAllHarnesses(slideToggleHarness);
|
||||
expect(await enabledToggle.isDisabled()).toBe(false);
|
||||
expect(await disabledToggle.isDisabled()).toBe(true);
|
||||
});
|
||||
|
||||
it('should get required state', async () => {
|
||||
const [requiredToggle, optionalToggle] = await loader.getAllHarnesses(slideToggleHarness);
|
||||
expect(await requiredToggle.isRequired()).toBe(true);
|
||||
expect(await optionalToggle.isRequired()).toBe(false);
|
||||
});
|
||||
|
||||
it('should get valid state', async () => {
|
||||
const [requiredToggle, optionalToggle] = await loader.getAllHarnesses(slideToggleHarness);
|
||||
expect(await optionalToggle.isValid()).toBe(true, 'Expected optional toggle to be valid');
|
||||
expect(await requiredToggle.isValid())
|
||||
.toBe(true, 'Expected required checked toggle to be valid');
|
||||
await requiredToggle.uncheck();
|
||||
expect(await requiredToggle.isValid())
|
||||
.toBe(false, 'Expected required unchecked toggle to be invalid');
|
||||
});
|
||||
|
||||
it('should get name', async () => {
|
||||
const slideToggle = await loader.getHarness(slideToggleHarness.with({label: 'First'}));
|
||||
expect(await slideToggle.getName()).toBe('first-name');
|
||||
});
|
||||
|
||||
it('should get aria-label', async () => {
|
||||
const slideToggle = await loader.getHarness(slideToggleHarness.with({label: 'First'}));
|
||||
expect(await slideToggle.getAriaLabel()).toBe('First slide-toggle');
|
||||
});
|
||||
|
||||
it('should get aria-labelledby', async () => {
|
||||
const slideToggle = await loader.getHarness(slideToggleHarness.with({label: 'Second'}));
|
||||
expect(await slideToggle.getAriaLabelledby()).toBe('second-label');
|
||||
});
|
||||
|
||||
it('should get label text', async () => {
|
||||
const [firstToggle, secondToggle] = await loader.getAllHarnesses(slideToggleHarness);
|
||||
expect(await firstToggle.getLabelText()).toBe('First');
|
||||
expect(await secondToggle.getLabelText()).toBe('Second');
|
||||
});
|
||||
|
||||
it('should focus slide-toggle', async () => {
|
||||
const slideToggle = await loader.getHarness(slideToggleHarness.with({label: 'First'}));
|
||||
expect(getActiveElementTagName()).not.toBe('input');
|
||||
await slideToggle.foucs();
|
||||
expect(getActiveElementTagName()).toBe('input');
|
||||
});
|
||||
|
||||
it('should blur slide-toggle', async () => {
|
||||
const slideToggle = await loader.getHarness(slideToggleHarness.with({label: 'First'}));
|
||||
await slideToggle.foucs();
|
||||
expect(getActiveElementTagName()).toBe('input');
|
||||
await slideToggle.blur();
|
||||
expect(getActiveElementTagName()).not.toBe('input');
|
||||
});
|
||||
|
||||
it('should toggle slide-toggle', async () => {
|
||||
fixture.componentInstance.disabled = false;
|
||||
const [checkedToggle, uncheckedToggle] = await loader.getAllHarnesses(slideToggleHarness);
|
||||
await checkedToggle.toggle();
|
||||
await uncheckedToggle.toggle();
|
||||
expect(await checkedToggle.isChecked()).toBe(false);
|
||||
expect(await uncheckedToggle.isChecked()).toBe(true);
|
||||
});
|
||||
|
||||
it('should check slide-toggle', async () => {
|
||||
fixture.componentInstance.disabled = false;
|
||||
const [checkedToggle, uncheckedToggle] = await loader.getAllHarnesses(slideToggleHarness);
|
||||
await checkedToggle.check();
|
||||
await uncheckedToggle.check();
|
||||
expect(await checkedToggle.isChecked()).toBe(true);
|
||||
expect(await uncheckedToggle.isChecked()).toBe(true);
|
||||
});
|
||||
|
||||
it('should uncheck slide-toggle', async () => {
|
||||
fixture.componentInstance.disabled = false;
|
||||
const [checkedToggle, uncheckedToggle] = await loader.getAllHarnesses(slideToggleHarness);
|
||||
await checkedToggle.uncheck();
|
||||
await uncheckedToggle.uncheck();
|
||||
expect(await checkedToggle.isChecked()).toBe(false);
|
||||
expect(await uncheckedToggle.isChecked()).toBe(false);
|
||||
});
|
||||
|
||||
it('should not toggle disabled slide-toggle', async () => {
|
||||
const disabledToggle = await loader.getHarness(slideToggleHarness.with({label: 'Second'}));
|
||||
expect(await disabledToggle.isChecked()).toBe(false);
|
||||
await disabledToggle.toggle();
|
||||
expect(await disabledToggle.isChecked()).toBe(false);
|
||||
});
|
||||
}
|
||||
|
||||
function getActiveElementTagName() {
|
||||
return document.activeElement ? document.activeElement.tagName.toLowerCase() : '';
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<mat-slide-toggle
|
||||
[formControl]="ctrl"
|
||||
required
|
||||
name="first-name"
|
||||
aria-label="First slide-toggle">
|
||||
First
|
||||
</mat-slide-toggle>
|
||||
<mat-slide-toggle [disabled]="disabled" aria-labelledby="second-label">
|
||||
Second
|
||||
</mat-slide-toggle>
|
||||
<span id="second-label">Second slide-toggle</span>
|
||||
`
|
||||
})
|
||||
class SlideToggleHarnessTest {
|
||||
ctrl = new FormControl(true);
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* @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 {coerceBooleanProperty} from '@angular/cdk/coercion';
|
||||
import {SlideToggleHarnessFilters} from './slide-toggle-harness-filters';
|
||||
|
||||
|
||||
/**
|
||||
* Harness for interacting with a standard mat-slide-toggle in tests.
|
||||
* @dynamic
|
||||
*/
|
||||
export class MatSlideToggleHarness extends ComponentHarness {
|
||||
static hostSelector = 'mat-slide-toggle';
|
||||
|
||||
/**
|
||||
* Gets a `HarnessPredicate` that can be used to search for a slide-toggle w/ specific attributes.
|
||||
* @param options Options for narrowing the search:
|
||||
* - `label` finds a slide-toggle with specific label text.
|
||||
* @return a `HarnessPredicate` configured with the given options.
|
||||
*/
|
||||
static with(options: SlideToggleHarnessFilters = {}): HarnessPredicate<MatSlideToggleHarness> {
|
||||
return new HarnessPredicate(MatSlideToggleHarness)
|
||||
.addOption('label', options.label,
|
||||
(harness, label) => HarnessPredicate.stringMatches(harness.getLabelText(), label));
|
||||
}
|
||||
|
||||
private _label = this.locatorFor('label');
|
||||
private _input = this.locatorFor('input');
|
||||
private _inputContainer = this.locatorFor('.mat-slide-toggle-bar');
|
||||
|
||||
/** Gets a boolean promise indicating if the slide-toggle is checked. */
|
||||
async isChecked(): Promise<boolean> {
|
||||
const checked = (await this._input()).getAttribute('checked');
|
||||
return coerceBooleanProperty(await checked);
|
||||
}
|
||||
|
||||
/** Gets a boolean promise indicating if the slide-toggle is disabled. */
|
||||
async isDisabled(): Promise<boolean> {
|
||||
const disabled = (await this._input()).getAttribute('disabled');
|
||||
return coerceBooleanProperty(await disabled);
|
||||
}
|
||||
|
||||
/** Gets a boolean promise indicating if the slide-toggle is required. */
|
||||
async isRequired(): Promise<boolean> {
|
||||
const required = (await this._input()).getAttribute('required');
|
||||
return coerceBooleanProperty(await required);
|
||||
}
|
||||
|
||||
/** Gets a boolean promise indicating if the slide-toggle is valid. */
|
||||
async isValid(): Promise<boolean> {
|
||||
const invalid = (await this.host()).hasClass('ng-invalid');
|
||||
return !(await invalid);
|
||||
}
|
||||
|
||||
/** Gets a promise for the slide-toggle's name. */
|
||||
async getName(): Promise<string | null> {
|
||||
return (await this._input()).getAttribute('name');
|
||||
}
|
||||
|
||||
/** Gets a promise for the slide-toggle's aria-label. */
|
||||
async getAriaLabel(): Promise<string | null> {
|
||||
return (await this._input()).getAttribute('aria-label');
|
||||
}
|
||||
|
||||
/** Gets a promise for the slide-toggle's aria-labelledby. */
|
||||
async getAriaLabelledby(): Promise<string | null> {
|
||||
return (await this._input()).getAttribute('aria-labelledby');
|
||||
}
|
||||
|
||||
/** Gets a promise for the slide-toggle's label text. */
|
||||
async getLabelText(): Promise<string> {
|
||||
return (await this._label()).text();
|
||||
}
|
||||
|
||||
/** Focuses the slide-toggle and returns a void promise that indicates action completion. */
|
||||
async foucs(): Promise<void> {
|
||||
return (await this._input()).focus();
|
||||
}
|
||||
|
||||
/** Blurs the slide-toggle and returns a void promise that indicates action completion. */
|
||||
async blur(): Promise<void> {
|
||||
return (await this._input()).blur();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the checked state of the slide-toggle and returns a void promise that indicates when the
|
||||
* action is complete.
|
||||
*
|
||||
* Note: This toggles the slide-toggle as a user would, by clicking it.
|
||||
*/
|
||||
async toggle(): Promise<void> {
|
||||
return (await this._inputContainer()).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the slide-toggle in a checked state by toggling it if it is currently unchecked, or doing
|
||||
* nothing if it is already checked. Returns a void promise that indicates when the action is
|
||||
* complete.
|
||||
*
|
||||
* Note: This attempts to check the slide-toggle as a user would, by clicking it.
|
||||
*/
|
||||
async check(): Promise<void> {
|
||||
if (!(await this.isChecked())) {
|
||||
await this.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the slide-toggle in an unchecked state by toggling it if it is currently checked, or doing
|
||||
* nothing if it is already unchecked. Returns a void promise that indicates when the action is
|
||||
* complete.
|
||||
*
|
||||
* Note: This toggles the slide-toggle as a user would, by clicking it.
|
||||
*/
|
||||
async uncheck(): Promise<void> {
|
||||
if (await this.isChecked()) {
|
||||
await this.toggle();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user