Skip to content

Commit c517e86

Browse files
committed
feat(material-experimental): add autocomplete test harness
* Adds a test harness for `mat-autocomplete`. * Fixes the global element locator not working for `TestBed` elements. * Adds the ability to get a property of a `TestElement`.
1 parent 88631b9 commit c517e86

18 files changed

+494
-1
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484

8585
# Material experimental package
8686
/src/material-experimental/* @jelbourn
87+
/src/material-experimental/mdc-autocomplete/** @crisbeto
8788
/src/material-experimental/mdc-button/** @andrewseguin
8889
/src/material-experimental/mdc-card/** @mmalerba
8990
/src/material-experimental/mdc-checkbox/** @mmalerba

src/cdk-experimental/testing/protractor/protractor-element.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,8 @@ export class ProtractorElement implements TestElement {
5555
const classes = (await this.getAttribute('class')) || '';
5656
return new Set(classes.split(/\s+/).filter(c => c)).has(name);
5757
}
58+
59+
async getPropertyValue(name: string): Promise<any> {
60+
return browser.executeScript(`arguments[0][${name}]`, this.element);
61+
}
5862
}

src/cdk-experimental/testing/test-element.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,7 @@ export interface TestElement {
4646

4747
/** Checks whether the element has the given class. */
4848
hasClass(name: string): Promise<boolean>;
49+
50+
/** Gets a property of an element. */
51+
getPropertyValue(name: string): Promise<any>;
4952
}

src/cdk-experimental/testing/testbed/testbed-harness-environment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class TestbedHarnessEnvironment extends HarnessEnvironment<Element> {
3737
}
3838

3939
protected getDocumentRoot(): Element {
40-
return this._fixture.nativeElement;
40+
return document.body;
4141
}
4242

4343
protected createTestElement(element: Element): TestElement {

src/cdk-experimental/testing/testbed/unit-test-element.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,9 @@ export class UnitTestElement implements TestElement {
105105
await this._stabilize();
106106
return this.element.classList.contains(name);
107107
}
108+
109+
async getPropertyValue(name: string): Promise<any> {
110+
await this._stabilize();
111+
return (this.element as any)[name];
112+
}
108113
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package(default_visibility = ["//visibility:public"])
2+
3+
load("@io_bazel_rules_sass//:defs.bzl", "sass_binary", "sass_library")
4+
load("//tools:defaults.bzl", "ng_e2e_test_library", "ng_module", "ng_test_library", "ng_web_test_suite", "ts_library")
5+
load("//src/e2e-app:test_suite.bzl", "e2e_test_suite")
6+
7+
ng_module(
8+
name = "mdc-autocomplete",
9+
srcs = glob(
10+
["**/*.ts"],
11+
exclude = [
12+
"**/*.spec.ts",
13+
"harness/**",
14+
],
15+
),
16+
assets = [
17+
# TODO: include scss assets
18+
] + glob(["**/*.html"]),
19+
module_name = "@angular/material-experimental/mdc-autocomplete",
20+
deps = [
21+
"//src/material/core",
22+
],
23+
)
24+
25+
ts_library(
26+
name = "harness",
27+
srcs = glob(
28+
["harness/**/*.ts"],
29+
exclude = ["**/*.spec.ts"],
30+
),
31+
deps = [
32+
"//src/cdk-experimental/testing",
33+
"//src/cdk/coercion",
34+
],
35+
)
36+
37+
sass_library(
38+
name = "mdc_autocomplete_scss_lib",
39+
srcs = glob(["**/_*.scss"]),
40+
deps = [
41+
"//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib",
42+
"//src/material-experimental/mdc-helpers:mdc_scss_deps_lib",
43+
"//src/material/core:core_scss_lib",
44+
],
45+
)
46+
47+
sass_binary(
48+
name = "autocomplete_scss",
49+
src = "autocomplete.scss",
50+
include_paths = [
51+
"external/npm/node_modules",
52+
],
53+
deps = [
54+
"//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib",
55+
"//src/material-experimental/mdc-helpers:mdc_scss_deps_lib",
56+
"//src/material/core:all_themes",
57+
],
58+
)
59+
60+
ng_test_library(
61+
name = "autocomplete_tests_lib",
62+
srcs = [
63+
"harness/autocomplete-harness.spec.ts",
64+
],
65+
deps = [
66+
":harness",
67+
":mdc-autocomplete",
68+
"//src/cdk-experimental/testing",
69+
"//src/cdk-experimental/testing/testbed",
70+
"//src/cdk/platform",
71+
"//src/cdk/testing",
72+
"//src/material/autocomplete",
73+
"@npm//@angular/platform-browser",
74+
],
75+
)
76+
77+
ng_web_test_suite(
78+
name = "unit_tests",
79+
deps = [
80+
":autocomplete_tests_lib",
81+
"//src/material-experimental:mdc_require_config.js",
82+
],
83+
)
84+
85+
ng_e2e_test_library(
86+
name = "e2e_test_sources",
87+
srcs = glob(["**/*.e2e.spec.ts"]),
88+
deps = [
89+
"//src/cdk/testing/e2e",
90+
],
91+
)
92+
93+
e2e_test_suite(
94+
name = "e2e_tests",
95+
deps = [
96+
":e2e_test_sources",
97+
"//src/cdk/testing/e2e",
98+
],
99+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<!-- TODO -->
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@import '../mdc-helpers/mdc-helpers';
2+
3+
@mixin mat-autocomplete-theme-mdc($theme) {
4+
@include mat-using-mdc-theme($theme) {
5+
// TODO: implement MDC-based autocomplete.
6+
}
7+
}
8+
9+
@mixin mat-autocomplete-typography-mdc($config) {
10+
@include mat-using-mdc-typography($config) {
11+
// TODO: implement MDC-based autocomplete.
12+
}
13+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// TODO: implement MDC-based autocomplete
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
export type AutocompleteHarnessFilters = {
10+
id?: string;
11+
name?: string,
12+
};
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import {HarnessLoader} from '@angular/cdk-experimental/testing';
2+
import {TestbedHarnessEnvironment} from '@angular/cdk-experimental/testing/testbed';
3+
import {Component} from '@angular/core';
4+
import {ComponentFixture, TestBed} from '@angular/core/testing';
5+
import {MatAutocompleteModule} from '@angular/material/autocomplete';
6+
import {MatAutocompleteModule as MatMdcAutocompleteModule} from '../index';
7+
import {MatAutocompleteHarness} from './autocomplete-harness';
8+
import {MatAutocompleteHarness as MatMdcAutocompleteHarness} from './mdc-autocomplete-harness';
9+
10+
let fixture: ComponentFixture<AutocompleteHarnessTest>;
11+
let loader: HarnessLoader;
12+
let harness: typeof MatAutocompleteHarness;
13+
14+
describe('MatAutocompleteHarness', () => {
15+
describe('non-MDC-based', () => {
16+
beforeEach(async () => {
17+
await TestBed.configureTestingModule({
18+
imports: [MatAutocompleteModule],
19+
declarations: [AutocompleteHarnessTest],
20+
}).compileComponents();
21+
22+
fixture = TestBed.createComponent(AutocompleteHarnessTest);
23+
fixture.detectChanges();
24+
loader = TestbedHarnessEnvironment.loader(fixture);
25+
harness = MatAutocompleteHarness;
26+
});
27+
28+
runTests();
29+
});
30+
31+
describe('MDC-based', () => {
32+
beforeEach(async () => {
33+
await TestBed.configureTestingModule({
34+
imports: [MatMdcAutocompleteModule],
35+
declarations: [AutocompleteHarnessTest],
36+
}).compileComponents();
37+
38+
fixture = TestBed.createComponent(AutocompleteHarnessTest);
39+
fixture.detectChanges();
40+
loader = TestbedHarnessEnvironment.loader(fixture);
41+
// Public APIs are the same as MatAutocompleteHarness, but cast
42+
// is necessary because of different private fields.
43+
harness = MatMdcAutocompleteHarness as any;
44+
});
45+
46+
// TODO: enable after MDC autocomplete is implemented
47+
// runTests();
48+
});
49+
});
50+
51+
/** Shared tests to run on both the original and MDC-based autocomplete. */
52+
function runTests() {
53+
it('should load all autocomplete harnesses', async () => {
54+
const inputs = await loader.getAllHarnesses(harness);
55+
expect(inputs.length).toBe(5);
56+
});
57+
58+
it('should be able to get text inside the input', async () => {
59+
const input = await loader.getHarness(harness.with({id: 'prefilled'}));
60+
expect(await input.getText()).toBe('Prefilled value');
61+
});
62+
63+
it('should get disabled state', async () => {
64+
const enabled = await loader.getHarness(harness.with({id: 'plain'}));
65+
const disabled = await loader.getHarness(harness.with({id: 'disabled'}));
66+
67+
expect(await enabled.isDisabled()).toBe(false);
68+
expect(await disabled.isDisabled()).toBe(true);
69+
});
70+
71+
it('should focus and blur an input', async () => {
72+
const input = await loader.getHarness(harness.with({id: 'plain'}));
73+
expect(getActiveElementId()).not.toBe('plain');
74+
await input.focus();
75+
expect(getActiveElementId()).toBe('plain');
76+
await input.blur();
77+
expect(getActiveElementId()).not.toBe('plain');
78+
});
79+
80+
it('should be able to type in an input', async () => {
81+
const input = await loader.getHarness(harness.with({id: 'plain'}));
82+
await input.enterText('Hello there');
83+
expect(await input.getText()).toBe('Hello there');
84+
});
85+
86+
it('should be able to get the autocomplete panel', async () => {
87+
const input = await loader.getHarness(harness.with({id: 'plain'}));
88+
await input.focus();
89+
expect(await input.getPanel()).toBeTruthy();
90+
});
91+
92+
it('should be able to get the autocomplete panel options', async () => {
93+
const input = await loader.getHarness(harness.with({id: 'plain'}));
94+
await input.focus();
95+
const options = await input.getOptions();
96+
97+
expect(options.length).toBe(11);
98+
expect(await options[5].text()).toBe('New York');
99+
});
100+
101+
it('should be able to get the autocomplete panel groups', async () => {
102+
const input = await loader.getHarness(harness.with({id: 'grouped'}));
103+
await input.focus();
104+
const groups = await input.getOptionGroups();
105+
const options = await input.getOptions();
106+
107+
expect(groups.length).toBe(3);
108+
expect(options.length).toBe(11);
109+
});
110+
111+
it('should be able to get the autocomplete panel', async () => {
112+
// Focusing without any options will render the panel, but it'll be invisible.
113+
fixture.componentInstance.states = [];
114+
fixture.detectChanges();
115+
116+
const input = await loader.getHarness(harness.with({id: 'plain'}));
117+
await input.focus();
118+
expect(await input.panelIsVisible()).toBe(false);
119+
});
120+
121+
it('should be able to get whether the autocomplete is open', async () => {
122+
const input = await loader.getHarness(harness.with({id: 'plain'}));
123+
124+
expect(await input.isOpen()).toBe(false);
125+
await input.focus();
126+
expect(await input.isOpen()).toBe(true);
127+
});
128+
129+
}
130+
131+
function getActiveElementId() {
132+
return document.activeElement ? document.activeElement.id : '';
133+
}
134+
135+
@Component({
136+
template: `
137+
<mat-autocomplete #autocomplete="matAutocomplete">
138+
<mat-option *ngFor="let state of states" [value]="state.code">{{ state.name }}</mat-option>
139+
</mat-autocomplete>
140+
141+
<mat-autocomplete #groupedAutocomplete="matAutocomplete">
142+
<mat-optgroup *ngFor="let group of stateGroups" [label]="group.name">
143+
<mat-option
144+
*ngFor="let state of group.states"
145+
[value]="state.code">{{ state.name }}</mat-option>
146+
</mat-optgroup>
147+
</mat-autocomplete>
148+
149+
<input id="plain" [matAutocomplete]="autocomplete">
150+
<input id="disabled" disabled [matAutocomplete]="autocomplete">
151+
<textarea id="textarea" [matAutocomplete]="autocomplete"></textarea>
152+
<input id="prefilled" [matAutocomplete]="autocomplete" value="Prefilled value">
153+
<input id="grouped" [matAutocomplete]="groupedAutocomplete">
154+
`
155+
})
156+
class AutocompleteHarnessTest {
157+
states = [
158+
{code: 'AL', name: 'Alabama'},
159+
{code: 'CA', name: 'California'},
160+
{code: 'FL', name: 'Florida'},
161+
{code: 'KS', name: 'Kansas'},
162+
{code: 'MA', name: 'Massachusetts'},
163+
{code: 'NY', name: 'New York'},
164+
{code: 'OR', name: 'Oregon'},
165+
{code: 'PA', name: 'Pennsylvania'},
166+
{code: 'TN', name: 'Tennessee'},
167+
{code: 'VA', name: 'Virginia'},
168+
{code: 'WY', name: 'Wyoming'},
169+
];
170+
171+
stateGroups = [
172+
{
173+
name: 'One',
174+
states: this.states.slice(0, 3)
175+
},
176+
{
177+
name: 'Two',
178+
states: this.states.slice(3, 7)
179+
},
180+
{
181+
name: 'Three',
182+
states: this.states.slice(7)
183+
}
184+
];
185+
}
186+

0 commit comments

Comments
 (0)