Skip to content

Commit d1a73e4

Browse files
committed
feat(material/autocomplete/testing): polish harness API
1 parent 1a589ce commit d1a73e4

File tree

5 files changed

+87
-51
lines changed

5 files changed

+87
-51
lines changed

src/material/autocomplete/testing/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ ng_test_library(
2626
deps = [
2727
":testing",
2828
"//src/cdk/overlay",
29+
"//src/cdk/private/testing",
2930
"//src/cdk/testing",
3031
"//src/cdk/testing/testbed",
3132
"//src/material/autocomplete",

src/material/autocomplete/testing/autocomplete-harness-filters.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@
88

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

11-
export interface AutocompleteHarnessFilters extends BaseHarnessFilters {}
11+
export interface AutocompleteHarnessFilters extends BaseHarnessFilters {
12+
value?: string | RegExp;
13+
}

src/material/autocomplete/testing/autocomplete-harness.ts

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {ComponentHarness, HarnessPredicate, TestElement} from '@angular/cdk/testing';
109
import {coerceBooleanProperty} from '@angular/cdk/coercion';
10+
import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing';
1111
import {AutocompleteHarnessFilters} from './autocomplete-harness-filters';
12-
import {MatAutocompleteOptionHarness, MatAutocompleteOptionGroupHarness} from './option-harness';
12+
import {
13+
MatAutocompleteOptionGroupHarness,
14+
MatAutocompleteOptionHarness,
15+
OptionGroupHarnessFilters,
16+
OptionHarnessFilters
17+
} from './option-harness';
1318

1419
/** Selector for the autocomplete panel. */
1520
const PANEL_SELECTOR = '.mat-autocomplete-panel';
@@ -20,10 +25,7 @@ const PANEL_SELECTOR = '.mat-autocomplete-panel';
2025
*/
2126
export class MatAutocompleteHarness extends ComponentHarness {
2227
private _documentRootLocator = this.documentRootLocatorFactory();
23-
private _panel = this._documentRootLocator.locatorFor(PANEL_SELECTOR);
2428
private _optionalPanel = this._documentRootLocator.locatorForOptional(PANEL_SELECTOR);
25-
private _options = this._documentRootLocator.locatorForAll(MatAutocompleteOptionHarness);
26-
private _groups = this._documentRootLocator.locatorForAll(MatAutocompleteOptionGroupHarness);
2729

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

@@ -35,11 +37,15 @@ export class MatAutocompleteHarness extends ComponentHarness {
3537
* @return a `HarnessPredicate` configured with the given options.
3638
*/
3739
static with(options: AutocompleteHarnessFilters = {}): HarnessPredicate<MatAutocompleteHarness> {
38-
return new HarnessPredicate(MatAutocompleteHarness, options);
40+
return new HarnessPredicate(MatAutocompleteHarness, options)
41+
.addOption('value', options.value,
42+
(harness, value) => HarnessPredicate.stringMatches(harness.getValue(), value));
3943
}
4044

41-
async getAttribute(attributeName: string): Promise<string|null> {
42-
return (await this.host()).getAttribute(attributeName);
45+
/** Gets the value of the autocomplete input. */
46+
async getValue(): Promise<string> {
47+
// The "value" property of the native input is never undefined.
48+
return (await (await this.host()).getProperty('value'))!;
4349
}
4450

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

51-
/** Gets a promise for the autocomplete's text. */
52-
async getText(): Promise<string> {
53-
return (await this.host()).getProperty('value');
54-
}
55-
5657
/** Focuses the input and returns a void promise that indicates when the action is complete. */
5758
async focus(): Promise<void> {
5859
return (await this.host()).focus();
@@ -68,28 +69,33 @@ export class MatAutocompleteHarness extends ComponentHarness {
6869
return (await this.host()).sendKeys(value);
6970
}
7071

71-
/** Gets the autocomplete panel. */
72-
async getPanel(): Promise<TestElement> {
73-
return this._panel();
74-
}
75-
7672
/** Gets the options inside the autocomplete panel. */
77-
async getOptions(): Promise<MatAutocompleteOptionHarness[]> {
78-
return this._options();
73+
async getOptions(filters: OptionHarnessFilters = {}): Promise<MatAutocompleteOptionHarness[]> {
74+
return this._documentRootLocator.locatorForAll(MatAutocompleteOptionHarness.with(filters))();
7975
}
8076

8177
/** Gets the groups of options inside the panel. */
82-
async getOptionGroups(): Promise<MatAutocompleteOptionGroupHarness[]> {
83-
return this._groups();
78+
async getOptionGroups(filters: OptionGroupHarnessFilters = {}):
79+
Promise<MatAutocompleteOptionGroupHarness[]> {
80+
return this._documentRootLocator.locatorForAll(
81+
MatAutocompleteOptionGroupHarness.with(filters))();
8482
}
8583

86-
/** Gets whether the autocomplete panel is visible. */
87-
async isPanelVisible(): Promise<boolean> {
88-
return (await this._panel()).hasClass('mat-autocomplete-visible');
84+
/** Selects the first options matching the given filters. */
85+
async selectOption(filters: OptionHarnessFilters = {}): Promise<void> {
86+
if (!await this.isOpen()) {
87+
throw Error('mat-autocomplete dropdown must be open to select an option');
88+
}
89+
const options = await this.getOptions(filters);
90+
if (!options.length) {
91+
throw Error(`Could not find a mat-option matching ${JSON.stringify(filters)}`);
92+
}
93+
await options[0].click();
8994
}
9095

9196
/** Gets whether the autocomplete is open. */
9297
async isOpen(): Promise<boolean> {
93-
return !!(await this._optionalPanel());
98+
const panel = await this._optionalPanel();
99+
return !!panel && await panel.hasClass('mat-autocomplete-visible');
94100
}
95101
}

src/material/autocomplete/testing/option-harness.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ import {ComponentHarness, HarnessPredicate, BaseHarnessFilters} from '@angular/c
1212
// and expand to cover all states once we have experimental/core.
1313

1414
export interface OptionHarnessFilters extends BaseHarnessFilters {
15-
text?: string;
15+
text?: string | RegExp;
1616
}
1717

1818
export interface OptionGroupHarnessFilters extends BaseHarnessFilters {
19-
labelText?: string;
19+
text?: string | RegExp;
2020
}
2121

2222
/**
@@ -29,8 +29,7 @@ export class MatAutocompleteOptionHarness extends ComponentHarness {
2929
static with(options: OptionHarnessFilters = {}) {
3030
return new HarnessPredicate(MatAutocompleteOptionHarness, options)
3131
.addOption('text', options.text,
32-
async (harness, title) =>
33-
HarnessPredicate.stringMatches(await harness.getText(), title));
32+
(harness, text) => HarnessPredicate.stringMatches(harness.getText(), text));
3433
}
3534

3635
/** Clicks the option. */
@@ -54,13 +53,12 @@ export class MatAutocompleteOptionGroupHarness extends ComponentHarness {
5453

5554
static with(options: OptionGroupHarnessFilters = {}) {
5655
return new HarnessPredicate(MatAutocompleteOptionGroupHarness, options)
57-
.addOption('labelText', options.labelText,
58-
async (harness, title) =>
59-
HarnessPredicate.stringMatches(await harness.getLabelText(), title));
56+
.addOption('text', options.text,
57+
(harness, title) => HarnessPredicate.stringMatches(harness.getText(), title));
6058
}
6159

6260
/** Gets a promise for the option group's label text. */
63-
async getLabelText(): Promise<string> {
61+
async getText(): Promise<string> {
6462
return (await this._label()).text();
6563
}
6664
}

src/material/autocomplete/testing/shared.spec.ts

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {OverlayContainer} from '@angular/cdk/overlay';
2+
import {expectAsyncError} from '@angular/cdk/private/testing';
23
import {HarnessLoader} from '@angular/cdk/testing';
34
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
45
import {Component} from '@angular/core';
@@ -42,9 +43,15 @@ export function runHarnessTests(
4243
expect(inputs.length).toBe(5);
4344
});
4445

45-
it('should be able to get text inside the input', async () => {
46+
it('should load harness for autocomplete with value', async () => {
47+
const ac = await loader.getHarness(autocompleteHarness.with({value: /Prefilled/}));
48+
const id = await (await ac.host()).getAttribute('id');
49+
expect(id).toBe('prefilled');
50+
});
51+
52+
it('should be able to get value of the input', async () => {
4653
const input = await loader.getHarness(autocompleteHarness.with({selector: '#prefilled'}));
47-
expect(await input.getText()).toBe('Prefilled value');
54+
expect(await input.getValue()).toBe('Prefilled value');
4855
});
4956

5057
it('should get disabled state', async () => {
@@ -67,13 +74,7 @@ export function runHarnessTests(
6774
it('should be able to type in an input', async () => {
6875
const input = await loader.getHarness(autocompleteHarness.with({selector: '#plain'}));
6976
await input.enterText('Hello there');
70-
expect(await input.getText()).toBe('Hello there');
71-
});
72-
73-
it('should be able to get the autocomplete panel', async () => {
74-
const input = await loader.getHarness(autocompleteHarness.with({selector: '#plain'}));
75-
await input.focus();
76-
expect(await input.getPanel()).toBeTruthy();
77+
expect(await input.getValue()).toBe('Hello there');
7778
});
7879

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

89+
it('should be able to get filtered options', async () => {
90+
const input = await loader.getHarness(autocompleteHarness.with({selector: '#plain'}));
91+
await input.focus();
92+
const options = await input.getOptions({text: /New/});
93+
94+
expect(options.length).toBe(1);
95+
expect(await options[0].getText()).toBe('New York');
96+
});
97+
8898
it('should be able to get the autocomplete panel groups', async () => {
8999
const input = await loader.getHarness(autocompleteHarness.with({selector: '#grouped'}));
90100
await input.focus();
@@ -95,14 +105,13 @@ export function runHarnessTests(
95105
expect(options.length).toBe(11);
96106
});
97107

98-
it('should be able to get the autocomplete panel', async () => {
99-
// Focusing without any options will render the panel, but it'll be invisible.
100-
fixture.componentInstance.states = [];
101-
fixture.detectChanges();
102-
103-
const input = await loader.getHarness(autocompleteHarness.with({selector: '#plain'}));
108+
it('should be able to get filtered panel groups', async () => {
109+
const input = await loader.getHarness(autocompleteHarness.with({selector: '#grouped'}));
104110
await input.focus();
105-
expect(await input.isPanelVisible()).toBe(false);
111+
const groups = await input.getOptionGroups({text: 'Two'});
112+
113+
expect(groups.length).toBe(1);
114+
expect(await groups[0].getText()).toBe('Two');
106115
});
107116

108117
it('should be able to get whether the autocomplete is open', async () => {
@@ -112,6 +121,26 @@ export function runHarnessTests(
112121
await input.focus();
113122
expect(await input.isOpen()).toBe(true);
114123
});
124+
125+
it('should be able to select option', async () => {
126+
const input = await loader.getHarness(autocompleteHarness.with({selector: '#plain'}));
127+
await input.enterText('New');
128+
await input.selectOption({text: 'New York'});
129+
expect(await input.getValue()).toBe('NY');
130+
});
131+
132+
it('should throw when selecting an option if autocomplete is not open', async () => {
133+
const input = await loader.getHarness(autocompleteHarness.with({selector: '#plain'}));
134+
await expectAsyncError(() => input.selectOption({text: 'New York'}),
135+
/mat-autocomplete dropdown must be open to select an option/);
136+
});
137+
138+
it('should throw when selecting an option that is not available', async () => {
139+
const input = await loader.getHarness(autocompleteHarness.with({selector: '#plain'}));
140+
await input.enterText('New');
141+
await expectAsyncError(() => input.selectOption({text: 'Texas'}),
142+
/Error: Could not find a mat-option matching {"text":"Texas"}/);
143+
});
115144
}
116145

117146
function getActiveElementId() {

0 commit comments

Comments
 (0)