Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 6a7fc81

Browse files
mmalerbajelbourn
authored andcommitted
feat(cdk-experimental/testing): Adds a HarnessPredicate class (#16319)
This enables users of harness authors to provide an API for querying harnesses based on arbitrary state as given by predicate functions.
1 parent 4b82786 commit 6a7fc81

File tree

9 files changed

+296
-80
lines changed

9 files changed

+296
-80
lines changed

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

Lines changed: 106 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99
import {TestElement} from './test-element';
1010

1111
/** An async function that returns a promise when called. */
12-
export type AsyncFn<T> = () => Promise<T>;
12+
export type AsyncFactoryFn<T> = () => Promise<T>;
13+
14+
/** An async function that takes an item and returns a boolean promise */
15+
export type AsyncPredicate<T> = (item: T) => Promise<boolean>;
16+
17+
/** An async function that takes an item and an option value and returns a boolean promise. */
18+
export type AsyncOptionPredicate<T, O> = (item: T, option: O) => Promise<boolean>;
1319

1420
/**
1521
* Interface used to load ComponentHarness objects. This interface is used by test authors to
@@ -44,17 +50,17 @@ export interface HarnessLoader {
4450
* @return An instance of the given harness type
4551
* @throws If a matching component instance can't be found.
4652
*/
47-
getHarness<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
48-
Promise<T>;
53+
getHarness<T extends ComponentHarness>(
54+
harnessType: ComponentHarnessConstructor<T>|HarnessPredicate<T>): Promise<T>;
4955

5056
/**
5157
* Searches for all instances of the component corresponding to the given harness type under the
5258
* `HarnessLoader`'s root element, and returns a list `ComponentHarness` for each instance.
5359
* @param harnessType The type of harness to create
5460
* @return A list instances of the given harness type.
5561
*/
56-
getAllHarnesses<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
57-
Promise<T[]>;
62+
getAllHarnesses<T extends ComponentHarness>(
63+
harnessType: ComponentHarnessConstructor<T>|HarnessPredicate<T>): Promise<T[]>;
5864
}
5965

6066
/**
@@ -78,7 +84,7 @@ export interface LocatorFactory {
7884
* @return An asynchronous locator function that searches for elements with the given selector,
7985
* and either finds one or throws an error
8086
*/
81-
locatorFor(selector: string): AsyncFn<TestElement>;
87+
locatorFor(selector: string): AsyncFactoryFn<TestElement>;
8288

8389
/**
8490
* Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a
@@ -89,8 +95,8 @@ export interface LocatorFactory {
8995
* @return An asynchronous locator function that searches components matching the given harness
9096
* type, and either returns a `ComponentHarness` for the component, or throws an error.
9197
*/
92-
locatorFor<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
93-
AsyncFn<T>;
98+
locatorFor<T extends ComponentHarness>(
99+
harnessType: ComponentHarnessConstructor<T>|HarnessPredicate<T>): AsyncFactoryFn<T>;
94100

95101
/**
96102
* Creates an asynchronous locator function that can be used to search for elements with the given
@@ -101,7 +107,7 @@ export interface LocatorFactory {
101107
* @return An asynchronous locator function that searches for elements with the given selector,
102108
* and either finds one or returns null.
103109
*/
104-
locatorForOptional(selector: string): AsyncFn<TestElement | null>;
110+
locatorForOptional(selector: string): AsyncFactoryFn<TestElement | null>;
105111

106112
/**
107113
* Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a
@@ -112,8 +118,8 @@ export interface LocatorFactory {
112118
* @return An asynchronous locator function that searches components matching the given harness
113119
* type, and either returns a `ComponentHarness` for the component, or null if none is found.
114120
*/
115-
locatorForOptional<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
116-
AsyncFn<T | null>;
121+
locatorForOptional<T extends ComponentHarness>(
122+
harnessType: ComponentHarnessConstructor<T>|HarnessPredicate<T>): AsyncFactoryFn<T | null>;
117123

118124
/**
119125
* Creates an asynchronous locator function that can be used to search for a list of elements with
@@ -123,7 +129,7 @@ export interface LocatorFactory {
123129
* @return An asynchronous locator function that searches for elements with the given selector,
124130
* and either finds one or throws an error
125131
*/
126-
locatorForAll(selector: string): AsyncFn<TestElement[]>;
132+
locatorForAll(selector: string): AsyncFactoryFn<TestElement[]>;
127133

128134
/**
129135
* Creates an asynchronous locator function that can be used to find a list of
@@ -134,8 +140,8 @@ export interface LocatorFactory {
134140
* @return An asynchronous locator function that searches components matching the given harness
135141
* type, and returns a list of `ComponentHarness`es.
136142
*/
137-
locatorForAll<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
138-
AsyncFn<T[]>;
143+
locatorForAll<T extends ComponentHarness>(
144+
harnessType: ComponentHarnessConstructor<T>|HarnessPredicate<T>): AsyncFactoryFn<T[]>;
139145
}
140146

141147
/**
@@ -169,7 +175,7 @@ export abstract class ComponentHarness {
169175
* @return An asynchronous locator function that searches for elements with the given selector,
170176
* and either finds one or throws an error
171177
*/
172-
protected locatorFor(selector: string): AsyncFn<TestElement>;
178+
protected locatorFor(selector: string): AsyncFactoryFn<TestElement>;
173179

174180
/**
175181
* Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a
@@ -181,7 +187,7 @@ export abstract class ComponentHarness {
181187
* type, and either returns a `ComponentHarness` for the component, or throws an error.
182188
*/
183189
protected locatorFor<T extends ComponentHarness>(
184-
harnessType: ComponentHarnessConstructor<T>): AsyncFn<T>;
190+
harnessType: ComponentHarnessConstructor<T>|HarnessPredicate<T>): AsyncFactoryFn<T>;
185191

186192
protected locatorFor(arg: any): any {
187193
return this.locatorFactory.locatorFor(arg);
@@ -196,7 +202,7 @@ export abstract class ComponentHarness {
196202
* @return An asynchronous locator function that searches for elements with the given selector,
197203
* and either finds one or returns null.
198204
*/
199-
protected locatorForOptional(selector: string): AsyncFn<TestElement | null>;
205+
protected locatorForOptional(selector: string): AsyncFactoryFn<TestElement | null>;
200206

201207
/**
202208
* Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a
@@ -208,7 +214,7 @@ export abstract class ComponentHarness {
208214
* type, and either returns a `ComponentHarness` for the component, or null if none is found.
209215
*/
210216
protected locatorForOptional<T extends ComponentHarness>(
211-
harnessType: ComponentHarnessConstructor<T>): AsyncFn<T | null>;
217+
harnessType: ComponentHarnessConstructor<T>|HarnessPredicate<T>): AsyncFactoryFn<T | null>;
212218

213219
protected locatorForOptional(arg: any): any {
214220
return this.locatorFactory.locatorForOptional(arg);
@@ -222,7 +228,7 @@ export abstract class ComponentHarness {
222228
* @return An asynchronous locator function that searches for elements with the given selector,
223229
* and either finds one or throws an error
224230
*/
225-
protected locatorForAll(selector: string): AsyncFn<TestElement[]>;
231+
protected locatorForAll(selector: string): AsyncFactoryFn<TestElement[]>;
226232

227233
/**
228234
* Creates an asynchronous locator function that can be used to find a list of
@@ -233,8 +239,8 @@ export abstract class ComponentHarness {
233239
* @return An asynchronous locator function that searches components matching the given harness
234240
* type, and returns a list of `ComponentHarness`es.
235241
*/
236-
protected locatorForAll<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
237-
AsyncFn<T[]>;
242+
protected locatorForAll<T extends ComponentHarness>(
243+
harnessType: ComponentHarnessConstructor<T>|HarnessPredicate<T>): AsyncFactoryFn<T[]>;
238244

239245
protected locatorForAll(arg: any): any {
240246
return this.locatorFactory.locatorForAll(arg);
@@ -252,3 +258,82 @@ export interface ComponentHarnessConstructor<T extends ComponentHarness> {
252258
*/
253259
hostSelector: string;
254260
}
261+
262+
/**
263+
* A class used to associate a ComponentHarness class with predicates functions that can be used to
264+
* filter instances of the class.
265+
*/
266+
export class HarnessPredicate<T extends ComponentHarness> {
267+
private _predicates: AsyncPredicate<T>[] = [];
268+
private _descriptions: string[] = [];
269+
270+
constructor(public harnessType: ComponentHarnessConstructor<T>) {}
271+
272+
/**
273+
* Checks if a string matches the given pattern.
274+
* @param s The string to check, or a Promise for the string to check.
275+
* @param pattern The pattern the string is expected to match. If `pattern` is a string, `s` is
276+
* expected to match exactly. If `pattern` is a regex, a partial match is allowed.
277+
* @return A Promise that resolves to whether the string matches the pattern.
278+
*/
279+
static async stringMatches(s: string | Promise<string>, pattern: string | RegExp):
280+
Promise<boolean> {
281+
s = await s;
282+
return typeof pattern === 'string' ? s === pattern : pattern.test(s);
283+
}
284+
285+
/**
286+
* Adds a predicate function to be run against candidate harnesses.
287+
* @param description A description of this predicate that may be used in error messages.
288+
* @param predicate An async predicate function.
289+
* @return this (for method chaining).
290+
*/
291+
add(description: string, predicate: AsyncPredicate<T>) {
292+
this._descriptions.push(description);
293+
this._predicates.push(predicate);
294+
return this;
295+
}
296+
297+
/**
298+
* Adds a predicate function that depends on an option value to be run against candidate
299+
* harnesses. If the option value is undefined, the predicate will be ignored.
300+
* @param name The name of the option (may be used in error messages).
301+
* @param option The option value.
302+
* @param predicate The predicate function to run if the option value is not undefined.
303+
* @return this (for method chaining).
304+
*/
305+
addOption<O>(name: string, option: O | undefined, predicate: AsyncOptionPredicate<T, O>) {
306+
// Add quotes around strings to differentiate them from other values
307+
const value = typeof option === 'string' ? `"${option}"` : `${option}`;
308+
if (option !== undefined) {
309+
this.add(`${name} = ${value}`, item => predicate(item, option));
310+
}
311+
return this;
312+
}
313+
314+
/**
315+
* Filters a list of harnesses on this predicate.
316+
* @param harnesses The list of harnesses to filter.
317+
* @return A list of harnesses that satisfy this predicate.
318+
*/
319+
async filter(harnesses: T[]): Promise<T[]> {
320+
const results = await Promise.all(harnesses.map(h => this.evaluate(h)));
321+
return harnesses.filter((_, i) => results[i]);
322+
}
323+
324+
/**
325+
* Evaluates whether the given harness satisfies this predicate.
326+
* @param harness The harness to check
327+
* @return A promise that resolves to true if the harness satisfies this predicate,
328+
* and resolves to false otherwise.
329+
*/
330+
async evaluate(harness: T): Promise<boolean> {
331+
const results = await Promise.all(this._predicates.map(p => p(harness)));
332+
return results.reduce((combined, current) => combined && current, true);
333+
}
334+
335+
/** Gets a description of this predicate for use in error messages. */
336+
getDescription() {
337+
return this._descriptions.join(', ');
338+
}
339+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /