Skip to content

refactor(cdk-experimental/testing): Refactor the ComponentHarness API #16234

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
316 changes: 190 additions & 126 deletions src/cdk-experimental/testing/component-harness.ts

Large diffs are not rendered by default.

144 changes: 144 additions & 0 deletions src/cdk-experimental/testing/harness-environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/**
* @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 {
AsyncFn,
ComponentHarness,
ComponentHarnessConstructor,
HarnessLoader,
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<E> 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): AsyncFn<TestElement>;
locatorFor<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
AsyncFn<T>;
locatorFor<T extends ComponentHarness>(
arg: string | ComponentHarnessConstructor<T>): AsyncFn<TestElement | T> {
return async () => {
if (typeof arg === 'string') {
const element = await this.getRawElement(arg);
if (element) {
return this.createTestElement(element);
}
} else {
const element = await this.getRawElement(arg.hostSelector);
if (element) {
return this.createComponentHarness(arg, element);
}
}
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<T extends ComponentHarness>(
arg: string | ComponentHarnessConstructor<T>): AsyncFn<TestElement | T | null> {
return async () => {
if (typeof arg === 'string') {
const element = await this.getRawElement(arg);
return element ? this.createTestElement(element) : null;
} else {
const element = await this.getRawElement(arg.hostSelector);
return element ? this.createComponentHarness(arg, element) : null;
}
};
}

// Implemented as part of the `LocatorFactory` interface.
locatorForAll(selector: string): AsyncFn<TestElement[]>;
locatorForAll<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
AsyncFn<T[]>;
locatorForAll<T extends ComponentHarness>(
arg: string | ComponentHarnessConstructor<T>): AsyncFn<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));
}
};
}

// Implemented as part of the `HarnessLoader` interface.
getHarness<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
Promise<T> {
return this.locatorFor(harnessType)();
}

// Implemented as part of the `HarnessLoader` interface.
getAllHarnesses<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<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);
if (element) {
return this.createEnvironment(element);
}
throw Error(`Expected to find element matching selector: "${selector}", but none was found`);
}

// Implemented as part of the `HarnessLoader` interface.
async getAllChildLoaders(selector: string): Promise<HarnessLoader[]> {
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<T extends ComponentHarness>(
harnessType: ComponentHarnessConstructor<T>, 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<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[]>;
}
170 changes: 0 additions & 170 deletions src/cdk-experimental/testing/protractor.ts

This file was deleted.

10 changes: 10 additions & 0 deletions src/cdk-experimental/testing/protractor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* @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
*/

export * from './protractor-element';
export * from './protractor-harness-environment';
53 changes: 53 additions & 0 deletions src/cdk-experimental/testing/protractor/protractor-element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* @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 {browser, ElementFinder} from 'protractor';
import {TestElement} from '../test-element';

/** A `TestElement` implementation for Protractor. */
export class ProtractorElement implements TestElement {
constructor(readonly element: ElementFinder) {}

async blur(): Promise<void> {
return browser.executeScript('arguments[0].blur()', this.element);
}

async clear(): Promise<void> {
return this.element.clear();
}

async click(): Promise<void> {
return this.element.click();
}

async focus(): Promise<void> {
return browser.executeScript('arguments[0].focus()', this.element);
}

async getCssValue(property: string): Promise<string> {
return this.element.getCssValue(property);
}

async hover(): Promise<void> {
return browser.actions()
.mouseMove(await this.element.getWebElement())
.perform();
}

async sendKeys(keys: string): Promise<void> {
return this.element.sendKeys(keys);
}

async text(): Promise<string> {
return this.element.getText();
}

async getAttribute(name: string): Promise<string|null> {
return this.element.getAttribute(name);
}
}
Loading