/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import { AsyncFactoryFn, ComponentHarness, ComponentHarnessConstructor, HarnessLoader, HarnessPredicate, LocatorFactory } from './component-harness'; import {TestElement} from './test-element'; /** * 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 * functionality of both a `HarnessLoader` and `LocatorFactory`. This class is generic on the raw * element type, `E`, used by the particular test environment. */ export abstract class HarnessEnvironment implements HarnessLoader, LocatorFactory { // Implemented as part of the `LocatorFactory` interface. rootElement: TestElement; protected constructor(protected rawRootElement: E) { this.rootElement = this.createTestElement(rawRootElement); } // Implemented as part of the `LocatorFactory` interface. documentRootLocatorFactory(): LocatorFactory { return this.createEnvironment(this.getDocumentRoot()); } // Implemented as part of the `LocatorFactory` interface. locatorFor(selector: string): AsyncFactoryFn; locatorFor( harnessType: ComponentHarnessConstructor | HarnessPredicate): AsyncFactoryFn; locatorFor( arg: string | ComponentHarnessConstructor | HarnessPredicate) { return async () => { if (typeof arg === 'string') { return this.createTestElement(await this._assertElementFound(arg)); } else { return this._assertHarnessFound(arg); } }; } // Implemented as part of the `LocatorFactory` interface. locatorForOptional(selector: string): AsyncFactoryFn; locatorForOptional( harnessType: ComponentHarnessConstructor | HarnessPredicate): AsyncFactoryFn; locatorForOptional( arg: string | ComponentHarnessConstructor | HarnessPredicate) { return async () => { if (typeof arg === 'string') { const element = (await this.getAllRawElements(arg))[0]; return element ? this.createTestElement(element) : null; } else { const candidates = await this._getAllHarnesses(arg); return candidates[0] || null; } }; } // Implemented as part of the `LocatorFactory` interface. locatorForAll(selector: string): AsyncFactoryFn; locatorForAll( harnessType: ComponentHarnessConstructor | HarnessPredicate): AsyncFactoryFn; locatorForAll( arg: string | ComponentHarnessConstructor | HarnessPredicate) { return async () => { if (typeof arg === 'string') { return (await this.getAllRawElements(arg)).map(e => this.createTestElement(e)); } else { return this._getAllHarnesses(arg); } }; } // Implemented as part of the `HarnessLoader` interface. getHarness( harnessType: ComponentHarnessConstructor | HarnessPredicate): Promise { return this.locatorFor(harnessType)(); } // Implemented as part of the `HarnessLoader` interface. getAllHarnesses( harnessType: ComponentHarnessConstructor | HarnessPredicate): Promise { return this.locatorForAll(harnessType)(); } // Implemented as part of the `HarnessLoader` interface. async getChildLoader(selector: string): Promise { return this.createEnvironment(await this._assertElementFound(selector)); } // Implemented as part of the `HarnessLoader` interface. async getAllChildLoaders(selector: string): Promise { return (await this.getAllRawElements(selector)).map(e => this.createEnvironment(e)); } /** Creates a `ComponentHarness` for the given harness type with the given raw host element. */ protected createComponentHarness( harnessType: ComponentHarnessConstructor, element: E): T { return new harnessType(this.createEnvironment(element)); } /** Gets the root element for the document. */ protected abstract getDocumentRoot(): E; /** Creates a `TestElement` from a raw element. */ protected abstract createTestElement(element: E): TestElement; /** Creates a `HarnessLoader` rooted at the given raw element. */ protected abstract createEnvironment(element: E): HarnessEnvironment; /** * Gets a list of all elements matching the given selector under this environment's root element. */ protected abstract getAllRawElements(selector: string): Promise; private async _getAllHarnesses( harnessType: ComponentHarnessConstructor | HarnessPredicate): Promise { const harnessPredicate = harnessType instanceof HarnessPredicate ? harnessType : new HarnessPredicate(harnessType, {}); const elements = await this.getAllRawElements(harnessPredicate.getSelector()); return harnessPredicate.filter(elements.map( element => this.createComponentHarness(harnessPredicate.harnessType, element))); } private async _assertElementFound(selector: string): Promise { const element = (await this.getAllRawElements(selector))[0]; if (!element) { throw Error(`Expected to find element matching selector: "${selector}", but none was found`); } return element; } private async _assertHarnessFound( harnessType: ComponentHarnessConstructor | HarnessPredicate): Promise { const harness = (await this._getAllHarnesses(harnessType))[0]; if (!harness) { throw _getErrorForMissingHarness(harnessType); } return harness; } } function _getErrorForMissingHarness( harnessType: ComponentHarnessConstructor | HarnessPredicate): Error { const harnessPredicate = harnessType instanceof HarnessPredicate ? harnessType : new HarnessPredicate(harnessType, {}); const {name, hostSelector} = harnessPredicate.harnessType; let 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); }