diff --git a/src/material/autocomplete/testing/BUILD.bazel b/src/material/autocomplete/testing/BUILD.bazel index 3a46ae587e35..4a2f96a25aca 100644 --- a/src/material/autocomplete/testing/BUILD.bazel +++ b/src/material/autocomplete/testing/BUILD.bazel @@ -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", diff --git a/src/material/autocomplete/testing/autocomplete-harness-filters.ts b/src/material/autocomplete/testing/autocomplete-harness-filters.ts index 3abf42268820..5b63585fad69 100644 --- a/src/material/autocomplete/testing/autocomplete-harness-filters.ts +++ b/src/material/autocomplete/testing/autocomplete-harness-filters.ts @@ -8,4 +8,6 @@ import {BaseHarnessFilters} from '@angular/cdk/testing'; -export interface AutocompleteHarnessFilters extends BaseHarnessFilters {} +export interface AutocompleteHarnessFilters extends BaseHarnessFilters { + value?: string | RegExp; +} diff --git a/src/material/autocomplete/testing/autocomplete-harness.ts b/src/material/autocomplete/testing/autocomplete-harness.ts index 0d019bdc9b6b..c937813f222d 100644 --- a/src/material/autocomplete/testing/autocomplete-harness.ts +++ b/src/material/autocomplete/testing/autocomplete-harness.ts @@ -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'; @@ -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'; @@ -35,11 +37,14 @@ export class MatAutocompleteHarness extends ComponentHarness { * @return a `HarnessPredicate` configured with the given options. */ static with(options: AutocompleteHarnessFilters = {}): HarnessPredicate { - 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 { - return (await this.host()).getAttribute(attributeName); + /** Gets the value of the autocomplete input. */ + async getValue(): Promise { + return (await this.host()).getProperty('value'); } /** Gets a boolean promise indicating if the autocomplete input is disabled. */ @@ -48,11 +53,6 @@ export class MatAutocompleteHarness extends ComponentHarness { return coerceBooleanProperty(await disabled); } - /** Gets a promise for the autocomplete's text. */ - async getText(): Promise { - return (await this.host()).getProperty('value'); - } - /** Focuses the input and returns a void promise that indicates when the action is complete. */ async focus(): Promise { return (await this.host()).focus(); @@ -68,28 +68,31 @@ export class MatAutocompleteHarness extends ComponentHarness { return (await this.host()).sendKeys(value); } - /** Gets the autocomplete panel. */ - async getPanel(): Promise { - return this._panel(); - } - /** Gets the options inside the autocomplete panel. */ - async getOptions(): Promise { - return this._options(); + async getOptions(filters: OptionHarnessFilters = {}): Promise { + return this._documentRootLocator.locatorForAll(MatAutocompleteOptionHarness.with(filters))(); } /** Gets the groups of options inside the panel. */ - async getOptionGroups(): Promise { - return this._groups(); + async getOptionGroups(filters: OptionGroupHarnessFilters = {}): + Promise { + return this._documentRootLocator.locatorForAll( + MatAutocompleteOptionGroupHarness.with(filters))(); } - /** Gets whether the autocomplete panel is visible. */ - async isPanelVisible(): Promise { - return (await this._panel()).hasClass('mat-autocomplete-visible'); + /** Selects the first option matching the given filters. */ + async selectOption(filters: OptionHarnessFilters): Promise { + 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 { - return !!(await this._optionalPanel()); + const panel = await this._optionalPanel(); + return !!panel && await panel.hasClass('mat-autocomplete-visible'); } } diff --git a/src/material/autocomplete/testing/option-harness.ts b/src/material/autocomplete/testing/option-harness.ts index 3b0b118afa0c..e3b1d5604554 100644 --- a/src/material/autocomplete/testing/option-harness.ts +++ b/src/material/autocomplete/testing/option-harness.ts @@ -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; } /** @@ -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 { + async select(): Promise { return (await this.host()).click(); } @@ -55,8 +54,7 @@ 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. */ @@ -64,4 +62,3 @@ export class MatAutocompleteOptionGroupHarness extends ComponentHarness { return (await this._label()).text(); } } - diff --git a/src/material/autocomplete/testing/shared.spec.ts b/src/material/autocomplete/testing/shared.spec.ts index 782b9bd09cc5..55bfcb1b65db 100644 --- a/src/material/autocomplete/testing/shared.spec.ts +++ b/src/material/autocomplete/testing/shared.spec.ts @@ -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'; @@ -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 () => { @@ -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 () => { @@ -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(); @@ -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 () => { @@ -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() {