Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions src/material-experimental/mdc-slide-toggle/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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"]),
Expand Down Expand Up @@ -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",
],
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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
};
Original file line number Diff line number Diff line change
@@ -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;
}

Loading