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.
This commit is contained in:
mmalerba
2019-06-19 13:48:33 -07:00
committed by Jeremy Elbourn
parent 4b82786333
commit 6a7fc8156e
9 changed files with 296 additions and 80 deletions
+106 -21
View File
@@ -9,7 +9,13 @@
import {TestElement} from './test-element';
/** An async function that returns a promise when called. */
export type AsyncFn<T> = () => Promise<T>;
export type AsyncFactoryFn<T> = () => Promise<T>;
/** An async function that takes an item and returns a boolean promise */
export type AsyncPredicate<T> = (item: T) => Promise<boolean>;
/** An async function that takes an item and an option value and returns a boolean promise. */
export type AsyncOptionPredicate<T, O> = (item: T, option: O) => Promise<boolean>;
/**
* Interface used to load ComponentHarness objects. This interface is used by test authors to
@@ -44,8 +50,8 @@ export interface HarnessLoader {
* @return An instance of the given harness type
* @throws If a matching component instance can't be found.
*/
getHarness<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
Promise<T>;
getHarness<T extends ComponentHarness>(
harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): Promise<T>;
/**
* Searches for all instances of the component corresponding to the given harness type under the
@@ -53,8 +59,8 @@ export interface HarnessLoader {
* @param harnessType The type of harness to create
* @return A list instances of the given harness type.
*/
getAllHarnesses<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
Promise<T[]>;
getAllHarnesses<T extends ComponentHarness>(
harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): Promise<T[]>;
}
/**
@@ -78,7 +84,7 @@ export interface LocatorFactory {
* @return An asynchronous locator function that searches for elements with the given selector,
* and either finds one or throws an error
*/
locatorFor(selector: string): AsyncFn<TestElement>;
locatorFor(selector: string): AsyncFactoryFn<TestElement>;
/**
* Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a
@@ -89,8 +95,8 @@ export interface LocatorFactory {
* @return An asynchronous locator function that searches components matching the given harness
* type, and either returns a `ComponentHarness` for the component, or throws an error.
*/
locatorFor<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
AsyncFn<T>;
locatorFor<T extends ComponentHarness>(
harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): AsyncFactoryFn<T>;
/**
* Creates an asynchronous locator function that can be used to search for elements with the given
@@ -101,7 +107,7 @@ export interface LocatorFactory {
* @return An asynchronous locator function that searches for elements with the given selector,
* and either finds one or returns null.
*/
locatorForOptional(selector: string): AsyncFn<TestElement | null>;
locatorForOptional(selector: string): AsyncFactoryFn<TestElement | null>;
/**
* Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a
@@ -112,8 +118,8 @@ export interface LocatorFactory {
* @return An asynchronous locator function that searches components matching the given harness
* type, and either returns a `ComponentHarness` for the component, or null if none is found.
*/
locatorForOptional<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
AsyncFn<T | null>;
locatorForOptional<T extends ComponentHarness>(
harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): AsyncFactoryFn<T | null>;
/**
* Creates an asynchronous locator function that can be used to search for a list of elements with
@@ -123,7 +129,7 @@ export interface LocatorFactory {
* @return An asynchronous locator function that searches for elements with the given selector,
* and either finds one or throws an error
*/
locatorForAll(selector: string): AsyncFn<TestElement[]>;
locatorForAll(selector: string): AsyncFactoryFn<TestElement[]>;
/**
* Creates an asynchronous locator function that can be used to find a list of
@@ -134,8 +140,8 @@ export interface LocatorFactory {
* @return An asynchronous locator function that searches components matching the given harness
* type, and returns a list of `ComponentHarness`es.
*/
locatorForAll<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
AsyncFn<T[]>;
locatorForAll<T extends ComponentHarness>(
harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): AsyncFactoryFn<T[]>;
}
/**
@@ -169,7 +175,7 @@ export abstract class ComponentHarness {
* @return An asynchronous locator function that searches for elements with the given selector,
* and either finds one or throws an error
*/
protected locatorFor(selector: string): AsyncFn<TestElement>;
protected locatorFor(selector: string): AsyncFactoryFn<TestElement>;
/**
* Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a
@@ -181,7 +187,7 @@ export abstract class ComponentHarness {
* type, and either returns a `ComponentHarness` for the component, or throws an error.
*/
protected locatorFor<T extends ComponentHarness>(
harnessType: ComponentHarnessConstructor<T>): AsyncFn<T>;
harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): AsyncFactoryFn<T>;
protected locatorFor(arg: any): any {
return this.locatorFactory.locatorFor(arg);
@@ -196,7 +202,7 @@ export abstract class ComponentHarness {
* @return An asynchronous locator function that searches for elements with the given selector,
* and either finds one or returns null.
*/
protected locatorForOptional(selector: string): AsyncFn<TestElement | null>;
protected locatorForOptional(selector: string): AsyncFactoryFn<TestElement | null>;
/**
* Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a
@@ -208,7 +214,7 @@ export abstract class ComponentHarness {
* type, and either returns a `ComponentHarness` for the component, or null if none is found.
*/
protected locatorForOptional<T extends ComponentHarness>(
harnessType: ComponentHarnessConstructor<T>): AsyncFn<T | null>;
harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): AsyncFactoryFn<T | null>;
protected locatorForOptional(arg: any): any {
return this.locatorFactory.locatorForOptional(arg);
@@ -222,7 +228,7 @@ export abstract class ComponentHarness {
* @return An asynchronous locator function that searches for elements with the given selector,
* and either finds one or throws an error
*/
protected locatorForAll(selector: string): AsyncFn<TestElement[]>;
protected locatorForAll(selector: string): AsyncFactoryFn<TestElement[]>;
/**
* Creates an asynchronous locator function that can be used to find a list of
@@ -233,8 +239,8 @@ export abstract class ComponentHarness {
* @return An asynchronous locator function that searches components matching the given harness
* type, and returns a list of `ComponentHarness`es.
*/
protected locatorForAll<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
AsyncFn<T[]>;
protected locatorForAll<T extends ComponentHarness>(
harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): AsyncFactoryFn<T[]>;
protected locatorForAll(arg: any): any {
return this.locatorFactory.locatorForAll(arg);
@@ -252,3 +258,82 @@ export interface ComponentHarnessConstructor<T extends ComponentHarness> {
*/
hostSelector: string;
}
/**
* A class used to associate a ComponentHarness class with predicates functions that can be used to
* filter instances of the class.
*/
export class HarnessPredicate<T extends ComponentHarness> {
private _predicates: AsyncPredicate<T>[] = [];
private _descriptions: string[] = [];
constructor(public harnessType: ComponentHarnessConstructor<T>) {}
/**
* Checks if a string matches the given pattern.
* @param s The string to check, or a Promise for the string to check.
* @param pattern The pattern the string is expected to match. If `pattern` is a string, `s` is
* expected to match exactly. If `pattern` is a regex, a partial match is allowed.
* @return A Promise that resolves to whether the string matches the pattern.
*/
static async stringMatches(s: string | Promise<string>, pattern: string | RegExp):
Promise<boolean> {
s = await s;
return typeof pattern === 'string' ? s === pattern : pattern.test(s);
}
/**
* Adds a predicate function to be run against candidate harnesses.
* @param description A description of this predicate that may be used in error messages.
* @param predicate An async predicate function.
* @return this (for method chaining).
*/
add(description: string, predicate: AsyncPredicate<T>) {
this._descriptions.push(description);
this._predicates.push(predicate);
return this;
}
/**
* Adds a predicate function that depends on an option value to be run against candidate
* harnesses. If the option value is undefined, the predicate will be ignored.
* @param name The name of the option (may be used in error messages).
* @param option The option value.
* @param predicate The predicate function to run if the option value is not undefined.
* @return this (for method chaining).
*/
addOption<O>(name: string, option: O | undefined, predicate: AsyncOptionPredicate<T, O>) {
// Add quotes around strings to differentiate them from other values
const value = typeof option === 'string' ? `"${option}"` : `${option}`;
if (option !== undefined) {
this.add(`${name} = ${value}`, item => predicate(item, option));
}
return this;
}
/**
* Filters a list of harnesses on this predicate.
* @param harnesses The list of harnesses to filter.
* @return A list of harnesses that satisfy this predicate.
*/
async filter(harnesses: T[]): Promise<T[]> {
const results = await Promise.all(harnesses.map(h => this.evaluate(h)));
return harnesses.filter((_, i) => results[i]);
}
/**
* Evaluates whether the given harness satisfies this predicate.
* @param harness The harness to check
* @return A promise that resolves to true if the harness satisfies this predicate,
* and resolves to false otherwise.
*/
async evaluate(harness: T): Promise<boolean> {
const results = await Promise.all(this._predicates.map(p => p(harness)));
return results.reduce((combined, current) => combined && current, true);
}
/** Gets a description of this predicate for use in error messages. */
getDescription() {
return this._descriptions.join(', ');
}
}
@@ -7,14 +7,33 @@
*/
import {
AsyncFn,
AsyncFactoryFn,
ComponentHarness,
ComponentHarnessConstructor,
HarnessLoader,
HarnessPredicate,
LocatorFactory
} from './component-harness';
import {TestElement} from './test-element';
function _getErrorForMissingSelector(selector: string): Error {
return Error(`Expected to find element matching selector: "${selector}", but none was found`);
}
function _getErrorForMissingHarness<T extends ComponentHarness>(
harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): Error {
const harnessPredicate =
harnessType instanceof HarnessPredicate ? harnessType : new HarnessPredicate(harnessType);
const {name, hostSelector} = harnessPredicate.harnessType;
const restrictions = harnessPredicate.getDescription();
let message = `Expected to find element for ${name} matching selector: "${hostSelector}"`;
if (restrictions) {
message += ` (with restrictions: ${restrictions})`;
}
message += ', but none was found';
return Error(message);
}
/**
* Base harness environment class that can be extended to allow `ComponentHarness`es to be used in
* different test environments (e.g. testbed, protractor, etc.). This class implements the
@@ -35,80 +54,82 @@ export abstract class HarnessEnvironment<E> implements HarnessLoader, LocatorFac
}
// Implemented as part of the `LocatorFactory` interface.
locatorFor(selector: string): AsyncFn<TestElement>;
locatorFor<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
AsyncFn<T>;
locatorFor(selector: string): AsyncFactoryFn<TestElement>;
locatorFor<T extends ComponentHarness>(
arg: string | ComponentHarnessConstructor<T>): AsyncFn<TestElement | T> {
harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): AsyncFactoryFn<T>;
locatorFor<T extends ComponentHarness>(
arg: string | ComponentHarnessConstructor<T> | HarnessPredicate<T>):
AsyncFactoryFn<TestElement | T> {
return async () => {
if (typeof arg === 'string') {
const element = await this.getRawElement(arg);
const element = (await this.getAllRawElements(arg))[0];
if (element) {
return this.createTestElement(element);
}
throw _getErrorForMissingSelector(arg);
} else {
const element = await this.getRawElement(arg.hostSelector);
if (element) {
return this.createComponentHarness(arg, element);
const harness = (await this._getAllHarnesses(arg))[0];
if (harness) {
return harness;
}
throw _getErrorForMissingHarness(arg);
}
const selector = typeof arg === 'string' ? arg : arg.hostSelector;
throw Error(`Expected to find element matching selector: "${selector}", but none was found`);
};
}
// Implemented as part of the `LocatorFactory` interface.
locatorForOptional(selector: string): AsyncFn<TestElement | null>;
locatorForOptional<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
AsyncFn<T | null>;
locatorForOptional(selector: string): AsyncFactoryFn<TestElement | null>;
locatorForOptional<T extends ComponentHarness>(
arg: string | ComponentHarnessConstructor<T>): AsyncFn<TestElement | T | null> {
harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): AsyncFactoryFn<T | null>;
locatorForOptional<T extends ComponentHarness>(
arg: string | ComponentHarnessConstructor<T> | HarnessPredicate<T>):
AsyncFactoryFn<TestElement | T | null> {
return async () => {
if (typeof arg === 'string') {
const element = await this.getRawElement(arg);
const element = (await this.getAllRawElements(arg))[0];
return element ? this.createTestElement(element) : null;
} else {
const element = await this.getRawElement(arg.hostSelector);
return element ? this.createComponentHarness(arg, element) : null;
const candidates = await this._getAllHarnesses(arg);
return candidates[0] || null;
}
};
}
// Implemented as part of the `LocatorFactory` interface.
locatorForAll(selector: string): AsyncFn<TestElement[]>;
locatorForAll<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
AsyncFn<T[]>;
locatorForAll(selector: string): AsyncFactoryFn<TestElement[]>;
locatorForAll<T extends ComponentHarness>(
arg: string | ComponentHarnessConstructor<T>): AsyncFn<TestElement[] | T[]> {
harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): AsyncFactoryFn<T[]>;
locatorForAll<T extends ComponentHarness>(
arg: string | ComponentHarnessConstructor<T> | HarnessPredicate<T>):
AsyncFactoryFn<TestElement[] | T[]> {
return async () => {
if (typeof arg === 'string') {
return (await this.getAllRawElements(arg)).map(e => this.createTestElement(e));
} else {
return (await this.getAllRawElements(arg.hostSelector))
.map(e => this.createComponentHarness(arg, e));
return this._getAllHarnesses(arg);
}
};
}
// Implemented as part of the `HarnessLoader` interface.
getHarness<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
Promise<T> {
getHarness<T extends ComponentHarness>(
harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): Promise<T> {
return this.locatorFor(harnessType)();
}
// Implemented as part of the `HarnessLoader` interface.
getAllHarnesses<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
Promise<T[]> {
getAllHarnesses<T extends ComponentHarness>(
harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): Promise<T[]> {
return this.locatorForAll(harnessType)();
}
// Implemented as part of the `HarnessLoader` interface.
async getChildLoader(selector: string): Promise<HarnessLoader> {
const element = await this.getRawElement(selector);
const element = (await this.getAllRawElements(selector))[0];
if (element) {
return this.createEnvironment(element);
}
throw Error(`Expected to find element matching selector: "${selector}", but none was found`);
throw _getErrorForMissingSelector(selector);
}
// Implemented as part of the `HarnessLoader` interface.
@@ -131,14 +152,17 @@ export abstract class HarnessEnvironment<E> implements HarnessLoader, LocatorFac
/** Creates a `HarnessLoader` rooted at the given raw element. */
protected abstract createEnvironment(element: E): HarnessEnvironment<E>;
/**
* Gets the first element matching the given selector under this environment's root element, or
* null if no elements match.
*/
protected abstract getRawElement(selector: string): Promise<E | null>;
/**
* Gets a list of all elements matching the given selector under this environment's root element.
*/
protected abstract getAllRawElements(selector: string): Promise<E[]>;
private async _getAllHarnesses<T extends ComponentHarness>(
harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): Promise<T[]> {
const harnessPredicate =
harnessType instanceof HarnessPredicate ? harnessType : new HarnessPredicate(harnessType);
const elements = await this.getAllRawElements(harnessPredicate.harnessType.hostSelector);
return harnessPredicate.filter(elements.map(
element => this.createComponentHarness(harnessPredicate.harnessType, element)));
}
}
@@ -35,14 +35,13 @@ export class ProtractorHarnessEnvironment extends HarnessEnvironment<ElementFind
return new ProtractorHarnessEnvironment(element);
}
protected async getRawElement(selector: string): Promise<ElementFinder | null> {
const element = this.rawRootElement.element(by.css(selector));
return await element.isPresent() ? element : null;
}
protected async getAllRawElements(selector: string): Promise<ElementFinder[]> {
const elements = this.rawRootElement.all(by.css(selector));
return elements.reduce(
(result: ElementFinder[], el: ElementFinder) => el ? result.concat([el]) : result, []);
const elementFinderArray = this.rawRootElement.all(by.css(selector));
const length = await elementFinderArray.count();
const elements: ElementFinder[] = [];
for (let i = 0; i < length; i++) {
elements.push(elementFinderArray.get(i));
}
return elements;
}
}
@@ -48,11 +48,6 @@ export class TestbedHarnessEnvironment extends HarnessEnvironment<Element> {
return new TestbedHarnessEnvironment(element, this._fixture);
}
protected async getRawElement(selector: string): Promise<Element | null> {
await this._stabilize();
return this.rawRootElement.querySelector(selector) || null;
}
protected async getAllRawElements(selector: string): Promise<Element[]> {
await this._stabilize();
return Array.from(this.rawRootElement.querySelectorAll(selector));
@@ -40,6 +40,14 @@ export class MainComponentHarness extends ComponentHarness {
readonly optionalSubComponent = this.locatorForOptional(SubComponentHarness);
readonly errorSubComponent = this.locatorFor(WrongComponentHarness);
readonly fourItemLists = this.locatorForAll(SubComponentHarness.with({itemCount: 4}));
readonly toolsLists = this.locatorForAll(SubComponentHarness.with({title: 'List of test tools'}));
readonly fourItemToolsLists =
this.locatorForAll(SubComponentHarness.with({title: 'List of test tools', itemCount: 4}));
readonly testLists = this.locatorForAll(SubComponentHarness.with({title: /test/}));
readonly requiredFourIteamToolsLists =
this.locatorFor(SubComponentHarness.with({title: 'List of test tools', itemCount: 4}));
private _testTools = this.locatorFor(SubComponentHarness);
async increaseCounter(times: number) {
@@ -6,12 +6,22 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ComponentHarness} from '../../component-harness';
import {ComponentHarness, HarnessPredicate} from '../../component-harness';
import {TestElement} from '../../test-element';
/** @dynamic */
export class SubComponentHarness extends ComponentHarness {
static readonly hostSelector = 'test-sub';
static with(options: {title?: string | RegExp, itemCount?: number} = {}) {
return new HarnessPredicate(SubComponentHarness)
.addOption('title', options.title,
async (harness, title) =>
HarnessPredicate.stringMatches((await harness.title()).text(), title))
.addOption('item count', options.itemCount,
async (harness, count) => (await harness.getItems()).length === count);
}
readonly title = this.locatorFor('h2');
readonly getItems = this.locatorForAll('li');
readonly globalElement = this.documentRootLocatorFactory().locatorFor('#username');
@@ -52,8 +52,9 @@ describe('ProtractorHarnessEnvironment', () => {
await countersLoader.getHarness(SubComponentHarness);
fail('Expected to throw');
} catch (e) {
expect(e.message)
.toBe('Expected to find element matching selector: "test-sub", but none was found');
expect(e.message).toBe(
'Expected to find element for SubComponentHarness matching selector:' +
' "test-sub", but none was found');
}
});
@@ -114,7 +115,8 @@ describe('ProtractorHarnessEnvironment', () => {
fail('Expected to throw');
} catch (e) {
expect(e.message).toBe(
'Expected to find element matching selector: "wrong-selector", but none was found');
'Expected to find element for WrongComponentHarness matching selector:' +
' "wrong-selector", but none was found');
}
});
@@ -135,10 +137,11 @@ describe('ProtractorHarnessEnvironment', () => {
expect(await items1[0].text()).toBe('Protractor');
expect(await items1[1].text()).toBe('TestBed');
expect(await items1[2].text()).toBe('Other');
expect(items2.length).toBe(3);
expect(items2.length).toBe(4);
expect(await items2[0].text()).toBe('Unit Test');
expect(await items2[1].text()).toBe('Integration Test');
expect(await items2[2].text()).toBe('Performance Test');
expect(await items2[3].text()).toBe('Mutation Test');
});
it('should wait for async operation to complete', async () => {
@@ -228,4 +231,48 @@ describe('ProtractorHarnessEnvironment', () => {
.not.toBe(await button.text());
});
});
describe('HarnessPredicate', () => {
let harness: MainComponentHarness;
beforeEach(async () => {
harness = await ProtractorHarnessEnvironment.loader().getHarness(MainComponentHarness);
});
it('should find subcomponents with specific item count', async () => {
const fourItemLists = await harness.fourItemLists();
expect(fourItemLists.length).toBe(1);
expect(await (await fourItemLists[0].title()).text()).toBe('List of test methods');
});
it('should find subcomponents with specific title', async () => {
const toolsLists = await harness.toolsLists();
expect(toolsLists.length).toBe(1);
expect(await (await toolsLists[0].title()).text()).toBe('List of test tools');
});
it('should find no subcomponents if predicate does not match', async () => {
const fourItemToolsLists = await harness.fourItemToolsLists();
expect(fourItemToolsLists.length).toBe(0);
});
it('should find subcomponents with title regex', async () => {
const testLists = await harness.testLists();
expect(testLists.length).toBe(2);
expect(await (await testLists[0].title()).text()).toBe('List of test tools');
expect(await (await testLists[1].title()).text()).toBe('List of test methods');
});
it('should error if predicate does not match but a harness is required', async () => {
try {
await harness.requiredFourIteamToolsLists();
fail('Expected to throw');
} catch (e) {
expect(e.message).toBe(
'Expected to find element for SubComponentHarness matching selector: "test-sub"' +
' (with restrictions: title = "List of test tools", item count = 4),' +
' but none was found');
}
});
});
});
@@ -50,7 +50,7 @@ export class TestMainComponent {
this.asyncCounter = 0;
this.memo = '';
this.testTools = ['Protractor', 'TestBed', 'Other'];
this.testMethods = ['Unit Test', 'Integration Test', 'Performance Test'];
this.testMethods = ['Unit Test', 'Integration Test', 'Performance Test', 'Mutation Test'];
setTimeout(() => {
this.asyncCounter = 5;
this._cdr.markForCheck();
@@ -67,8 +67,9 @@ describe('TestbedHarnessEnvironment', () => {
await countersLoader.getHarness(SubComponentHarness);
fail('Expected to throw');
} catch (e) {
expect(e.message)
.toBe('Expected to find element matching selector: "test-sub", but none was found');
expect(e.message).toBe(
'Expected to find element for SubComponentHarness matching selector:' +
' "test-sub", but none was found');
}
});
@@ -130,7 +131,8 @@ describe('TestbedHarnessEnvironment', () => {
fail('Expected to throw');
} catch (e) {
expect(e.message).toBe(
'Expected to find element matching selector: "wrong-selector", but none was found');
'Expected to find element for WrongComponentHarness matching selector:' +
' "wrong-selector", but none was found');
}
});
@@ -151,10 +153,11 @@ describe('TestbedHarnessEnvironment', () => {
expect(await items1[0].text()).toBe('Protractor');
expect(await items1[1].text()).toBe('TestBed');
expect(await items1[2].text()).toBe('Other');
expect(items2.length).toBe(3);
expect(items2.length).toBe(4);
expect(await items2[0].text()).toBe('Unit Test');
expect(await items2[1].text()).toBe('Integration Test');
expect(await items2[2].text()).toBe('Performance Test');
expect(await items2[3].text()).toBe('Mutation Test');
});
it('should wait for async operation to complete', async () => {
@@ -245,4 +248,49 @@ describe('TestbedHarnessEnvironment', () => {
expect(activeElementText()).not.toBe(await button.text());
});
});
describe('HarnessPredicate', () => {
let harness: MainComponentHarness;
beforeEach(async () => {
harness =
await TestbedHarnessEnvironment.harnessForFixture(fixture, MainComponentHarness);
});
it('should find subcomponents with specific item count', async () => {
const fourItemLists = await harness.fourItemLists();
expect(fourItemLists.length).toBe(1);
expect(await (await fourItemLists[0].title()).text()).toBe('List of test methods');
});
it('should find subcomponents with specific title', async () => {
const toolsLists = await harness.toolsLists();
expect(toolsLists.length).toBe(1);
expect(await (await toolsLists[0].title()).text()).toBe('List of test tools');
});
it('should find no subcomponents if predicate does not match', async () => {
const fourItemToolsLists = await harness.fourItemToolsLists();
expect(fourItemToolsLists.length).toBe(0);
});
it('should find subcomponents with title regex', async () => {
const testLists = await harness.testLists();
expect(testLists.length).toBe(2);
expect(await (await testLists[0].title()).text()).toBe('List of test tools');
expect(await (await testLists[1].title()).text()).toBe('List of test methods');
});
it('should error if predicate does not match but a harness is required', async () => {
try {
await harness.requiredFourIteamToolsLists();
fail('Expected to throw');
} catch (e) {
expect(e.message).toBe(
'Expected to find element for SubComponentHarness matching selector: "test-sub"' +
' (with restrictions: title = "List of test tools", item count = 4),' +
' but none was found');
}
});
});
});