Skip to content

refactor(material-experimental/mdc-checkbox): de-duplicate test harness logic #21506

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
Jan 25, 2021
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
4 changes: 4 additions & 0 deletions scripts/check-mdc-exports-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ export const config = {
// Private symbols that are only exported for MDC.
'_MatTableDataSource',
'_MAT_TEXT_COLUMN_TEMPLATE'
],
'mdc-checkbox/testing': [
// Private symbols that are only exported for MDC.
'_MatCheckboxHarnessBase'
]
}
};
1 change: 0 additions & 1 deletion src/material-experimental/mdc-checkbox/testing/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ ts_library(
),
module_name = "@angular/material-experimental/mdc-checkbox/testing",
deps = [
"//src/cdk/coercion",
"//src/cdk/testing",
"//src/material/checkbox/testing",
],
Expand Down
119 changes: 5 additions & 114 deletions src/material-experimental/mdc-checkbox/testing/checkbox-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/

import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {CheckboxHarnessFilters} from '@angular/material/checkbox/testing';
import {HarnessPredicate} from '@angular/cdk/testing';
import {CheckboxHarnessFilters, _MatCheckboxHarnessBase} from '@angular/material/checkbox/testing';

/** Harness for interacting with a MDC-based mat-checkbox in tests. */
export class MatCheckboxHarness extends ComponentHarness {
export class MatCheckboxHarness extends _MatCheckboxHarnessBase {
static hostSelector = '.mat-mdc-checkbox';

/**
Expand All @@ -33,120 +32,12 @@ export class MatCheckboxHarness extends ComponentHarness {
.addOption('name', options.name, async (harness, name) => await harness.getName() === name);
}

private _label = this.locatorFor('label');
private _input = this.locatorFor('input');
protected _input = this.locatorFor('input');
protected _label = this.locatorFor('label');
private _inputContainer = this.locatorFor('.mdc-checkbox');

/** Gets a boolean promise indicating if the checkbox is checked. */
async isChecked(): Promise<boolean> {
const checked = (await this._input()).getProperty('checked');
return coerceBooleanProperty(await checked);
}

/** Gets a boolean promise indicating if the checkbox is in an indeterminate state. */
async isIndeterminate(): Promise<boolean> {
const indeterminate = (await this._input()).getProperty('indeterminate');
return coerceBooleanProperty(await indeterminate);
}

/** Gets a boolean promise indicating if the checkbox is disabled. */
async isDisabled(): Promise<boolean> {
const disabled = (await this._input()).getAttribute('disabled');
return coerceBooleanProperty(await disabled);
}

/** Gets a boolean promise indicating if the checkbox is required. */
async isRequired(): Promise<boolean> {
const required = (await this._input()).getAttribute('required');
return coerceBooleanProperty(await required);
}

/** Gets a boolean promise indicating if the checkbox is valid. */
async isValid(): Promise<boolean> {
const invalid = (await this.host()).hasClass('ng-invalid');
return !(await invalid);
}

/** Gets a promise for the checkbox's name. */
async getName(): Promise<string|null> {
return (await this._input()).getAttribute('name');
}

/** Gets a promise for the checkbox's value. */
async getValue(): Promise<string|null> {
return (await this._input()).getProperty('value');
}

/** Gets a promise for the checkbox's aria-label. */
async getAriaLabel(): Promise<string|null> {
return (await this._input()).getAttribute('aria-label');
}

/** Gets a promise for the checkbox's aria-labelledby. */
async getAriaLabelledby(): Promise<string|null> {
return (await this._input()).getAttribute('aria-labelledby');
}

/** Gets a promise for the checkbox's label text. */
async getLabelText(): Promise<string> {
return (await this._label()).text();
}

/** Focuses the checkbox and returns a void promise that indicates when the action is complete. */
async focus(): Promise<void> {
return (await this._input()).focus();
}

/** Blurs the checkbox and returns a void promise that indicates when the action is complete. */
async blur(): Promise<void> {
return (await this._input()).blur();
}

/** Whether the checkbox is focused. */
async isFocused(): Promise<boolean> {
return (await this._input()).isFocused();
}

/**
* Toggle the checked state of the checkbox and returns a void promise that indicates when the
* action is complete.
*
* Note: This attempts to toggle the checkbox as a user would, by clicking it. Therefore if you
* are using `MAT_CHECKBOX_DEFAULT_OPTIONS` to change the behavior on click, calling this method
* might not have the expected result.
*/
async toggle(): Promise<void> {
const elToClick = await this.isDisabled() ? this._inputContainer() : this._input();
return (await elToClick).click();
}

/**
* Puts the checkbox 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 checkbox as a user would, by clicking it. Therefore if you
* are using `MAT_CHECKBOX_DEFAULT_OPTIONS` to change the behavior on click, calling this method
* might not have the expected result.
*/
async check(): Promise<void> {
if (!(await this.isChecked())) {
await this.toggle();
}
}

/**
* Puts the checkbox 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 checkbox as a user would, by clicking it. Therefore if you
* are using `MAT_CHECKBOX_DEFAULT_OPTIONS` to change the behavior on click, calling this method
* might not have the expected result.
*/
async uncheck(): Promise<void> {
if (await this.isChecked()) {
await this.toggle();
}
}
}
70 changes: 41 additions & 29 deletions src/material/checkbox/testing/checkbox-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,17 @@
*/

import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing';
import {
AsyncFactoryFn,
ComponentHarness,
HarnessPredicate,
TestElement,
} from '@angular/cdk/testing';
import {CheckboxHarnessFilters} from './checkbox-harness-filters';

/** Harness for interacting with a standard mat-checkbox in tests. */
export class MatCheckboxHarness extends ComponentHarness {
/** The selector for the host element of a `MatCheckbox` instance. */
static hostSelector = '.mat-checkbox';

/**
* Gets a `HarnessPredicate` that can be used to search for a `MatCheckboxHarness` that meets
* certain criteria.
* @param options Options for filtering which checkbox instances are considered a match.
* @return a `HarnessPredicate` configured with the given options.
*/
static with(options: CheckboxHarnessFilters = {}): HarnessPredicate<MatCheckboxHarness> {
return new HarnessPredicate(MatCheckboxHarness, options)
.addOption(
'label', options.label,
(harness, label) => HarnessPredicate.stringMatches(harness.getLabelText(), label))
// We want to provide a filter option for "name" because the name of the checkbox is
// only set on the underlying input. This means that it's not possible for developers
// to retrieve the harness of a specific checkbox with name through a CSS selector.
.addOption('name', options.name, async (harness, name) => await harness.getName() === name);
}

private _label = this.locatorFor('.mat-checkbox-label');
private _input = this.locatorFor('input');
private _inputContainer = this.locatorFor('.mat-checkbox-inner-container');
export abstract class _MatCheckboxHarnessBase extends ComponentHarness {
protected abstract _input: AsyncFactoryFn<TestElement>;
protected abstract _label: AsyncFactoryFn<TestElement>;

/** Whether the checkbox is checked. */
async isChecked(): Promise<boolean> {
Expand Down Expand Up @@ -113,9 +96,7 @@ export class MatCheckboxHarness extends ComponentHarness {
* are using `MAT_CHECKBOX_DEFAULT_OPTIONS` to change the behavior on click, calling this method
* might not have the expected result.
*/
async toggle(): Promise<void> {
return (await this._inputContainer()).click();
}
abstract toggle(): Promise<void>;

/**
* Puts the checkbox in a checked state by toggling it if it is currently unchecked, or doing
Expand Down Expand Up @@ -145,3 +126,34 @@ export class MatCheckboxHarness extends ComponentHarness {
}
}
}

/** Harness for interacting with a standard mat-checkbox in tests. */
export class MatCheckboxHarness extends _MatCheckboxHarnessBase {
/** The selector for the host element of a `MatCheckbox` instance. */
static hostSelector = '.mat-checkbox';

/**
* Gets a `HarnessPredicate` that can be used to search for a `MatCheckboxHarness` that meets
* certain criteria.
* @param options Options for filtering which checkbox instances are considered a match.
* @return a `HarnessPredicate` configured with the given options.
*/
static with(options: CheckboxHarnessFilters = {}): HarnessPredicate<MatCheckboxHarness> {
return new HarnessPredicate(MatCheckboxHarness, options)
.addOption(
'label', options.label,
(harness, label) => HarnessPredicate.stringMatches(harness.getLabelText(), label))
// We want to provide a filter option for "name" because the name of the checkbox is
// only set on the underlying input. This means that it's not possible for developers
// to retrieve the harness of a specific checkbox with name through a CSS selector.
.addOption('name', options.name, async (harness, name) => await harness.getName() === name);
}

protected _input = this.locatorFor('input');
protected _label = this.locatorFor('.mat-checkbox-label');
private _inputContainer = this.locatorFor('.mat-checkbox-inner-container');

async toggle(): Promise<void> {
return (await this._inputContainer()).click();
}
}
22 changes: 15 additions & 7 deletions tools/public_api_guard/material/checkbox/testing.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
export interface CheckboxHarnessFilters extends BaseHarnessFilters {
label?: string | RegExp;
name?: string;
}

export declare class MatCheckboxHarness extends ComponentHarness {
export declare abstract class _MatCheckboxHarnessBase extends ComponentHarness {
protected abstract _input: AsyncFactoryFn<TestElement>;
protected abstract _label: AsyncFactoryFn<TestElement>;
blur(): Promise<void>;
check(): Promise<void>;
focus(): Promise<void>;
Expand All @@ -18,8 +15,19 @@ export declare class MatCheckboxHarness extends ComponentHarness {
isIndeterminate(): Promise<boolean>;
isRequired(): Promise<boolean>;
isValid(): Promise<boolean>;
toggle(): Promise<void>;
abstract toggle(): Promise<void>;
uncheck(): Promise<void>;
}

export interface CheckboxHarnessFilters extends BaseHarnessFilters {
label?: string | RegExp;
name?: string;
}

export declare class MatCheckboxHarness extends _MatCheckboxHarnessBase {
protected _input: AsyncFactoryFn<TestElement>;
protected _label: AsyncFactoryFn<TestElement>;
toggle(): Promise<void>;
static hostSelector: string;
static with(options?: CheckboxHarnessFilters): HarnessPredicate<MatCheckboxHarness>;
}