Skip to content

Commit 42b6ee6

Browse files
committed
feat(material/testing): Modify HarnessLoader with the addition of
`getHarnessAtIndex` and `countHarnesses` These two new functions are intended to expand harness testing functionality by providing built-in functions for commonly used patterns. * `getHarnessAtIndex` functions similarly to `getHarness`, but returns a harness for the matching component instance with the given index. An example use case is to fetch the nth MatOptionHarness on screen. * `countHarnesses` simply counts the number of matching component instances and returns the result. Documentation is updated to reflect this changes, and adds a missing row for the `hasHarness` function Manually tested using the MatInputHarness tests
1 parent f19ab40 commit 42b6ee6

File tree

4 files changed

+84
-4
lines changed

4 files changed

+84
-4
lines changed

src/cdk/testing/component-harness.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,17 @@ export interface HarnessLoader {
119119
*/
120120
getHarnessOrNull<T extends ComponentHarness>(query: HarnessQuery<T>): Promise<T | null>;
121121

122+
/**
123+
* Searches for an instance of the component corresponding to the given harness type under the
124+
* `HarnessLoader`'s root element, and returns a `ComponentHarness` for the instance on the page
125+
* at the given index. If no matching component exists at that index, an error is thrown.
126+
* @param query A query for a harness to create
127+
* @param index The zero-indexed offset of the matching component instance to return
128+
* @return An instance of the given harness type.
129+
* @throws If a matching component instance can't be found at the given index.
130+
*/
131+
getHarnessAtIndex<T extends ComponentHarness>(query: HarnessQuery<T>, index: number): Promise<T>;
132+
122133
/**
123134
* Searches for all instances of the component corresponding to the given harness type under the
124135
* `HarnessLoader`'s root element, and returns a list `ComponentHarness` for each instance.
@@ -127,6 +138,14 @@ export interface HarnessLoader {
127138
*/
128139
getAllHarnesses<T extends ComponentHarness>(query: HarnessQuery<T>): Promise<T[]>;
129140

141+
/**
142+
* Searches for all instances of the component corresponding to the given harness type under the
143+
* `HarnessLoader`'s root element, and returns the total count of all matching components.
144+
* @param query A query for a harness to create
145+
* @return An integer indicating the number of instances that were found.
146+
*/
147+
countHarnesses<T extends ComponentHarness>(query: HarnessQuery<T>): Promise<number>;
148+
130149
/**
131150
* Searches for an instance of the component corresponding to the given harness type under the
132151
* `HarnessLoader`'s root element, and returns a boolean indicating if any were found.
@@ -500,6 +519,20 @@ export abstract class ContentContainerComponentHarness<S extends string = string
500519
return (await this.getRootHarnessLoader()).getHarnessOrNull(query);
501520
}
502521

522+
/**
523+
* Gets a matching harness for the given query and index within the current harness's content.
524+
* @param query The harness query to search for.
525+
* @param index The zero-indexed offset of the component to find.
526+
* @returns The first harness matching the given query.
527+
* @throws If no matching harness is found.
528+
*/
529+
async getHarnessAtIndex<T extends ComponentHarness>(
530+
query: HarnessQuery<T>,
531+
index: number,
532+
): Promise<T> {
533+
return (await this.getRootHarnessLoader()).getHarnessAtIndex(query, index);
534+
}
535+
503536
/**
504537
* Gets all matching harnesses for the given query within the current harness's content.
505538
* @param query The harness query to search for.
@@ -509,12 +542,23 @@ export abstract class ContentContainerComponentHarness<S extends string = string
509542
return (await this.getRootHarnessLoader()).getAllHarnesses(query);
510543
}
511544

545+
/**
546+
* Returns the number of matching harnesses for the given query within the current harness's
547+
* content.
548+
*
549+
* @param query The harness query to search for.
550+
* @returns The number of matching harnesses for the given query.
551+
*/
552+
async countHarnesses<T extends ComponentHarness>(query: HarnessQuery<T>): Promise<number> {
553+
return (await this.getRootHarnessLoader()).countHarnesses(query);
554+
}
555+
512556
/**
513557
* Checks whether there is a matching harnesses for the given query within the current harness's
514558
* content.
515559
*
516560
* @param query The harness query to search for.
517-
* @returns Whetehr there is matching harnesses for the given query.
561+
* @returns Whether there is matching harnesses for the given query.
518562
*/
519563
async hasHarness<T extends ComponentHarness>(query: HarnessQuery<T>): Promise<boolean> {
520564
return (await this.getRootHarnessLoader()).hasHarness(query);

src/cdk/testing/harness-environment.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,30 @@ export abstract class HarnessEnvironment<E> implements HarnessLoader, LocatorFac
248248
return this.locatorForOptional(query)();
249249
}
250250

251+
/**
252+
* Searches for an instance of the component corresponding to the given harness type and index
253+
* under the `HarnessEnvironment`'s root element, and returns a `ComponentHarness` for that
254+
* instance. The index specifies the offset of the component to find. If no matching
255+
* component is found at that index, an error is thrown.
256+
* @param query A query for a harness to create
257+
* @param index The zero-indexed offset of the component to find
258+
* @return An instance of the given harness type
259+
* @throws If a matching component instance can't be found.
260+
*/
261+
async getHarnessAtIndex<T extends ComponentHarness>(
262+
query: HarnessQuery<T>,
263+
offset: number,
264+
): Promise<T> {
265+
if (offset < 0) {
266+
throw Error('Index must not be negative');
267+
}
268+
const harnesses = await this.locatorForAll(query)();
269+
if (offset >= harnesses.length) {
270+
throw Error(`No harness was located at index ${offset}`);
271+
}
272+
return harnesses[offset];
273+
}
274+
251275
/**
252276
* Searches for all instances of the component corresponding to the given harness type under the
253277
* `HarnessEnvironment`'s root element, and returns a list `ComponentHarness` for each instance.
@@ -258,6 +282,16 @@ export abstract class HarnessEnvironment<E> implements HarnessLoader, LocatorFac
258282
return this.locatorForAll(query)();
259283
}
260284

285+
/**
286+
* Searches for all instance of the component corresponding to the given harness type under the
287+
* `HarnessEnvironment`'s root element, and returns the number that were found.
288+
* @param query A query for a harness to create
289+
* @return The number of instances that were found.
290+
*/
291+
async countHarnesses<T extends ComponentHarness>(query: HarnessQuery<T>): Promise<number> {
292+
return (await this.locatorForAll(query)()).length;
293+
}
294+
261295
/**
262296
* Searches for an instance of the component corresponding to the given harness type under the
263297
* `HarnessEnvironment`'s root element, and returns a boolean indicating if any were found.

src/cdk/testing/test-harnesses.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,12 @@ are used to create `ComponentHarness` instances for elements under this root ele
134134
| `getChildLoader(selector: string): Promise<HarnessLoader>` | Searches for an element matching the given selector below the root element of this `HarnessLoader`, and returns a new `HarnessLoader` rooted at the first matching element |
135135
| `getAllChildLoaders(selector: string): Promise<HarnessLoader[]>` | Acts like `getChildLoader`, but returns an array of `HarnessLoader` instances, one for each matching element, rather than just the first matching element |
136136
| `getHarness<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T> \| HarnessPredicate<T>): Promise<T>` | Searches for an instance of the given `ComponentHarness` class or `HarnessPredicate` below the root element of this `HarnessLoader` and returns an instance of the harness corresponding to the first matching element |
137+
| `getHarnessAtIndex<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T> \| HarnessPredicate<T>, index: number): Promise<T>` | Acts like `getHarness`, but returns an instance of the harness corresponding to the matching element with the given index (zero-indexed) |
137138
| `getAllHarnesses<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T> \| HarnessPredicate<T>): Promise<T[]>` | Acts like `getHarness`, but returns an array of harness instances, one for each matching element, rather than just the first matching element |
139+
| `countHarnesses<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T> \| HarnessPredicate<T>): Promise<number>` | Counts the number of instances of the given `ComponentHarness` class or `HarnessPredicate` below the root element of this `HarnessLoader`, and returns the result. |
140+
| `hasHarness<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T> \| HarnessPredicate<T>): Promise<boolean>` | Returns true if an instance of the given `ComponentHarness` class or `HarnessPredicate` exists below the root element of this `HarnessLoader` |
138141

139-
Calls to `getHarness` and `getAllHarnesses` can either take `ComponentHarness` subclass or a
142+
Calls to the harness functions can either take `ComponentHarness` subclass or a
140143
`HarnessPredicate`. `HarnessPredicate` applies additional restrictions to the search (e.g. searching
141144
for a button that has some particular text, etc). The
142145
[details of `HarnessPredicate`](#filtering-harness-instances-with-harnesspredicate) are discussed in

src/material/input/testing/input-harness.spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ describe('MatInputHarness', () => {
1818
});
1919

2020
it('should load all input harnesses', async () => {
21-
const inputs = await loader.getAllHarnesses(MatInputHarness);
22-
expect(inputs.length).toBe(7);
21+
expect(await loader.countHarnesses(MatInputHarness)).toBe(7);
2322
});
2423

2524
it('should load input with specific id', async () => {

0 commit comments

Comments
 (0)