From e3ccecbe1acabb0e8bb76c835cb4bcbd2776833d Mon Sep 17 00:00:00 2001 From: crisbeto Date: Sun, 28 Jul 2019 14:33:12 +0200 Subject: [PATCH] feat(material-experimental): add button toggle test harness Adds a test harness for `mat-button-toggle`. --- .github/CODEOWNERS | 1 + .../mdc-button-toggle/BUILD.bazel | 99 ++++++++++ .../mdc-button-toggle/README.md | 1 + .../mdc-button-toggle/_mdc-button-toggle.scss | 13 ++ .../button-toggle.e2e.spec.ts | 1 + .../mdc-button-toggle/button-toggle.scss | 1 + .../harness/button-toggle-harness-filters.ts | 12 ++ .../harness/button-toggle-harness.spec.ts | 187 ++++++++++++++++++ .../harness/button-toggle-harness.ts | 107 ++++++++++ .../harness/mdc-button-toggle-harness.ts | 18 ++ .../mdc-button-toggle/index.ts | 9 + .../mdc-button-toggle/module.ts | 18 ++ .../mdc-button-toggle/public-api.ts | 9 + .../mdc-button-toggle/tsconfig-build.json | 15 ++ 14 files changed, 491 insertions(+) create mode 100644 src/material-experimental/mdc-button-toggle/BUILD.bazel create mode 100644 src/material-experimental/mdc-button-toggle/README.md create mode 100644 src/material-experimental/mdc-button-toggle/_mdc-button-toggle.scss create mode 100644 src/material-experimental/mdc-button-toggle/button-toggle.e2e.spec.ts create mode 100644 src/material-experimental/mdc-button-toggle/button-toggle.scss create mode 100644 src/material-experimental/mdc-button-toggle/harness/button-toggle-harness-filters.ts create mode 100644 src/material-experimental/mdc-button-toggle/harness/button-toggle-harness.spec.ts create mode 100644 src/material-experimental/mdc-button-toggle/harness/button-toggle-harness.ts create mode 100644 src/material-experimental/mdc-button-toggle/harness/mdc-button-toggle-harness.ts create mode 100644 src/material-experimental/mdc-button-toggle/index.ts create mode 100644 src/material-experimental/mdc-button-toggle/module.ts create mode 100644 src/material-experimental/mdc-button-toggle/public-api.ts create mode 100644 src/material-experimental/mdc-button-toggle/tsconfig-build.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b7dda6d10ac1..b19bc85ddc19 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -108,6 +108,7 @@ /src/material-experimental/mdc-slider/** @devversion /src/material-experimental/mdc-tabs/** @crisbeto /src/material-experimental/mdc-sidenav/** @crisbeto +/src/material-experimental/mdc-button-toggle/** @crisbeto /src/material-experimental/mdc-theming/** @mmalerba /src/material-experimental/mdc-typography/** @mmalerba /src/material-experimental/popover-edit/** @kseamon @andrewseguin diff --git a/src/material-experimental/mdc-button-toggle/BUILD.bazel b/src/material-experimental/mdc-button-toggle/BUILD.bazel new file mode 100644 index 000000000000..b36c57da9ec0 --- /dev/null +++ b/src/material-experimental/mdc-button-toggle/BUILD.bazel @@ -0,0 +1,99 @@ +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", "ts_library") +load("//src/e2e-app:test_suite.bzl", "e2e_test_suite") + +ng_module( + name = "mdc-button-toggle", + srcs = glob( + ["**/*.ts"], + exclude = [ + "**/*.spec.ts", + "harness/**", + ], + ), + assets = [ + # TODO: include scss assets + ] + glob(["**/*.html"]), + module_name = "@angular/material-experimental/mdc-button-toggle", + deps = [ + "//src/material/core", + ], +) + +ts_library( + name = "harness", + srcs = glob( + ["harness/**/*.ts"], + exclude = ["**/*.spec.ts"], + ), + deps = [ + "//src/cdk-experimental/testing", + "//src/cdk/coercion", + ], +) + +sass_library( + name = "mdc_button_toggle_scss_lib", + srcs = glob(["**/_*.scss"]), + deps = [ + "//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib", + "//src/material-experimental/mdc-helpers:mdc_scss_deps_lib", + "//src/material/core:core_scss_lib", + ], +) + +sass_binary( + name = "button_toggle_scss", + src = "button-toggle.scss", + include_paths = [ + "external/npm/node_modules", + ], + deps = [ + "//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib", + "//src/material-experimental/mdc-helpers:mdc_scss_deps_lib", + "//src/material/core:all_themes", + ], +) + +ng_test_library( + name = "button_toggle_tests_lib", + srcs = [ + "harness/button-toggle-harness.spec.ts", + ], + deps = [ + ":harness", + ":mdc-button-toggle", + "//src/cdk-experimental/testing", + "//src/cdk-experimental/testing/testbed", + "//src/cdk/platform", + "//src/cdk/testing", + "//src/material/button-toggle", + "@npm//@angular/platform-browser", + ], +) + +ng_web_test_suite( + name = "unit_tests", + deps = [ + ":button_toggle_tests_lib", + "//src/material-experimental:mdc_require_config.js", + ], +) + +ng_e2e_test_library( + name = "e2e_test_sources", + srcs = glob(["**/*.e2e.spec.ts"]), + deps = [ + "//src/cdk/testing/e2e", + ], +) + +e2e_test_suite( + name = "e2e_tests", + deps = [ + ":e2e_test_sources", + "//src/cdk/testing/e2e", + ], +) diff --git a/src/material-experimental/mdc-button-toggle/README.md b/src/material-experimental/mdc-button-toggle/README.md new file mode 100644 index 000000000000..cf873b4c072b --- /dev/null +++ b/src/material-experimental/mdc-button-toggle/README.md @@ -0,0 +1 @@ + diff --git a/src/material-experimental/mdc-button-toggle/_mdc-button-toggle.scss b/src/material-experimental/mdc-button-toggle/_mdc-button-toggle.scss new file mode 100644 index 000000000000..0f411ed1fb85 --- /dev/null +++ b/src/material-experimental/mdc-button-toggle/_mdc-button-toggle.scss @@ -0,0 +1,13 @@ +@import '../mdc-helpers/mdc-helpers'; + +@mixin mat-button-toggle-theme-mdc($theme) { + @include mat-using-mdc-theme($theme) { + // TODO: implement MDC-based button toggle. + } +} + +@mixin mat-buttont-toggle-typography-mdc($config) { + @include mat-using-mdc-typography($config) { + // TODO: implement MDC-based button toggle. + } +} diff --git a/src/material-experimental/mdc-button-toggle/button-toggle.e2e.spec.ts b/src/material-experimental/mdc-button-toggle/button-toggle.e2e.spec.ts new file mode 100644 index 000000000000..c59ab9a48b79 --- /dev/null +++ b/src/material-experimental/mdc-button-toggle/button-toggle.e2e.spec.ts @@ -0,0 +1 @@ +// TODO: copy tests from existing mat-button-toggle, update as necessary to fix. diff --git a/src/material-experimental/mdc-button-toggle/button-toggle.scss b/src/material-experimental/mdc-button-toggle/button-toggle.scss new file mode 100644 index 000000000000..e028f7b95f71 --- /dev/null +++ b/src/material-experimental/mdc-button-toggle/button-toggle.scss @@ -0,0 +1 @@ +// TODO: implement MDC-based button toggle diff --git a/src/material-experimental/mdc-button-toggle/harness/button-toggle-harness-filters.ts b/src/material-experimental/mdc-button-toggle/harness/button-toggle-harness-filters.ts new file mode 100644 index 000000000000..05c23907d342 --- /dev/null +++ b/src/material-experimental/mdc-button-toggle/harness/button-toggle-harness-filters.ts @@ -0,0 +1,12 @@ +/** + * @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 ButtonToggleHarnessFilters = { + label?: string | RegExp, + name?: string, +}; diff --git a/src/material-experimental/mdc-button-toggle/harness/button-toggle-harness.spec.ts b/src/material-experimental/mdc-button-toggle/harness/button-toggle-harness.spec.ts new file mode 100644 index 000000000000..d3c4f312b6f8 --- /dev/null +++ b/src/material-experimental/mdc-button-toggle/harness/button-toggle-harness.spec.ts @@ -0,0 +1,187 @@ +import {HarnessLoader} from '@angular/cdk-experimental/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk-experimental/testing/testbed'; +import {Component} from '@angular/core'; +import {ComponentFixture, TestBed, inject} from '@angular/core/testing'; +import {MatButtonToggleModule} from '@angular/material/button-toggle'; +import {Platform, PlatformModule} from '@angular/cdk/platform'; +import {MatButtonToggleModule as MatMdcButtonToggleModule} from '../index'; +import {MatButtonToggleHarness} from './button-toggle-harness'; +import {MatButtonToggleHarness as MatMdcButtonToggleHarness} from './mdc-button-toggle-harness'; + +let fixture: ComponentFixture; +let loader: HarnessLoader; +let harness: typeof MatButtonToggleHarness; + +describe('MatButtonToggleHarness', () => { + describe('non-MDC-based', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MatButtonToggleModule, PlatformModule], + declarations: [ButtonToggleHarnessTest], + }).compileComponents(); + + fixture = TestBed.createComponent(ButtonToggleHarnessTest); + fixture.detectChanges(); + loader = TestbedHarnessEnvironment.loader(fixture); + harness = MatButtonToggleHarness; + }); + + runTests(); + }); + + describe('MDC-based', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MatMdcButtonToggleModule, PlatformModule], + declarations: [ButtonToggleHarnessTest], + }).compileComponents(); + + fixture = TestBed.createComponent(ButtonToggleHarnessTest); + fixture.detectChanges(); + loader = TestbedHarnessEnvironment.loader(fixture); + // Public APIs are the same as MatButtonToggleHarness, but cast is + // necessary because of different private fields. + harness = MatMdcButtonToggleHarness as any; + }); + + // TODO: enable when the MDC-based harness is done. + // runTests(); + }); +}); + +/** Shared tests to run on both the original and MDC-based toggles. */ +function runTests() { + let platform: Platform; + + beforeEach(inject([Platform], (p: Platform) => { + platform = p; + })); + + it('should load all button toggle harnesses', async () => { + const toggles = await loader.getAllHarnesses(harness); + expect(toggles.length).toBe(2); + }); + + it('should load a button toggle with exact label', async () => { + const toggles = await loader.getAllHarnesses(harness.with({label: 'First'})); + expect(toggles.length).toBe(1); + expect(await toggles[0].getText()).toBe('First'); + }); + + it('should load a button toggle with regex label match', async () => { + const toggles = await loader.getAllHarnesses(harness.with({label: /^s/i})); + expect(toggles.length).toBe(1); + expect(await toggles[0].getText()).toBe('Second'); + }); + + it('should get checked state', async () => { + const [checkedToggle, uncheckedToggle] = await loader.getAllHarnesses(harness); + expect(await checkedToggle.isChecked()).toBe(true); + expect(await uncheckedToggle.isChecked()).toBe(false); + }); + + it('should get disabled state', async () => { + const [enabledToggle, disabledToggle] = await loader.getAllHarnesses(harness); + expect(await enabledToggle.isDisabled()).toBe(false); + expect(await disabledToggle.isDisabled()).toBe(true); + }); + + it('should get name', async () => { + const toggle = await loader.getHarness(harness.with({label: 'First'})); + expect(await toggle.getName()).toBe('first-name'); + }); + + it('should get aria-label', async () => { + const toggle = await loader.getHarness(harness.with({label: 'First'})); + expect(await toggle.getAriaLabel()).toBe('First toggle'); + }); + + it('should get aria-labelledby', async () => { + const toggle = await loader.getHarness(harness.with({label: 'Second'})); + expect(await toggle.getAriaLabelledby()).toBe('second-label'); + }); + + it('should get label text', async () => { + const [firstToggle, secondToggle] = await loader.getAllHarnesses(harness); + expect(await firstToggle.getText()).toBe('First'); + expect(await secondToggle.getText()).toBe('Second'); + }); + + it('should focus button', async () => { + const toggle = await loader.getHarness(harness.with({label: 'First'})); + expect(getActiveElementTagName()).not.toBe('button'); + await toggle.focus(); + expect(getActiveElementTagName()).toBe('button'); + }); + + it('should blur button', async () => { + const toggle = await loader.getHarness(harness.with({label: 'First'})); + await toggle.focus(); + expect(getActiveElementTagName()).toBe('button'); + await toggle.blur(); + expect(getActiveElementTagName()).not.toBe('button'); + }); + + it('should toggle', async () => { + fixture.componentInstance.disabled = false; + const [checkedToggle, uncheckedToggle] = await loader.getAllHarnesses(harness); + await checkedToggle.toggle(); + await uncheckedToggle.toggle(); + expect(await checkedToggle.isChecked()).toBe(false); + expect(await uncheckedToggle.isChecked()).toBe(true); + }); + + it('should check toggle', async () => { + fixture.componentInstance.disabled = false; + const [checkedToggle, uncheckedToggle] = await loader.getAllHarnesses(harness); + await checkedToggle.check(); + await uncheckedToggle.check(); + expect(await checkedToggle.isChecked()).toBe(true); + expect(await uncheckedToggle.isChecked()).toBe(true); + }); + + it('should uncheck toggle', async () => { + fixture.componentInstance.disabled = false; + const [checkedToggle, uncheckedToggle] = await loader.getAllHarnesses(harness); + await checkedToggle.uncheck(); + await uncheckedToggle.uncheck(); + expect(await checkedToggle.isChecked()).toBe(false); + expect(await uncheckedToggle.isChecked()).toBe(false); + }); + + it('should not change disabled toggle', async () => { + // Older versions of Edge have a bug where `disabled` buttons are still clickable if + // they contain child elements. We skip this check on Edge. + // See https://stackoverflow.com/questions/32377026/disabled-button-is-clickable-on-edge-browser + if (platform.EDGE) { + return; + } + + const disabledToggle = await loader.getHarness(harness.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: ` + First + Second + Second toggle + ` +}) +class ButtonToggleHarnessTest { + disabled = true; +} + diff --git a/src/material-experimental/mdc-button-toggle/harness/button-toggle-harness.ts b/src/material-experimental/mdc-button-toggle/harness/button-toggle-harness.ts new file mode 100644 index 000000000000..49ede91f3d5c --- /dev/null +++ b/src/material-experimental/mdc-button-toggle/harness/button-toggle-harness.ts @@ -0,0 +1,107 @@ +/** + * @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 {ButtonToggleHarnessFilters} from './button-toggle-harness-filters'; + +/** + * Harness for interacting with a mat-button-toggle in tests. + * @dynamic + */ +export class MatButtonToggleHarness extends ComponentHarness { + static hostSelector = 'mat-button-toggle'; + + /** + * Gets a `HarnessPredicate` that can be used to search for a toggle with specific attributes. + * @param options Options for narrowing the search: + * @return a `HarnessPredicate` configured with the given options. + */ + static with(options: ButtonToggleHarnessFilters = {}): HarnessPredicate { + return new HarnessPredicate(MatButtonToggleHarness) + .addOption('name', options.name, + async (harness, name) => (await harness.getName()) === name) + .addOption('label', options.label, + (harness, label) => HarnessPredicate.stringMatches(harness.getText(), label)); + } + + private _label = this.locatorFor('.mat-button-toggle-label-content'); + private _button = this.locatorFor('.mat-button-toggle-button'); + + /** Gets a boolean promise indicating if the button toggle is checked. */ + async isChecked(): Promise { + const checked = (await this._button()).getAttribute('aria-pressed'); + return coerceBooleanProperty(await checked); + } + + /** Gets a boolean promise indicating if the button toggle is disabled. */ + async isDisabled(): Promise { + const disabled = (await this._button()).getAttribute('disabled'); + return coerceBooleanProperty(await disabled); + } + + /** Gets a promise for the button toggle's name. */ + async getName(): Promise { + return (await this._button()).getAttribute('name'); + } + + /** Gets a promise for the button toggle's aria-label. */ + async getAriaLabel(): Promise { + return (await this._button()).getAttribute('aria-label'); + } + + /** Gets a promise for the button toggles's aria-labelledby. */ + async getAriaLabelledby(): Promise { + return (await this._button()).getAttribute('aria-labelledby'); + } + + /** Gets a promise for the button toggle's text. */ + async getText(): Promise { + return (await this._label()).text(); + } + + /** Focuses the toggle and returns a void promise that indicates when the action is complete. */ + async focus(): Promise { + return (await this._button()).focus(); + } + + /** Blurs the toggle and returns a void promise that indicates when the action is complete. */ + async blur(): Promise { + return (await this._button()).blur(); + } + + /** + * Toggle the checked state of the buttons toggle and returns + * a void promise that indicates when the action is complete. + */ + async toggle(): Promise { + return (await this._button()).click(); + } + + /** + * Puts the button toggle in a checked state by toggling it if it's currently unchecked, or doing + * nothing if it is already checked. Returns a void promise that indicates when the action is + * complete. + */ + async check(): Promise { + if (!(await this.isChecked())) { + await this.toggle(); + } + } + + /** + * Puts the button toggle in an unchecked state by toggling it if it is currently checked, or + * doing nothing if it's already unchecked. Returns a void promise that indicates when the action + * is complete. + */ + async uncheck(): Promise { + if (await this.isChecked()) { + await this.toggle(); + } + } +} diff --git a/src/material-experimental/mdc-button-toggle/harness/mdc-button-toggle-harness.ts b/src/material-experimental/mdc-button-toggle/harness/mdc-button-toggle-harness.ts new file mode 100644 index 000000000000..aa57a2569c47 --- /dev/null +++ b/src/material-experimental/mdc-button-toggle/harness/mdc-button-toggle-harness.ts @@ -0,0 +1,18 @@ +/** + * @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} from '@angular/cdk-experimental/testing'; + + +/** + * Harness for interacting with a MDC-based mat-button-toggle in tests. + * @dynamic + */ +export class MatButtonToggleHarness extends ComponentHarness { + // TODO: implement once MDC button toggle is done. +} diff --git a/src/material-experimental/mdc-button-toggle/index.ts b/src/material-experimental/mdc-button-toggle/index.ts new file mode 100644 index 000000000000..676ca90f1ffa --- /dev/null +++ b/src/material-experimental/mdc-button-toggle/index.ts @@ -0,0 +1,9 @@ +/** + * @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 * from './public-api'; diff --git a/src/material-experimental/mdc-button-toggle/module.ts b/src/material-experimental/mdc-button-toggle/module.ts new file mode 100644 index 000000000000..37f7105ef99b --- /dev/null +++ b/src/material-experimental/mdc-button-toggle/module.ts @@ -0,0 +1,18 @@ +/** + * @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 {CommonModule} from '@angular/common'; +import {NgModule} from '@angular/core'; +import {MatCommonModule} from '@angular/material/core'; + +@NgModule({ + imports: [MatCommonModule, CommonModule], + exports: [MatCommonModule], +}) +export class MatButtonToggleModule { +} diff --git a/src/material-experimental/mdc-button-toggle/public-api.ts b/src/material-experimental/mdc-button-toggle/public-api.ts new file mode 100644 index 000000000000..508adc834fb3 --- /dev/null +++ b/src/material-experimental/mdc-button-toggle/public-api.ts @@ -0,0 +1,9 @@ +/** + * @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 * from './module'; diff --git a/src/material-experimental/mdc-button-toggle/tsconfig-build.json b/src/material-experimental/mdc-button-toggle/tsconfig-build.json new file mode 100644 index 000000000000..0f2b43e24b5f --- /dev/null +++ b/src/material-experimental/mdc-button-toggle/tsconfig-build.json @@ -0,0 +1,15 @@ +{ + "extends": "../tsconfig-build", + "files": [ + "public-api.ts", + "../typings.d.ts" + ], + "angularCompilerOptions": { + "annotateForClosureCompiler": true, + "strictMetadataEmit": true, + "flatModuleOutFile": "index.js", + "flatModuleId": "@angular/material-experimental/mdc-button-toggle", + "skipTemplateCodegen": true, + "fullTemplateTypeCheck": true + } +}