Skip to content

feat(material/autocomplete/testing): polish harness API #17350

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 5 commits into from
Oct 14, 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
1 change: 1 addition & 0 deletions src/material/autocomplete/testing/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ ng_test_library(
deps = [
":testing",
"//src/cdk/overlay",
"//src/cdk/private/testing",
"//src/cdk/testing",
"//src/cdk/testing/testbed",
"//src/material/autocomplete",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@

import {BaseHarnessFilters} from '@angular/cdk/testing';

export interface AutocompleteHarnessFilters extends BaseHarnessFilters {}
export interface AutocompleteHarnessFilters extends BaseHarnessFilters {
value?: string | RegExp;
}
55 changes: 29 additions & 26 deletions src/material/autocomplete/testing/autocomplete-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
* found in the LICENSE file at https://angular.io/license
*/

import {ComponentHarness, HarnessPredicate, TestElement} from '@angular/cdk/testing';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing';
import {AutocompleteHarnessFilters} from './autocomplete-harness-filters';
import {MatAutocompleteOptionHarness, MatAutocompleteOptionGroupHarness} from './option-harness';
import {
MatAutocompleteOptionGroupHarness,
MatAutocompleteOptionHarness,
OptionGroupHarnessFilters,
OptionHarnessFilters
} from './option-harness';

/** Selector for the autocomplete panel. */
const PANEL_SELECTOR = '.mat-autocomplete-panel';
Expand All @@ -20,10 +25,7 @@ const PANEL_SELECTOR = '.mat-autocomplete-panel';
*/
export class MatAutocompleteHarness extends ComponentHarness {
private _documentRootLocator = this.documentRootLocatorFactory();
private _panel = this._documentRootLocator.locatorFor(PANEL_SELECTOR);
private _optionalPanel = this._documentRootLocator.locatorForOptional(PANEL_SELECTOR);
private _options = this._documentRootLocator.locatorForAll(MatAutocompleteOptionHarness);
private _groups = this._documentRootLocator.locatorForAll(MatAutocompleteOptionGroupHarness);

static hostSelector = '.mat-autocomplete-trigger';

Expand All @@ -35,11 +37,14 @@ export class MatAutocompleteHarness extends ComponentHarness {
* @return a `HarnessPredicate` configured with the given options.
*/
static with(options: AutocompleteHarnessFilters = {}): HarnessPredicate<MatAutocompleteHarness> {
return new HarnessPredicate(MatAutocompleteHarness, options);
return new HarnessPredicate(MatAutocompleteHarness, options)
.addOption('value', options.value,
(harness, value) => HarnessPredicate.stringMatches(harness.getValue(), value));
}

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

/** Gets a boolean promise indicating if the autocomplete input is disabled. */
Expand All @@ -48,11 +53,6 @@ export class MatAutocompleteHarness extends ComponentHarness {
return coerceBooleanProperty(await disabled);
}

/** Gets a promise for the autocomplete's text. */
async getText(): Promise<string> {
return (await this.host()).getProperty('value');
}

/** Focuses the input and returns a void promise that indicates when the action is complete. */
async focus(): Promise<void> {
return (await this.host()).focus();
Expand All @@ -68,28 +68,31 @@ export class MatAutocompleteHarness extends ComponentHarness {
return (await this.host()).sendKeys(value);
}

/** Gets the autocomplete panel. */
async getPanel(): Promise<TestElement> {
return this._panel();
}

/** Gets the options inside the autocomplete panel. */
async getOptions(): Promise<MatAutocompleteOptionHarness[]> {
return this._options();
async getOptions(filters: OptionHarnessFilters = {}): Promise<MatAutocompleteOptionHarness[]> {
return this._documentRootLocator.locatorForAll(MatAutocompleteOptionHarness.with(filters))();
}

/** Gets the groups of options inside the panel. */
async getOptionGroups(): Promise<MatAutocompleteOptionGroupHarness[]> {
return this._groups();
async getOptionGroups(filters: OptionGroupHarnessFilters = {}):
Promise<MatAutocompleteOptionGroupHarness[]> {
return this._documentRootLocator.locatorForAll(
MatAutocompleteOptionGroupHarness.with(filters))();
}

/** Gets whether the autocomplete panel is visible. */
async isPanelVisible(): Promise<boolean> {
return (await this._panel()).hasClass('mat-autocomplete-visible');
/** 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].select();
}

/** Gets whether the autocomplete is open. */
async isOpen(): Promise<boolean> {
return !!(await this._optionalPanel());
const panel = await this._optionalPanel();
return !!panel && await panel.hasClass('mat-autocomplete-visible');
}
}
13 changes: 5 additions & 8 deletions src/material/autocomplete/testing/option-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import {ComponentHarness, HarnessPredicate, BaseHarnessFilters} from '@angular/c
// and expand to cover all states once we have experimental/core.

export interface OptionHarnessFilters extends BaseHarnessFilters {
text?: string;
text?: string | RegExp;
}

export interface OptionGroupHarnessFilters extends BaseHarnessFilters {
labelText?: string;
labelText?: string | RegExp;
}

/**
Expand All @@ -29,12 +29,11 @@ export class MatAutocompleteOptionHarness extends ComponentHarness {
static with(options: OptionHarnessFilters = {}) {
return new HarnessPredicate(MatAutocompleteOptionHarness, options)
.addOption('text', options.text,
async (harness, title) =>
HarnessPredicate.stringMatches(await harness.getText(), title));
(harness, text) => HarnessPredicate.stringMatches(harness.getText(), text));
}

/** Clicks the option. */
async click(): Promise<void> {
async select(): Promise<void> {
return (await this.host()).click();
}

Expand All @@ -55,13 +54,11 @@ export class MatAutocompleteOptionGroupHarness extends ComponentHarness {
static with(options: OptionGroupHarnessFilters = {}) {
return new HarnessPredicate(MatAutocompleteOptionGroupHarness, options)
.addOption('labelText', options.labelText,
async (harness, title) =>
HarnessPredicate.stringMatches(await harness.getLabelText(), title));
(harness, label) => HarnessPredicate.stringMatches(harness.getLabelText(), label));
}

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

54 changes: 38 additions & 16 deletions src/material/autocomplete/testing/shared.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {OverlayContainer} from '@angular/cdk/overlay';
import {expectAsyncError} from '@angular/cdk/private/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {Component} from '@angular/core';
Expand Down Expand Up @@ -42,9 +43,15 @@ export function runHarnessTests(
expect(inputs.length).toBe(5);
});

it('should be able to get text inside the input', async () => {
it('should load harness for autocomplete with value', async () => {
const ac = await loader.getHarness(autocompleteHarness.with({value: /Prefilled/}));
const id = await (await ac.host()).getAttribute('id');
expect(id).toBe('prefilled');
});

it('should be able to get value of the input', async () => {
const input = await loader.getHarness(autocompleteHarness.with({selector: '#prefilled'}));
expect(await input.getText()).toBe('Prefilled value');
expect(await input.getValue()).toBe('Prefilled value');
});

it('should get disabled state', async () => {
Expand All @@ -67,13 +74,7 @@ export function runHarnessTests(
it('should be able to type in an input', async () => {
const input = await loader.getHarness(autocompleteHarness.with({selector: '#plain'}));
await input.enterText('Hello there');
expect(await input.getText()).toBe('Hello there');
});

it('should be able to get the autocomplete panel', async () => {
const input = await loader.getHarness(autocompleteHarness.with({selector: '#plain'}));
await input.focus();
expect(await input.getPanel()).toBeTruthy();
expect(await input.getValue()).toBe('Hello there');
});

it('should be able to get the autocomplete panel options', async () => {
Expand All @@ -85,6 +86,15 @@ export function runHarnessTests(
expect(await options[5].getText()).toBe('New York');
});

it('should be able to get filtered options', async () => {
const input = await loader.getHarness(autocompleteHarness.with({selector: '#plain'}));
await input.focus();
const options = await input.getOptions({text: /New/});

expect(options.length).toBe(1);
expect(await options[0].getText()).toBe('New York');
});

it('should be able to get the autocomplete panel groups', async () => {
const input = await loader.getHarness(autocompleteHarness.with({selector: '#grouped'}));
await input.focus();
Expand All @@ -95,14 +105,13 @@ export function runHarnessTests(
expect(options.length).toBe(11);
});

it('should be able to get the autocomplete panel', async () => {
// Focusing without any options will render the panel, but it'll be invisible.
fixture.componentInstance.states = [];
fixture.detectChanges();

const input = await loader.getHarness(autocompleteHarness.with({selector: '#plain'}));
it('should be able to get filtered panel groups', async () => {
const input = await loader.getHarness(autocompleteHarness.with({selector: '#grouped'}));
await input.focus();
expect(await input.isPanelVisible()).toBe(false);
const groups = await input.getOptionGroups({labelText: 'Two'});

expect(groups.length).toBe(1);
expect(await groups[0].getLabelText()).toBe('Two');
});

it('should be able to get whether the autocomplete is open', async () => {
Expand All @@ -112,6 +121,19 @@ export function runHarnessTests(
await input.focus();
expect(await input.isOpen()).toBe(true);
});

it('should be able to select option', async () => {
const input = await loader.getHarness(autocompleteHarness.with({selector: '#plain'}));
await input.selectOption({text: 'New York'});
expect(await input.getValue()).toBe('NY');
});

it('should throw when selecting an option that is not available', async () => {
const input = await loader.getHarness(autocompleteHarness.with({selector: '#plain'}));
await input.enterText('New');
await expectAsyncError(() => input.selectOption({text: 'Texas'}),
/Error: Could not find a mat-option matching {"text":"Texas"}/);
});
}

function getActiveElementId() {
Expand Down