Skip to content

Commit c95342f

Browse files
committed
address @jelbourn's comments
1 parent 08eb23e commit c95342f

File tree

7 files changed

+136
-123
lines changed

7 files changed

+136
-123
lines changed

src/cdk-experimental/testing/component-harness.ts

Lines changed: 44 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -6,61 +6,33 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
/**
10-
* Test Element interface
11-
* This is a wrapper of native element
12-
*/
13-
export interface TestElement {
14-
blur(): Promise<void>;
15-
clear(): Promise<void>;
16-
click(): Promise<void>;
17-
focus(): Promise<void>;
18-
getCssValue(property: string): Promise<string>;
19-
hover(): Promise<void>;
20-
sendKeys(keys: string): Promise<void>;
21-
text(): Promise<string>;
22-
getAttribute(name: string): Promise<string|null>;
23-
}
9+
import {TestElement} from './test-element';
2410

25-
/**
26-
* Extra searching options used by searching functions
27-
*
28-
* @param allowNull Optional, whether the found element can be null. If
29-
* allowNull is set, the searching function will always try to fetch the element
30-
* at once. When the element cannot be found, the searching function should
31-
* return null if allowNull is set to true, throw an error if allowNull is set
32-
* to false. If allowNull is not set, the framework will choose the behaviors
33-
* that make more sense for each test type (e.g. for unit test, the framework
34-
* will make sure the element is not null; otherwise throw an error); however,
35-
* the internal behavior is not guaranteed and user should not rely on it. Note
36-
* that in most cases, you don't need to care about whether an element is
37-
* present when loading the element and don't need to set this parameter unless
38-
* you do want to check whether the element is present when calling the
39-
* searching function. e.g. you want to make sure some element is not there when
40-
* loading the element in order to check whether a "ngif" works well.
41-
*
42-
* @param global Optional. If global is set to true, the selector will match any
43-
* element on the page and is not limited to the root of the harness. If
44-
* global is unset or set to false, the selector will only find elements under
45-
* the current root.
46-
*/
47-
export interface Options {
11+
/** Options that can be specified when querying for an Element. */
12+
export interface QueryOptions {
13+
/**
14+
* Whether the found element can be null. If allowNull is set, the searching function will always
15+
* try to fetch the element at once. When the element cannot be found, the searching function
16+
* should return null if allowNull is set to true, throw an error if allowNull is set to false.
17+
* If allowNull is not set, the framework will choose the behaviors that make more sense for each
18+
* test type (e.g. for unit test, the framework will make sure the element is not null; otherwise
19+
* throw an error); however, the internal behavior is not guaranteed and user should not rely on
20+
* it. Note that in most cases, you don't need to care about whether an element is present when
21+
* loading the element and don't need to set this parameter unless you do want to check whether
22+
* the element is present when calling the searching function. e.g. you want to make sure some
23+
* element is not there when loading the element in order to check whether a "ngif" works well.
24+
*/
4825
allowNull?: boolean;
26+
/**
27+
* If global is set to true, the selector will match any element on the page and is not limited to
28+
* the root of the harness. If global is unset or set to false, the selector will only find
29+
* elements under the current root.
30+
*/
4931
global?: boolean;
5032
}
5133

52-
/**
53-
* Type narrowing of Options to allow the overloads of ComponentHarness.find to
54-
* return null only if allowNull is set to true.
55-
*/
56-
interface OptionsWithAllowNullSet extends Options {
57-
allowNull: true;
58-
}
59-
60-
/**
61-
* Locator interface
62-
*/
63-
export interface Locator {
34+
/** Interface that is used to find elements in the DOM and create harnesses for them. */
35+
export interface HarnessLocator {
6436
/**
6537
* Get the host element of locator.
6638
*/
@@ -71,7 +43,7 @@ export interface Locator {
7143
* @param selector The CSS selector of the test elements.
7244
* @param options Optional, extra searching options
7345
*/
74-
querySelector(selector: string, options?: Options): Promise<TestElement|null>;
46+
querySelector(selector: string, options?: QueryOptions): Promise<TestElement|null>;
7547

7648
/**
7749
* Search all matched test elements under current root by CSS selector.
@@ -86,16 +58,16 @@ export interface Locator {
8658
* @param options Optional, extra searching options
8759
*/
8860
load<T extends ComponentHarness>(
89-
componentHarness: ComponentHarnessType<T>, root: string,
90-
options?: Options): Promise<T|null>;
61+
componentHarness: ComponentHarnessConstructor<T>, root: string,
62+
options?: QueryOptions): Promise<T|null>;
9163

9264
/**
9365
* Load all Component Harnesses under current root.
9466
* @param componentHarness Type of user customized harness.
9567
* @param root CSS root selector of the new component harnesses.
9668
*/
9769
loadAll<T extends ComponentHarness>(
98-
componentHarness: ComponentHarnessType<T>, root: string): Promise<T[]>;
70+
componentHarness: ComponentHarnessConstructor<T>, root: string): Promise<T[]>;
9971
}
10072

10173
/**
@@ -104,8 +76,8 @@ export interface Locator {
10476
* sub-component harness. It should be inherited when defining user's own
10577
* harness.
10678
*/
107-
export class ComponentHarness {
108-
constructor(private readonly locator: Locator) {}
79+
export abstract class ComponentHarness {
80+
constructor(private readonly locator: HarnessLocator) {}
10981

11082
/**
11183
* Get the host element of component harness.
@@ -127,7 +99,7 @@ export class ComponentHarness {
12799
* @param selector The CSS selector of the test element.
128100
* @param options Extra searching options
129101
*/
130-
protected find(selector: string, options: OptionsWithAllowNullSet):
102+
protected find(selector: string, options: QueryOptions & {allowNull: true}):
131103
() => Promise<TestElement|null>;
132104

133105
/**
@@ -136,15 +108,15 @@ export class ComponentHarness {
136108
* @param selector The CSS selector of the test element.
137109
* @param options Extra searching options
138110
*/
139-
protected find(selector: string, options: Options): () => Promise<TestElement>;
111+
protected find(selector: string, options: QueryOptions): () => Promise<TestElement>;
140112

141113
/**
142114
* Generate a function to find the first matched Component Harness.
143115
* @param componentHarness Type of user customized harness.
144116
* @param root CSS root selector of the new component harness.
145117
*/
146118
protected find<T extends ComponentHarness>(
147-
componentHarness: ComponentHarnessType<T>,
119+
componentHarness: ComponentHarnessConstructor<T>,
148120
root: string): () => Promise<T>;
149121

150122
/**
@@ -154,8 +126,8 @@ export class ComponentHarness {
154126
* @param options Extra searching options
155127
*/
156128
protected find<T extends ComponentHarness>(
157-
componentHarness: ComponentHarnessType<T>, root: string,
158-
options: OptionsWithAllowNullSet): () => Promise<T|null>;
129+
componentHarness: ComponentHarnessConstructor<T>, root: string,
130+
options: QueryOptions & {allowNull: true}): () => Promise<T|null>;
159131

160132
/**
161133
* Generate a function to find the first matched Component Harness.
@@ -164,16 +136,16 @@ export class ComponentHarness {
164136
* @param options Extra searching options
165137
*/
166138
protected find<T extends ComponentHarness>(
167-
componentHarness: ComponentHarnessType<T>, root: string,
168-
options: Options): () => Promise<T>;
139+
componentHarness: ComponentHarnessConstructor<T>, root: string,
140+
options: QueryOptions): () => Promise<T>;
169141

170142
protected find<T extends ComponentHarness>(
171-
selectorOrComponentHarness: string|ComponentHarnessType<T>,
172-
selectorOrOptions?: string|Options,
173-
options?: Options): () => Promise<TestElement|T|null> {
143+
selectorOrComponentHarness: string|ComponentHarnessConstructor<T>,
144+
selectorOrOptions?: string|QueryOptions,
145+
options?: QueryOptions): () => Promise<TestElement|T|null> {
174146
if (typeof selectorOrComponentHarness === 'string') {
175147
const selector = selectorOrComponentHarness;
176-
return () => this.locator.querySelector(selector, selectorOrOptions as Options);
148+
return () => this.locator.querySelector(selector, selectorOrOptions as QueryOptions);
177149
} else {
178150
const componentHarness = selectorOrComponentHarness;
179151
const selector = selectorOrOptions as string;
@@ -196,11 +168,11 @@ export class ComponentHarness {
196168
* locate harnesses under the current root.
197169
*/
198170
protected findAll<T extends ComponentHarness>(
199-
componentHarness: ComponentHarnessType<T>,
171+
componentHarness: ComponentHarnessConstructor<T>,
200172
root: string): () => Promise<T[]>;
201173

202174
protected findAll<T extends ComponentHarness>(
203-
selectorOrComponentHarness: string|ComponentHarnessType<T>,
175+
selectorOrComponentHarness: string|ComponentHarnessConstructor<T>,
204176
root?: string): () => Promise<TestElement[]|T[]> {
205177
if (typeof selectorOrComponentHarness === 'string') {
206178
const selector = selectorOrComponentHarness;
@@ -212,9 +184,7 @@ export class ComponentHarness {
212184
}
213185
}
214186

215-
/**
216-
* Type of ComponentHarness.
217-
*/
218-
export interface ComponentHarnessType<T extends ComponentHarness> {
219-
new(locator: Locator): T;
187+
/** Constructor for a ComponentHarness subclass. */
188+
export interface ComponentHarnessConstructor<T extends ComponentHarness> {
189+
new(locator: HarnessLocator): T;
220190
}

src/cdk-experimental/testing/protractor.ts

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import {browser, by, element as protractorElement, ElementFinder} from 'protract
1313

1414
import {
1515
ComponentHarness,
16-
ComponentHarnessType,
17-
Locator,
18-
Options,
19-
TestElement
16+
ComponentHarnessConstructor,
17+
HarnessLocator,
18+
QueryOptions
2019
} from './component-harness';
20+
import {TestElement} from './test-element';
2121

2222
/**
2323
* Component harness factory for protractor.
@@ -31,7 +31,7 @@ import {
3131
* Set to 'body' by default
3232
*/
3333
export async function load<T extends ComponentHarness>(
34-
componentHarness: ComponentHarnessType<T>,
34+
componentHarness: ComponentHarnessConstructor<T>,
3535
rootSelector: string): Promise<T>;
3636

3737
/**
@@ -42,12 +42,12 @@ export async function load<T extends ComponentHarness>(
4242
* @param options Optional. Extra searching options
4343
*/
4444
export async function load<T extends ComponentHarness>(
45-
componentHarness: ComponentHarnessType<T>, rootSelector?: string,
46-
options?: Options): Promise<T|null>;
45+
componentHarness: ComponentHarnessConstructor<T>, rootSelector?: string,
46+
options?: QueryOptions): Promise<T|null>;
4747

4848
export async function load<T extends ComponentHarness>(
49-
componentHarness: ComponentHarnessType<T>, rootSelector = 'body',
50-
options?: Options): Promise<T|null> {
49+
componentHarness: ComponentHarnessConstructor<T>, rootSelector = 'body',
50+
options?: QueryOptions): Promise<T|null> {
5151
const root = await getElement(rootSelector, undefined, options);
5252
return root && new componentHarness(new ProtractorLocator(root));
5353
}
@@ -60,10 +60,10 @@ export function getElementFinder(testElement: TestElement): ElementFinder {
6060
return testElement.element;
6161
}
6262

63-
throw new Error('Invalid element provided');
63+
throw Error(`Expected an instance of ProtractorElement, got ${testElement}`);
6464
}
6565

66-
class ProtractorLocator implements Locator {
66+
class ProtractorLocator implements HarnessLocator {
6767
private readonly _root: ProtractorElement;
6868

6969
constructor(private _rootFinder: ElementFinder) {
@@ -74,41 +74,34 @@ class ProtractorLocator implements Locator {
7474
return this._root;
7575
}
7676

77-
async querySelector(selector: string, options?: Options): Promise<TestElement|null> {
77+
async querySelector(selector: string, options?: QueryOptions): Promise<TestElement|null> {
7878
const finder = await getElement(selector, this._rootFinder, options);
7979
return finder && new ProtractorElement(finder);
8080
}
8181

8282
async querySelectorAll(selector: string): Promise<TestElement[]> {
8383
const elementFinders = this._rootFinder.all(by.css(selector));
84-
const res: TestElement[] = [];
85-
await elementFinders.each(el => {
86-
if (el) {
87-
res.push(new ProtractorElement(el));
88-
}
89-
});
90-
return res;
84+
return elementFinders.reduce(
85+
(result: TestElement[], el: ElementFinder) =>
86+
el ? result.concat([new ProtractorElement(el)]) : result,
87+
[]);
9188
}
9289

9390
async load<T extends ComponentHarness>(
94-
componentHarness: ComponentHarnessType<T>, selector: string,
95-
options?: Options): Promise<T|null> {
91+
componentHarness: ComponentHarnessConstructor<T>, selector: string,
92+
options?: QueryOptions): Promise<T|null> {
9693
const root = await getElement(selector, this._rootFinder, options);
9794
return root && new componentHarness(new ProtractorLocator(root));
9895
}
9996

10097
async loadAll<T extends ComponentHarness>(
101-
componentHarness: ComponentHarnessType<T>,
98+
componentHarness: ComponentHarnessConstructor<T>,
10299
rootSelector: string): Promise<T[]> {
103100
const roots = this._rootFinder.all(by.css(rootSelector));
104-
const res: T[] = [];
105-
await roots.each(el => {
106-
if (el) {
107-
const locator = new ProtractorLocator(el);
108-
res.push(new componentHarness(locator));
109-
}
110-
});
111-
return res;
101+
return roots.reduce(
102+
(result: T[], el: ElementFinder) =>
103+
el ? result.concat(new componentHarness(new ProtractorLocator(el))) : result,
104+
[]);
112105
}
113106
}
114107

@@ -163,7 +156,7 @@ class ProtractorElement implements TestElement {
163156
* search element globally. If options.global is set, root is ignored.
164157
* @param options Optional, extra searching options
165158
*/
166-
async function getElement(selector: string, root?: ElementFinder, options?: Options):
159+
async function getElement(selector: string, root?: ElementFinder, options?: QueryOptions):
167160
Promise<ElementFinder|null> {
168161
const useGlobalRoot = options && !!options.global;
169162
const elem = root === undefined || useGlobalRoot ?
@@ -174,7 +167,7 @@ async function getElement(selector: string, root?: ElementFinder, options?: Opti
174167
if (allowNull) {
175168
return null;
176169
}
177-
throw new Error('Cannot find element based on the CSS selector: ' + selector);
170+
throw Error('Cannot find element based on the CSS selector: ' + selector);
178171
}
179172
return elem;
180173
}

src/cdk-experimental/testing/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ import * as protractor from './protractor';
1010
import * as testbed from './testbed';
1111

1212
export * from './component-harness';
13+
export * from './test-element';
1314
export {protractor, testbed};
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
/**
10+
* This acts as a common interface for DOM elements across both unit and e2e tests. It is the
11+
* interface through which the ComponentHarness interacts with the component's DOM.
12+
*/
13+
export interface TestElement {
14+
/** Blur the element. */
15+
blur(): Promise<void>;
16+
17+
/** Clear the element's input (for input elements only). */
18+
clear(): Promise<void>;
19+
20+
/** Click the element. */
21+
click(): Promise<void>;
22+
23+
/** Focus the element. */
24+
focus(): Promise<void>;
25+
26+
/** Get the computed value of the given CSS property for the element. */
27+
getCssValue(property: string): Promise<string>;
28+
29+
/** Hovers the mouse over the element. */
30+
hover(): Promise<void>;
31+
32+
/**
33+
* Sends the given string to the input as a series of key presses. Also fires input events
34+
* and attempts to add the string to the Element's value.
35+
*/
36+
sendKeys(keys: string): Promise<void>;
37+
38+
/** Gets the text from the element. */
39+
text(): Promise<string>;
40+
41+
/**
42+
* Gets the value for the given attribute from the element. If the attribute does not exist,
43+
* falls back to reading the property.
44+
*/
45+
getAttribute(name: string): Promise<string | null>;
46+
}

0 commit comments

Comments
 (0)