Skip to content

refactor(material-experimental/mdc-autocomplete): de-duplicate test harness logic #21476

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
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ ts_library(
),
module_name = "@angular/material-experimental/mdc-autocomplete/testing",
deps = [
"//src/cdk/coercion",
"//src/cdk/testing",
"//src/material-experimental/mdc-core/testing",
"//src/material/autocomplete/testing",
],
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,24 @@
* found in the LICENSE file at https://angular.io/license
*/

import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing';
import {HarnessPredicate} from '@angular/cdk/testing';
import {
MatOptgroupHarness,
MatOptionHarness,
OptgroupHarnessFilters,
OptionHarnessFilters
} from '@angular/material-experimental/mdc-core/testing';
import {_MatAutocompleteHarnessBase} from '@angular/material/autocomplete/testing';
import {AutocompleteHarnessFilters} from './autocomplete-harness-filters';

/** Harness for interacting with an MDC-based mat-autocomplete in tests. */
export class MatAutocompleteHarness extends ComponentHarness {
private _documentRootLocator = this.documentRootLocatorFactory();
export class MatAutocompleteHarness extends _MatAutocompleteHarnessBase<
typeof MatOptionHarness, MatOptionHarness, OptionHarnessFilters,
typeof MatOptgroupHarness, MatOptgroupHarness, OptgroupHarnessFilters
> {
protected _prefix = 'mat-mdc';
protected _optionClass = MatOptionHarness;
protected _optionGroupClass = MatOptgroupHarness;

/** The selector for the host element of a `MatAutocomplete` instance. */
static hostSelector = '.mat-mdc-autocomplete-trigger';
Expand All @@ -34,81 +39,4 @@ export class MatAutocompleteHarness extends ComponentHarness {
.addOption('value', options.value,
(harness, value) => HarnessPredicate.stringMatches(harness.getValue(), value));
}

/** Gets the value of the autocomplete input. */
async getValue(): Promise<string> {
return (await this.host()).getProperty('value');
}

/** Whether the autocomplete input is disabled. */
async isDisabled(): Promise<boolean> {
const disabled = (await this.host()).getAttribute('disabled');
return coerceBooleanProperty(await disabled);
}

/** Focuses the autocomplete input. */
async focus(): Promise<void> {
return (await this.host()).focus();
}

/** Blurs the autocomplete input. */
async blur(): Promise<void> {
return (await this.host()).blur();
}

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

/** Enters text into the autocomplete. */
async enterText(value: string): Promise<void> {
return (await this.host()).sendKeys(value);
}

/** Gets the options inside the autocomplete panel. */
async getOptions(filters: Omit<OptionHarnessFilters, 'ancestor'> = {}):
Promise<MatOptionHarness[]> {
return this._documentRootLocator.locatorForAll(MatOptionHarness.with({
...filters,
ancestor: await this._getPanelSelector()
}))();
}

/** Gets the option groups inside the autocomplete panel. */
async getOptionGroups(filters: Omit<OptgroupHarnessFilters, 'ancestor'> = {}):
Promise<MatOptgroupHarness[]> {
return this._documentRootLocator.locatorForAll(MatOptgroupHarness.with({
...filters,
ancestor: await this._getPanelSelector()
}))();
}

/** Selects the first option matching the given filters. */
async selectOption(filters: OptionHarnessFilters): Promise<void> {
await this.focus(); // Focus the input to make sure the autocomplete panel is shown.
const options = await this.getOptions(filters);
if (!options.length) {
throw Error(`Could not find a mat-option matching ${JSON.stringify(filters)}`);
}
await options[0].click();
}

/** Whether the autocomplete is open. */
async isOpen(): Promise<boolean> {
const panel = await this._getPanel();
return !!panel && await panel.hasClass('mat-mdc-autocomplete-visible');
}

/** Gets the panel associated with this autocomplete trigger. */
private async _getPanel() {
// Technically this is static, but it needs to be in a
// function, because the autocomplete's panel ID can changed.
return this._documentRootLocator.locatorForOptional(await this._getPanelSelector())();
}

/** Gets the selector that can be used to find the autocomplete trigger's panel. */
private async _getPanelSelector(): Promise<string> {
return `#${(await (await this.host()).getAttribute('aria-owns'))}`;
}
}
84 changes: 54 additions & 30 deletions src/material/autocomplete/testing/autocomplete-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
*/

import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing';
import {
BaseHarnessFilters,
ComponentHarness,
ComponentHarnessConstructor,
HarnessPredicate,
} from '@angular/cdk/testing';
import {
MatOptgroupHarness,
MatOptionHarness,
Expand All @@ -16,24 +21,20 @@ import {
} from '@angular/material/core/testing';
import {AutocompleteHarnessFilters} from './autocomplete-harness-filters';

/** Harness for interacting with a standard mat-autocomplete in tests. */
export class MatAutocompleteHarness extends ComponentHarness {
export abstract class _MatAutocompleteHarnessBase<
OptionType extends (ComponentHarnessConstructor<Option> & {
with: (options?: OptionFilters) => HarnessPredicate<Option>}),
Option extends ComponentHarness & {click(): Promise<void>},
OptionFilters extends BaseHarnessFilters,
OptionGroupType extends (ComponentHarnessConstructor<OptionGroup> & {
with: (options?: OptionGroupFilters) => HarnessPredicate<OptionGroup>}),
OptionGroup extends ComponentHarness,
OptionGroupFilters extends BaseHarnessFilters
> extends ComponentHarness {
private _documentRootLocator = this.documentRootLocatorFactory();

/** The selector for the host element of a `MatAutocomplete` instance. */
static hostSelector = '.mat-autocomplete-trigger';

/**
* Gets a `HarnessPredicate` that can be used to search for a `MatAutocompleteHarness` that meets
* certain criteria.
* @param options Options for filtering which autocomplete instances are considered a match.
* @return a `HarnessPredicate` configured with the given options.
*/
static with(options: AutocompleteHarnessFilters = {}): HarnessPredicate<MatAutocompleteHarness> {
return new HarnessPredicate(MatAutocompleteHarness, options)
.addOption('value', options.value,
(harness, value) => HarnessPredicate.stringMatches(harness.getValue(), value));
}
protected abstract _prefix: string;
protected abstract _optionClass: OptionType;
protected abstract _optionGroupClass: OptionGroupType;

/** Gets the value of the autocomplete input. */
async getValue(): Promise<string> {
Expand Down Expand Up @@ -67,25 +68,23 @@ export class MatAutocompleteHarness extends ComponentHarness {
}

/** Gets the options inside the autocomplete panel. */
async getOptions(filters: Omit<OptionHarnessFilters, 'ancestor'> = {}):
Promise<MatOptionHarness[]> {
return this._documentRootLocator.locatorForAll(MatOptionHarness.with({
...filters,
async getOptions(filters?: Omit<OptionFilters, 'ancestor'>): Promise<Option[]> {
return this._documentRootLocator.locatorForAll(this._optionClass.with({
...(filters || {}),
ancestor: await this._getPanelSelector()
}))();
} as OptionFilters))();
}

/** Gets the option groups inside the autocomplete panel. */
async getOptionGroups(filters: Omit<OptgroupHarnessFilters, 'ancestor'> = {}):
Promise<MatOptgroupHarness[]> {
return this._documentRootLocator.locatorForAll(MatOptgroupHarness.with({
...filters,
async getOptionGroups(filters?: Omit<OptionGroupFilters, 'ancestor'>): Promise<OptionGroup[]> {
return this._documentRootLocator.locatorForAll(this._optionGroupClass.with({
...(filters || {}),
ancestor: await this._getPanelSelector()
}))();
} as OptionGroupFilters))();
}

/** Selects the first option matching the given filters. */
async selectOption(filters: OptionHarnessFilters): Promise<void> {
async selectOption(filters: OptionFilters): Promise<void> {
await this.focus(); // Focus the input to make sure the autocomplete panel is shown.
const options = await this.getOptions(filters);
if (!options.length) {
Expand All @@ -97,7 +96,7 @@ export class MatAutocompleteHarness extends ComponentHarness {
/** Whether the autocomplete is open. */
async isOpen(): Promise<boolean> {
const panel = await this._getPanel();
return !!panel && await panel.hasClass('mat-autocomplete-visible');
return !!panel && await panel.hasClass(`${this._prefix}-autocomplete-visible`);
}

/** Gets the panel associated with this autocomplete trigger. */
Expand All @@ -112,3 +111,28 @@ export class MatAutocompleteHarness extends ComponentHarness {
return `#${(await (await this.host()).getAttribute('aria-owns'))}`;
}
}

/** Harness for interacting with a standard mat-autocomplete in tests. */
export class MatAutocompleteHarness extends _MatAutocompleteHarnessBase<
typeof MatOptionHarness, MatOptionHarness, OptionHarnessFilters,
typeof MatOptgroupHarness, MatOptgroupHarness, OptgroupHarnessFilters
> {
protected _prefix = 'mat';
protected _optionClass = MatOptionHarness;
protected _optionGroupClass = MatOptgroupHarness;

/** The selector for the host element of a `MatAutocomplete` instance. */
static hostSelector = '.mat-autocomplete-trigger';

/**
* Gets a `HarnessPredicate` that can be used to search for a `MatAutocompleteHarness` that meets
* certain criteria.
* @param options Options for filtering which autocomplete instances are considered a match.
* @return a `HarnessPredicate` configured with the given options.
*/
static with(options: AutocompleteHarnessFilters = {}): HarnessPredicate<MatAutocompleteHarness> {
return new HarnessPredicate(MatAutocompleteHarness, options)
.addOption('value', options.value,
(harness, value) => HarnessPredicate.stringMatches(harness.getValue(), value));
}
}
2 changes: 1 addition & 1 deletion src/material/autocomplete/testing/shared.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {MatAutocompleteHarness} from '@angular/material/autocomplete/testing';

/**
* Function that can be used to run the shared autocomplete harness tests for either the non-MDC or
* MDC based checkbox harness.
* MDC based autocomplete harness.
*/
export function runHarnessTests(
autocompleteModule: typeof MatAutocompleteModule,
Expand Down
31 changes: 23 additions & 8 deletions tools/public_api_guard/material/autocomplete/testing.d.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
export interface AutocompleteHarnessFilters extends BaseHarnessFilters {
value?: string | RegExp;
}

export declare class MatAutocompleteHarness extends ComponentHarness {
export declare abstract class _MatAutocompleteHarnessBase<OptionType extends (ComponentHarnessConstructor<Option> & {
with: (options?: OptionFilters) => HarnessPredicate<Option>;
}), Option extends ComponentHarness & {
click(): Promise<void>;
}, OptionFilters extends BaseHarnessFilters, OptionGroupType extends (ComponentHarnessConstructor<OptionGroup> & {
with: (options?: OptionGroupFilters) => HarnessPredicate<OptionGroup>;
}), OptionGroup extends ComponentHarness, OptionGroupFilters extends BaseHarnessFilters> extends ComponentHarness {
protected abstract _optionClass: OptionType;
protected abstract _optionGroupClass: OptionGroupType;
protected abstract _prefix: string;
blur(): Promise<void>;
enterText(value: string): Promise<void>;
focus(): Promise<void>;
getOptionGroups(filters?: Omit<OptgroupHarnessFilters, 'ancestor'>): Promise<MatOptgroupHarness[]>;
getOptions(filters?: Omit<OptionHarnessFilters, 'ancestor'>): Promise<MatOptionHarness[]>;
getOptionGroups(filters?: Omit<OptionGroupFilters, 'ancestor'>): Promise<OptionGroup[]>;
getOptions(filters?: Omit<OptionFilters, 'ancestor'>): Promise<Option[]>;
getValue(): Promise<string>;
isDisabled(): Promise<boolean>;
isFocused(): Promise<boolean>;
isOpen(): Promise<boolean>;
selectOption(filters: OptionHarnessFilters): Promise<void>;
selectOption(filters: OptionFilters): Promise<void>;
}

export interface AutocompleteHarnessFilters extends BaseHarnessFilters {
value?: string | RegExp;
}

export declare class MatAutocompleteHarness extends _MatAutocompleteHarnessBase<typeof MatOptionHarness, MatOptionHarness, OptionHarnessFilters, typeof MatOptgroupHarness, MatOptgroupHarness, OptgroupHarnessFilters> {
protected _optionClass: typeof MatOptionHarness;
protected _optionGroupClass: typeof MatOptgroupHarness;
protected _prefix: string;
static hostSelector: string;
static with(options?: AutocompleteHarnessFilters): HarnessPredicate<MatAutocompleteHarness>;
}