feat(material-experimental): slide-toggle test harnesses (#16545)

It slides, it toggles, it makes julienne fries.
This commit is contained in:
Jeremy Elbourn
2019-07-25 13:14:18 -07:00
committed by GitHub
parent 615070acbd
commit 2a04ccfd46
5 changed files with 478 additions and 2 deletions
@@ -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();
}
}
}