mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(cdk-experimental/testing): refactor the ComponentHarness API (#16234)
Renames several APIs for readability
This commit is contained in:
@@ -8,183 +8,247 @@
|
||||
|
||||
import {TestElement} from './test-element';
|
||||
|
||||
/** Options that can be specified when querying for an Element. */
|
||||
export interface QueryOptions {
|
||||
/**
|
||||
* Whether the found element can be null. If allowNull is set, the searching function will always
|
||||
* try to fetch the element at once. When the element cannot be found, the searching function
|
||||
* should return null if allowNull is set to true, throw an error if allowNull is set to false.
|
||||
* If allowNull is not set, the framework will choose the behaviors that make more sense for each
|
||||
* test type (e.g. for unit test, the framework will make sure the element is not null; otherwise
|
||||
* throw an error); however, the internal behavior is not guaranteed and user should not rely on
|
||||
* it. Note that in most cases, you don't need to care about whether an element is present when
|
||||
* loading the element and don't need to set this parameter unless you do want to check whether
|
||||
* the element is present when calling the searching function. e.g. you want to make sure some
|
||||
* element is not there when loading the element in order to check whether a "ngif" works well.
|
||||
*/
|
||||
allowNull?: boolean;
|
||||
/**
|
||||
* If global is set to true, the selector will match any element on the page and is not limited to
|
||||
* the root of the harness. If global is unset or set to false, the selector will only find
|
||||
* elements under the current root.
|
||||
*/
|
||||
global?: boolean;
|
||||
}
|
||||
/** An async function that returns a promise when called. */
|
||||
export type AsyncFn<T> = () => Promise<T>;
|
||||
|
||||
/** Interface that is used to find elements in the DOM and create harnesses for them. */
|
||||
export interface HarnessLocator {
|
||||
/**
|
||||
* Interface used to load ComponentHarness objects. This interface is used by test authors to
|
||||
* instantiate `ComponentHarness`es.
|
||||
*/
|
||||
export interface HarnessLoader {
|
||||
/**
|
||||
* Get the host element of locator.
|
||||
* Searches for an element with the given selector under the current instances's root element,
|
||||
* and returns a `HarnessLoader` rooted at the matching element. If multiple elements match the
|
||||
* selector, the first is used. If no elements match, an error is thrown.
|
||||
* @param selector The selector for the root element of the new `HarnessLoader`
|
||||
* @return A `HarnessLoader` rooted at the element matching the given selector.
|
||||
* @throws If a matching element can't be found.
|
||||
*/
|
||||
host(): TestElement;
|
||||
getChildLoader(selector: string): Promise<HarnessLoader>;
|
||||
|
||||
/**
|
||||
* Search the first matched test element.
|
||||
* @param selector The CSS selector of the test elements.
|
||||
* @param options Optional, extra searching options
|
||||
* Searches for all elements with the given selector under the current instances's root element,
|
||||
* and returns an array of `HarnessLoader`s, one for each matching element, rooted at that
|
||||
* element.
|
||||
* @param selector The selector for the root element of the new `HarnessLoader`
|
||||
* @return A list of `HarnessLoader`s, one for each matching element, rooted at that element.
|
||||
*/
|
||||
querySelector(selector: string, options?: QueryOptions): Promise<TestElement|null>;
|
||||
getAllChildLoaders(selector: string): Promise<HarnessLoader[]>;
|
||||
|
||||
/**
|
||||
* Search all matched test elements under current root by CSS selector.
|
||||
* @param selector The CSS selector of the test elements.
|
||||
* Searches for an instance of the component corresponding to the given harness type under the
|
||||
* `HarnessLoader`'s root element, and returns a `ComponentHarness` for that instance. If multiple
|
||||
* matching components are found, a harness for the first one is returned. If no matching
|
||||
* component is found, an error is thrown.
|
||||
* @param harnessType The type of harness to create
|
||||
* @return An instance of the given harness type
|
||||
* @throws If a matching component instance can't be found.
|
||||
*/
|
||||
querySelectorAll(selector: string): Promise<TestElement[]>;
|
||||
getHarness<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
|
||||
Promise<T>;
|
||||
|
||||
/**
|
||||
* Load the first matched Component Harness.
|
||||
* @param componentHarness Type of user customized harness.
|
||||
* @param root CSS root selector of the new component harness.
|
||||
* @param options Optional, extra searching options
|
||||
* Searches for all instances of the component corresponding to the given harness type under the
|
||||
* `HarnessLoader`'s root element, and returns a list `ComponentHarness` for each instance.
|
||||
* @param harnessType The type of harness to create
|
||||
* @return A list instances of the given harness type.
|
||||
*/
|
||||
load<T extends ComponentHarness>(
|
||||
componentHarness: ComponentHarnessConstructor<T>, root: string,
|
||||
options?: QueryOptions): Promise<T|null>;
|
||||
|
||||
/**
|
||||
* Load all Component Harnesses under current root.
|
||||
* @param componentHarness Type of user customized harness.
|
||||
* @param root CSS root selector of the new component harnesses.
|
||||
*/
|
||||
loadAll<T extends ComponentHarness>(
|
||||
componentHarness: ComponentHarnessConstructor<T>, root: string): Promise<T[]>;
|
||||
getAllHarnesses<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
|
||||
Promise<T[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base Component Harness
|
||||
* This base component harness provides the basic ability to locate element and
|
||||
* sub-component harness. It should be inherited when defining user's own
|
||||
* harness.
|
||||
* Interface used to create asynchronous locator functions used find elements and component
|
||||
* harnesses. This interface is used by `ComponentHarness` authors to create locator functions for
|
||||
* their `ComponentHarenss` subclass.
|
||||
*/
|
||||
export interface LocatorFactory {
|
||||
/** Gets a locator factory rooted at the document root. */
|
||||
documentRootLocatorFactory(): LocatorFactory;
|
||||
|
||||
/** The root element of this `LocatorFactory` as a `TestElement`. */
|
||||
rootElement: TestElement;
|
||||
|
||||
/**
|
||||
* Creates an asynchronous locator function that can be used to search for elements with the given
|
||||
* selector under the root element of this `LocatorFactory`. When the resulting locator function
|
||||
* is invoked, if multiple matching elements are found, the first element is returned. If no
|
||||
* elements are found, an error is thrown.
|
||||
* @param selector The selector for the element that the locator function should search for.
|
||||
* @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>;
|
||||
|
||||
/**
|
||||
* Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a
|
||||
* component matching the given harness type under the root element of this `LocatorFactory`.
|
||||
* When the resulting locator function is invoked, if multiple matching components are found, a
|
||||
* harness for the first one is returned. If no components are found, an error is thrown.
|
||||
* @param harnessType The type of harness to search for.
|
||||
* @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>;
|
||||
|
||||
/**
|
||||
* Creates an asynchronous locator function that can be used to search for elements with the given
|
||||
* selector under the root element of this `LocatorFactory`. When the resulting locator function
|
||||
* is invoked, if multiple matching elements are found, the first element is returned. If no
|
||||
* elements are found, null is returned.
|
||||
* @param selector The selector for the element that the locator function should search for.
|
||||
* @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>;
|
||||
|
||||
/**
|
||||
* Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a
|
||||
* component matching the given harness type under the root element of this `LocatorFactory`.
|
||||
* When the resulting locator function is invoked, if multiple matching components are found, a
|
||||
* harness for the first one is returned. If no components are found, null is returned.
|
||||
* @param harnessType The type of harness to search for.
|
||||
* @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>;
|
||||
|
||||
/**
|
||||
* Creates an asynchronous locator function that can be used to search for a list of elements with
|
||||
* the given selector under the root element of this `LocatorFactory`. When the resulting locator
|
||||
* function is invoked, a list of matching elements is returned.
|
||||
* @param selector The selector for the element that the locator function should search for.
|
||||
* @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[]>;
|
||||
|
||||
/**
|
||||
* Creates an asynchronous locator function that can be used to find a list of
|
||||
* `ComponentHarness`es for all components matching the given harness type under the root element
|
||||
* of this `LocatorFactory`. When the resulting locator function is invoked, a list of
|
||||
* `ComponentHarness`es for the matching components is returned.
|
||||
* @param harnessType The type of harness to search for.
|
||||
* @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[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for component harnesses that all component harness authors should extend. This base
|
||||
* component harness provides the basic ability to locate element and sub-component harness. It
|
||||
* should be inherited when defining user's own harness.
|
||||
*/
|
||||
export abstract class ComponentHarness {
|
||||
constructor(private readonly locator: HarnessLocator) {}
|
||||
constructor(private readonly locatorFactory: LocatorFactory) {}
|
||||
|
||||
/**
|
||||
* Get the host element of component harness.
|
||||
*/
|
||||
host(): TestElement {
|
||||
return this.locator.host();
|
||||
/** Gets a `Promise` for the `TestElement` representing the host element of the component. */
|
||||
async host(): Promise<TestElement> {
|
||||
return this.locatorFactory.rootElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a function to find the first matched test element by CSS
|
||||
* selector.
|
||||
* @param selector The CSS selector of the test element.
|
||||
* Gets a `LocatorFactory` for the document root element. This factory can be used to create
|
||||
* locators for elements that a component creates outside of its own root element. (e.g. by
|
||||
* appending to document.body).
|
||||
*/
|
||||
protected find(selector: string): () => Promise<TestElement>;
|
||||
|
||||
/**
|
||||
* Generate a function to find the first matched test element by CSS
|
||||
* selector.
|
||||
* @param selector The CSS selector of the test element.
|
||||
* @param options Extra searching options
|
||||
*/
|
||||
protected find(selector: string, options: QueryOptions & {allowNull: true}):
|
||||
() => Promise<TestElement|null>;
|
||||
|
||||
/**
|
||||
* Generate a function to find the first matched test element by CSS
|
||||
* selector.
|
||||
* @param selector The CSS selector of the test element.
|
||||
* @param options Extra searching options
|
||||
*/
|
||||
protected find(selector: string, options: QueryOptions): () => Promise<TestElement>;
|
||||
|
||||
/**
|
||||
* Generate a function to find the first matched Component Harness.
|
||||
* @param componentHarness Type of user customized harness.
|
||||
* @param root CSS root selector of the new component harness.
|
||||
*/
|
||||
protected find<T extends ComponentHarness>(
|
||||
componentHarness: ComponentHarnessConstructor<T>,
|
||||
root: string): () => Promise<T>;
|
||||
|
||||
/**
|
||||
* Generate a function to find the first matched Component Harness.
|
||||
* @param componentHarness Type of user customized harness.
|
||||
* @param root CSS root selector of the new component harness.
|
||||
* @param options Extra searching options
|
||||
*/
|
||||
protected find<T extends ComponentHarness>(
|
||||
componentHarness: ComponentHarnessConstructor<T>, root: string,
|
||||
options: QueryOptions & {allowNull: true}): () => Promise<T|null>;
|
||||
|
||||
/**
|
||||
* Generate a function to find the first matched Component Harness.
|
||||
* @param componentHarness Type of user customized harness.
|
||||
* @param root CSS root selector of the new component harness.
|
||||
* @param options Extra searching options
|
||||
*/
|
||||
protected find<T extends ComponentHarness>(
|
||||
componentHarness: ComponentHarnessConstructor<T>, root: string,
|
||||
options: QueryOptions): () => Promise<T>;
|
||||
|
||||
protected find<T extends ComponentHarness>(
|
||||
selectorOrComponentHarness: string|ComponentHarnessConstructor<T>,
|
||||
selectorOrOptions?: string|QueryOptions,
|
||||
options?: QueryOptions): () => Promise<TestElement|T|null> {
|
||||
if (typeof selectorOrComponentHarness === 'string') {
|
||||
const selector = selectorOrComponentHarness;
|
||||
return () => this.locator.querySelector(selector, selectorOrOptions as QueryOptions);
|
||||
} else {
|
||||
const componentHarness = selectorOrComponentHarness;
|
||||
const selector = selectorOrOptions as string;
|
||||
return () => this.locator.load(componentHarness, selector, options);
|
||||
}
|
||||
protected documentRootLocatorFactory(): LocatorFactory {
|
||||
return this.locatorFactory.documentRootLocatorFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a function to find all matched test elements by CSS selector.
|
||||
* @param selector The CSS root selector of elements. It will locate
|
||||
* elements under the current root.
|
||||
* Creates an asynchronous locator function that can be used to search for elements with the given
|
||||
* selector under the host element of this `ComponentHarness`. When the resulting locator function
|
||||
* is invoked, if multiple matching elements are found, the first element is returned. If no
|
||||
* elements are found, an error is thrown.
|
||||
* @param selector The selector for the element that the locator function should search for.
|
||||
* @return An asynchronous locator function that searches for elements with the given selector,
|
||||
* and either finds one or throws an error
|
||||
*/
|
||||
protected findAll(selector: string): () => Promise<TestElement[]>;
|
||||
protected locatorFor(selector: string): AsyncFn<TestElement>;
|
||||
|
||||
/**
|
||||
* Generate a function to find all Component Harnesses under current
|
||||
* component harness.
|
||||
* @param componentHarness Type of user customized harness.
|
||||
* @param root CSS root selector of the new component harnesses. It will
|
||||
* locate harnesses under the current root.
|
||||
* Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a
|
||||
* component matching the given harness type under the host element of this `ComponentHarness`.
|
||||
* When the resulting locator function is invoked, if multiple matching components are found, a
|
||||
* harness for the first one is returned. If no components are found, an error is thrown.
|
||||
* @param harnessType The type of harness to search for.
|
||||
* @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.
|
||||
*/
|
||||
protected findAll<T extends ComponentHarness>(
|
||||
componentHarness: ComponentHarnessConstructor<T>,
|
||||
root: string): () => Promise<T[]>;
|
||||
protected locatorFor<T extends ComponentHarness>(
|
||||
harnessType: ComponentHarnessConstructor<T>): AsyncFn<T>;
|
||||
|
||||
protected findAll<T extends ComponentHarness>(
|
||||
selectorOrComponentHarness: string|ComponentHarnessConstructor<T>,
|
||||
root?: string): () => Promise<TestElement[]|T[]> {
|
||||
if (typeof selectorOrComponentHarness === 'string') {
|
||||
const selector = selectorOrComponentHarness;
|
||||
return () => this.locator.querySelectorAll(selector);
|
||||
} else {
|
||||
const componentHarness = selectorOrComponentHarness;
|
||||
return () => this.locator.loadAll(componentHarness, root as string);
|
||||
}
|
||||
protected locatorFor(arg: any): any {
|
||||
return this.locatorFactory.locatorFor(arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an asynchronous locator function that can be used to search for elements with the given
|
||||
* selector under the host element of this `ComponentHarness`. When the resulting locator function
|
||||
* is invoked, if multiple matching elements are found, the first element is returned. If no
|
||||
* elements are found, null is returned.
|
||||
* @param selector The selector for the element that the locator function should search for.
|
||||
* @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>;
|
||||
|
||||
/**
|
||||
* Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a
|
||||
* component matching the given harness type under the host element of this `ComponentHarness`.
|
||||
* When the resulting locator function is invoked, if multiple matching components are found, a
|
||||
* harness for the first one is returned. If no components are found, null is returned.
|
||||
* @param harnessType The type of harness to search for.
|
||||
* @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.
|
||||
*/
|
||||
protected locatorForOptional<T extends ComponentHarness>(
|
||||
harnessType: ComponentHarnessConstructor<T>): AsyncFn<T | null>;
|
||||
|
||||
protected locatorForOptional(arg: any): any {
|
||||
return this.locatorFactory.locatorForOptional(arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an asynchronous locator function that can be used to search for a list of elements with
|
||||
* the given selector under the host element of this `ComponentHarness`. When the resulting
|
||||
* locator function is invoked, a list of matching elements is returned.
|
||||
* @param selector The selector for the element that the locator function should search for.
|
||||
* @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[]>;
|
||||
|
||||
/**
|
||||
* Creates an asynchronous locator function that can be used to find a list of
|
||||
* `ComponentHarness`es for all components matching the given harness type under the host element
|
||||
* of this `ComponentHarness`. When the resulting locator function is invoked, a list of
|
||||
* `ComponentHarness`es for the matching components is returned.
|
||||
* @param harnessType The type of harness to search for.
|
||||
* @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(arg: any): any {
|
||||
return this.locatorFactory.locatorForAll(arg);
|
||||
}
|
||||
}
|
||||
|
||||
/** Constructor for a ComponentHarness subclass. */
|
||||
export interface ComponentHarnessConstructor<T extends ComponentHarness> {
|
||||
new(locator: HarnessLocator): T;
|
||||
new(locatorFactory: LocatorFactory): T;
|
||||
|
||||
/**
|
||||
* `ComponentHarness` subclasses must specify a static `hostSelector` property that is used to
|
||||
* find the host element for the corresponding component. This property should match the selector
|
||||
* for the Angular component.
|
||||
*/
|
||||
hostSelector: string;
|
||||
}
|
||||
|
||||
@@ -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[]>;
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
/**
|
||||
* @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, by, element as protractorElement, ElementFinder} from 'protractor';
|
||||
|
||||
import {
|
||||
ComponentHarness,
|
||||
ComponentHarnessConstructor,
|
||||
HarnessLocator,
|
||||
QueryOptions
|
||||
} from './component-harness';
|
||||
import {TestElement} from './test-element';
|
||||
|
||||
/**
|
||||
* Component harness factory for protractor.
|
||||
* The function will not try to fetch the host element of harness at once, which
|
||||
* is for performance purpose; however, this is the most common way to load
|
||||
* protractor harness. If you do care whether the host element is present when
|
||||
* loading harness, using the load function that accepts extra searching
|
||||
* options.
|
||||
* @param componentHarness: Type of user defined harness.
|
||||
* @param rootSelector: Optional. CSS selector to specify the root of component.
|
||||
* Set to 'body' by default
|
||||
*/
|
||||
export async function load<T extends ComponentHarness>(
|
||||
componentHarness: ComponentHarnessConstructor<T>,
|
||||
rootSelector: string): Promise<T>;
|
||||
|
||||
/**
|
||||
* Component harness factory for protractor.
|
||||
* @param componentHarness: Type of user defined harness.
|
||||
* @param rootSelector: Optional. CSS selector to specify the root of component.
|
||||
* Set to 'body' by default.
|
||||
* @param options Optional. Extra searching options
|
||||
*/
|
||||
export async function load<T extends ComponentHarness>(
|
||||
componentHarness: ComponentHarnessConstructor<T>, rootSelector?: string,
|
||||
options?: QueryOptions): Promise<T|null>;
|
||||
|
||||
export async function load<T extends ComponentHarness>(
|
||||
componentHarness: ComponentHarnessConstructor<T>, rootSelector = 'body',
|
||||
options?: QueryOptions): Promise<T|null> {
|
||||
const root = await getElement(rootSelector, undefined, options);
|
||||
return root && new componentHarness(new ProtractorLocator(root));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the corresponding ElementFinder for the root of a TestElement.
|
||||
*/
|
||||
export function getElementFinder(testElement: TestElement): ElementFinder {
|
||||
if (testElement instanceof ProtractorElement) {
|
||||
return testElement.element;
|
||||
}
|
||||
|
||||
throw Error(`Expected an instance of ProtractorElement, got ${testElement}`);
|
||||
}
|
||||
|
||||
class ProtractorLocator implements HarnessLocator {
|
||||
private readonly _root: ProtractorElement;
|
||||
|
||||
constructor(private _rootFinder: ElementFinder) {
|
||||
this._root = new ProtractorElement(this._rootFinder);
|
||||
}
|
||||
|
||||
host(): TestElement {
|
||||
return this._root;
|
||||
}
|
||||
|
||||
async querySelector(selector: string, options?: QueryOptions): Promise<TestElement|null> {
|
||||
const finder = await getElement(selector, this._rootFinder, options);
|
||||
return finder && new ProtractorElement(finder);
|
||||
}
|
||||
|
||||
async querySelectorAll(selector: string): Promise<TestElement[]> {
|
||||
const elementFinders = this._rootFinder.all(by.css(selector));
|
||||
return elementFinders.reduce(
|
||||
(result: TestElement[], el: ElementFinder) =>
|
||||
el ? result.concat([new ProtractorElement(el)]) : result,
|
||||
[]);
|
||||
}
|
||||
|
||||
async load<T extends ComponentHarness>(
|
||||
componentHarness: ComponentHarnessConstructor<T>, selector: string,
|
||||
options?: QueryOptions): Promise<T|null> {
|
||||
const root = await getElement(selector, this._rootFinder, options);
|
||||
return root && new componentHarness(new ProtractorLocator(root));
|
||||
}
|
||||
|
||||
async loadAll<T extends ComponentHarness>(
|
||||
componentHarness: ComponentHarnessConstructor<T>,
|
||||
rootSelector: string): Promise<T[]> {
|
||||
const roots = this._rootFinder.all(by.css(rootSelector));
|
||||
return roots.reduce(
|
||||
(result: T[], el: ElementFinder) =>
|
||||
el ? result.concat(new componentHarness(new ProtractorLocator(el))) : result,
|
||||
[]);
|
||||
}
|
||||
}
|
||||
|
||||
class ProtractorElement implements TestElement {
|
||||
constructor(readonly element: ElementFinder) {}
|
||||
|
||||
async blur(): Promise<void> {
|
||||
return this.element['blur']();
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
return this.element.clear();
|
||||
}
|
||||
|
||||
async click(): Promise<void> {
|
||||
return this.element.click();
|
||||
}
|
||||
|
||||
async focus(): Promise<void> {
|
||||
return this.element['focus']();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an element finder based on the CSS selector and root element.
|
||||
* Note that it will check whether the element is present only when
|
||||
* Options.allowNull is set. This is for performance purpose.
|
||||
* @param selector The CSS selector
|
||||
* @param root Optional Search element under the root element. If not set,
|
||||
* search element globally. If options.global is set, root is ignored.
|
||||
* @param options Optional, extra searching options
|
||||
*/
|
||||
async function getElement(selector: string, root?: ElementFinder, options?: QueryOptions):
|
||||
Promise<ElementFinder|null> {
|
||||
const useGlobalRoot = options && !!options.global;
|
||||
const elem = root === undefined || useGlobalRoot ?
|
||||
protractorElement(by.css(selector)) : root.element(by.css(selector));
|
||||
const allowNull = options !== undefined && options.allowNull !== undefined ?
|
||||
options.allowNull : undefined;
|
||||
if (allowNull !== undefined && !(await elem.isPresent())) {
|
||||
if (allowNull) {
|
||||
return null;
|
||||
}
|
||||
throw Error('Cannot find element based on the CSS selector: ' + selector);
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @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 {by, element as protractorElement, ElementFinder} from 'protractor';
|
||||
import {HarnessLoader} from '../component-harness';
|
||||
import {HarnessEnvironment} from '../harness-environment';
|
||||
import {TestElement} from '../test-element';
|
||||
import {ProtractorElement} from './protractor-element';
|
||||
|
||||
/** A `HarnessEnvironment` implementation for Protractor. */
|
||||
export class ProtractorHarnessEnvironment extends HarnessEnvironment<ElementFinder> {
|
||||
protected constructor(rawRootElement: ElementFinder) {
|
||||
super(rawRootElement);
|
||||
}
|
||||
|
||||
/** Creates a `HarnessLoader` rooted at the document root. */
|
||||
static loader(): HarnessLoader {
|
||||
return new ProtractorHarnessEnvironment(protractorElement(by.css('body')));
|
||||
}
|
||||
|
||||
protected getDocumentRoot(): ElementFinder {
|
||||
return protractorElement(by.css('body'));
|
||||
}
|
||||
|
||||
protected createTestElement(element: ElementFinder): TestElement {
|
||||
return new ProtractorElement(element);
|
||||
}
|
||||
|
||||
protected createEnvironment(element: ElementFinder): HarnessEnvironment<ElementFinder> {
|
||||
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, []);
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as protractor from './protractor';
|
||||
import * as testbed from './testbed';
|
||||
|
||||
export * from './component-harness';
|
||||
export * from './harness-environment';
|
||||
export * from './protractor';
|
||||
export * from './test-element';
|
||||
export {protractor, testbed};
|
||||
export * from './testbed';
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
/**
|
||||
* @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 {
|
||||
dispatchFakeEvent,
|
||||
dispatchKeyboardEvent,
|
||||
dispatchMouseEvent,
|
||||
triggerBlur,
|
||||
triggerFocus
|
||||
} from '@angular/cdk/testing';
|
||||
import {ComponentFixture} from '@angular/core/testing';
|
||||
|
||||
import {
|
||||
ComponentHarness,
|
||||
ComponentHarnessConstructor,
|
||||
HarnessLocator,
|
||||
QueryOptions
|
||||
} from './component-harness';
|
||||
import {TestElement} from './test-element';
|
||||
|
||||
/**
|
||||
* Component harness factory for testbed.
|
||||
* @param componentHarness: Type of user defined harness.
|
||||
* @param fixture: Component Fixture of the component to be tested.
|
||||
*/
|
||||
export function load<T extends ComponentHarness>(
|
||||
componentHarness: ComponentHarnessConstructor<T>,
|
||||
fixture: ComponentFixture<{}>): T {
|
||||
const stabilize = async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
};
|
||||
return new componentHarness(new UnitTestLocator(fixture.nativeElement, stabilize));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the corresponding Element for the root of a TestElement.
|
||||
*/
|
||||
export function getNativeElement(testElement: TestElement): Element {
|
||||
if (testElement instanceof UnitTestElement) {
|
||||
return testElement.element;
|
||||
}
|
||||
|
||||
throw Error(`Expected an instance of UnitTestElement, got ${testElement}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Locator implementation for testbed.
|
||||
* Note that, this locator is exposed for internal usage, please do not use it.
|
||||
*/
|
||||
export class UnitTestLocator implements HarnessLocator {
|
||||
private readonly _rootElement: TestElement;
|
||||
|
||||
constructor(private _root: Element, private _stabilize: () => Promise<void>) {
|
||||
this._rootElement = new UnitTestElement(_root, this._stabilize);
|
||||
}
|
||||
|
||||
host(): TestElement {
|
||||
return this._rootElement;
|
||||
}
|
||||
|
||||
async querySelector(selector: string, options?: QueryOptions): Promise<TestElement|null> {
|
||||
await this._stabilize();
|
||||
const e = getElement(selector, this._root, options);
|
||||
return e && new UnitTestElement(e, this._stabilize);
|
||||
}
|
||||
|
||||
async querySelectorAll(selector: string): Promise<TestElement[]> {
|
||||
await this._stabilize();
|
||||
return Array.prototype.map.call(
|
||||
this._root.querySelectorAll(selector),
|
||||
(e: Element) => new UnitTestElement(e, this._stabilize));
|
||||
}
|
||||
|
||||
async load<T extends ComponentHarness>(
|
||||
componentHarness: ComponentHarnessConstructor<T>, selector: string,
|
||||
options?: QueryOptions): Promise<T|null> {
|
||||
await this._stabilize();
|
||||
const root = getElement(selector, this._root, options);
|
||||
return root && new componentHarness(new UnitTestLocator(root, this._stabilize));
|
||||
}
|
||||
|
||||
async loadAll<T extends ComponentHarness>(
|
||||
componentHarness: ComponentHarnessConstructor<T>,
|
||||
rootSelector: string): Promise<T[]> {
|
||||
await this._stabilize();
|
||||
return Array.prototype.map.call(
|
||||
this._root.querySelectorAll(rootSelector),
|
||||
(e: Element) => new componentHarness(new UnitTestLocator(e, this._stabilize)));
|
||||
}
|
||||
}
|
||||
|
||||
class UnitTestElement implements TestElement {
|
||||
constructor(readonly element: Element, private _stabilize: () => Promise<void>) {}
|
||||
|
||||
async blur(): Promise<void> {
|
||||
await this._stabilize();
|
||||
triggerBlur(this.element as HTMLElement);
|
||||
await this._stabilize();
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
await this._stabilize();
|
||||
if (!this._isTextInput(this.element)) {
|
||||
throw Error('Attempting to clear an invalid element');
|
||||
}
|
||||
triggerFocus(this.element as HTMLElement);
|
||||
this.element.value = '';
|
||||
dispatchFakeEvent(this.element, 'input');
|
||||
await this._stabilize();
|
||||
}
|
||||
|
||||
async click(): Promise<void> {
|
||||
await this._stabilize();
|
||||
dispatchMouseEvent(this.element, 'click');
|
||||
await this._stabilize();
|
||||
}
|
||||
|
||||
async focus(): Promise<void> {
|
||||
await this._stabilize();
|
||||
triggerFocus(this.element as HTMLElement);
|
||||
await this._stabilize();
|
||||
}
|
||||
|
||||
async getCssValue(property: string): Promise<string> {
|
||||
await this._stabilize();
|
||||
// TODO(mmalerba): Consider adding value normalization if we run into common cases where its
|
||||
// needed.
|
||||
return getComputedStyle(this.element).getPropertyValue(property);
|
||||
}
|
||||
|
||||
async hover(): Promise<void> {
|
||||
await this._stabilize();
|
||||
dispatchMouseEvent(this.element, 'mouseenter');
|
||||
await this._stabilize();
|
||||
}
|
||||
|
||||
async sendKeys(keys: string): Promise<void> {
|
||||
await this._stabilize();
|
||||
triggerFocus(this.element as HTMLElement);
|
||||
for (const key of keys) {
|
||||
const keyCode = key.charCodeAt(0);
|
||||
dispatchKeyboardEvent(this.element, 'keydown', keyCode);
|
||||
dispatchKeyboardEvent(this.element, 'keypress', keyCode);
|
||||
if (this._isTextInput(this.element)) {
|
||||
this.element.value += key;
|
||||
}
|
||||
dispatchKeyboardEvent(this.element, 'keyup', keyCode);
|
||||
if (this._isTextInput(this.element)) {
|
||||
dispatchFakeEvent(this.element, 'input');
|
||||
}
|
||||
}
|
||||
await this._stabilize();
|
||||
}
|
||||
|
||||
async text(): Promise<string> {
|
||||
await this._stabilize();
|
||||
return this.element.textContent || '';
|
||||
}
|
||||
|
||||
async getAttribute(name: string): Promise<string|null> {
|
||||
await this._stabilize();
|
||||
let value = this.element.getAttribute(name);
|
||||
// If cannot find attribute in the element, also try to find it in property,
|
||||
// this is useful for input/textarea tags.
|
||||
if (value === null && name in this.element) {
|
||||
// We need to cast the element so we can access its properties via string indexing.
|
||||
return (this.element as unknown as {[key: string]: string|null})[name];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private _isTextInput(element: Element): element is HTMLInputElement | HTMLTextAreaElement {
|
||||
return element.nodeName.toLowerCase() === 'input' ||
|
||||
element.nodeName.toLowerCase() === 'textarea' ;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get an element based on the CSS selector and root element.
|
||||
* @param selector The CSS selector
|
||||
* @param root Search element under the root element. If options.global is set,
|
||||
* root is ignored.
|
||||
* @param options Optional, extra searching options
|
||||
* @return When element is not present, return null if Options.allowNull is set
|
||||
* to true, throw an error if Options.allowNull is set to false; otherwise,
|
||||
* return the element
|
||||
*/
|
||||
function getElement(selector: string, root: Element, options?: QueryOptions): Element|null {
|
||||
const useGlobalRoot = options && options.global;
|
||||
const elem = (useGlobalRoot ? document : root).querySelector(selector);
|
||||
const allowNull = options !== undefined && options.allowNull !== undefined ?
|
||||
options.allowNull : undefined;
|
||||
if (elem === null) {
|
||||
if (allowNull) {
|
||||
return null;
|
||||
}
|
||||
throw Error('Cannot find element based on the CSS selector: ' + selector);
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
@@ -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 './testbed-harness-environment';
|
||||
export * from './unit-test-element';
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* @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 {ComponentFixture} from '@angular/core/testing';
|
||||
import {ComponentHarness, ComponentHarnessConstructor, HarnessLoader} from '../component-harness';
|
||||
import {HarnessEnvironment} from '../harness-environment';
|
||||
import {TestElement} from '../test-element';
|
||||
import {UnitTestElement} from './unit-test-element';
|
||||
|
||||
/** A `HarnessEnvironment` implementation for Angular's Testbed. */
|
||||
export class TestbedHarnessEnvironment extends HarnessEnvironment<Element> {
|
||||
protected constructor(rawRootElement: Element, private _fixture: ComponentFixture<unknown>) {
|
||||
super(rawRootElement);
|
||||
}
|
||||
|
||||
/** Creates a `HarnessLoader` rooted at the given fixture's root element. */
|
||||
static loader(fixture: ComponentFixture<unknown>): HarnessLoader {
|
||||
return new TestbedHarnessEnvironment(fixture.nativeElement, fixture);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of the given harness type, using the fixture's root element as the
|
||||
* harness's host element. This method should be used when creating a harness for the root element
|
||||
* of a fixture, as components do not have the correct selector when they are created as the root
|
||||
* of the fixture.
|
||||
*/
|
||||
static async harnessForFixture<T extends ComponentHarness>(
|
||||
fixture: ComponentFixture<unknown>, harnessType: ComponentHarnessConstructor<T>): Promise<T> {
|
||||
const environment = new TestbedHarnessEnvironment(fixture.nativeElement, fixture);
|
||||
await environment._stabilize();
|
||||
return environment.createComponentHarness(harnessType, fixture.nativeElement);
|
||||
}
|
||||
|
||||
protected getDocumentRoot(): Element {
|
||||
return this._fixture.nativeElement;
|
||||
}
|
||||
|
||||
protected createTestElement(element: Element): TestElement {
|
||||
return new UnitTestElement(element, this._stabilize.bind(this));
|
||||
}
|
||||
|
||||
protected createEnvironment(element: Element): 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));
|
||||
}
|
||||
|
||||
private async _stabilize(): Promise<void> {
|
||||
this._fixture.detectChanges();
|
||||
await this._fixture.whenStable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* @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 {
|
||||
dispatchFakeEvent,
|
||||
dispatchKeyboardEvent,
|
||||
dispatchMouseEvent,
|
||||
triggerBlur,
|
||||
triggerFocus
|
||||
} from '@angular/cdk/testing';
|
||||
import {TestElement} from '../test-element';
|
||||
|
||||
function isTextInput(element: Element): element is HTMLInputElement | HTMLTextAreaElement {
|
||||
return element.nodeName.toLowerCase() === 'input' ||
|
||||
element.nodeName.toLowerCase() === 'textarea' ;
|
||||
}
|
||||
|
||||
/** A `TestElement` implementation for unit tests. */
|
||||
export class UnitTestElement implements TestElement {
|
||||
constructor(readonly element: Element, private _stabilize: () => Promise<void>) {}
|
||||
|
||||
async blur(): Promise<void> {
|
||||
await this._stabilize();
|
||||
triggerBlur(this.element as HTMLElement);
|
||||
await this._stabilize();
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
await this._stabilize();
|
||||
if (!isTextInput(this.element)) {
|
||||
throw Error('Attempting to clear an invalid element');
|
||||
}
|
||||
triggerFocus(this.element as HTMLElement);
|
||||
this.element.value = '';
|
||||
dispatchFakeEvent(this.element, 'input');
|
||||
await this._stabilize();
|
||||
}
|
||||
|
||||
async click(): Promise<void> {
|
||||
await this._stabilize();
|
||||
dispatchMouseEvent(this.element, 'click');
|
||||
await this._stabilize();
|
||||
}
|
||||
|
||||
async focus(): Promise<void> {
|
||||
await this._stabilize();
|
||||
triggerFocus(this.element as HTMLElement);
|
||||
await this._stabilize();
|
||||
}
|
||||
|
||||
async getCssValue(property: string): Promise<string> {
|
||||
await this._stabilize();
|
||||
// TODO(mmalerba): Consider adding value normalization if we run into common cases where its
|
||||
// needed.
|
||||
return getComputedStyle(this.element).getPropertyValue(property);
|
||||
}
|
||||
|
||||
async hover(): Promise<void> {
|
||||
await this._stabilize();
|
||||
dispatchMouseEvent(this.element, 'mouseenter');
|
||||
await this._stabilize();
|
||||
}
|
||||
|
||||
async sendKeys(keys: string): Promise<void> {
|
||||
await this._stabilize();
|
||||
triggerFocus(this.element as HTMLElement);
|
||||
for (const key of keys) {
|
||||
const keyCode = key.charCodeAt(0);
|
||||
dispatchKeyboardEvent(this.element, 'keydown', keyCode);
|
||||
dispatchKeyboardEvent(this.element, 'keypress', keyCode);
|
||||
if (isTextInput(this.element)) {
|
||||
this.element.value += key;
|
||||
}
|
||||
dispatchKeyboardEvent(this.element, 'keyup', keyCode);
|
||||
if (isTextInput(this.element)) {
|
||||
dispatchFakeEvent(this.element, 'input');
|
||||
}
|
||||
}
|
||||
await this._stabilize();
|
||||
}
|
||||
|
||||
async text(): Promise<string> {
|
||||
await this._stabilize();
|
||||
return this.element.textContent || '';
|
||||
}
|
||||
|
||||
async getAttribute(name: string): Promise<string|null> {
|
||||
await this._stabilize();
|
||||
let value = this.element.getAttribute(name);
|
||||
// If cannot find attribute in the element, also try to find it in property,
|
||||
// this is useful for input/textarea tags.
|
||||
if (value === null && name in this.element) {
|
||||
// We need to cast the element so we can access its properties via string indexing.
|
||||
return (this.element as unknown as {[key: string]: string|null})[name];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -10,33 +10,40 @@ import {ComponentHarness} from '../../component-harness';
|
||||
import {TestElement} from '../../test-element';
|
||||
import {SubComponentHarness} from './sub-component-harness';
|
||||
|
||||
export class WrongComponentHarness extends ComponentHarness {
|
||||
static readonly hostSelector = 'wrong-selector';
|
||||
}
|
||||
|
||||
export class MainComponentHarness extends ComponentHarness {
|
||||
readonly title = this.find('h1');
|
||||
readonly asyncCounter = this.find('#asyncCounter');
|
||||
readonly counter = this.find('#counter');
|
||||
readonly input = this.find('#input');
|
||||
readonly value = this.find('#value');
|
||||
readonly allLabels = this.findAll('label');
|
||||
readonly allLists = this.findAll(SubComponentHarness, 'test-sub');
|
||||
readonly memo = this.find('textarea');
|
||||
static readonly hostSelector = 'test-main';
|
||||
|
||||
readonly title = this.locatorFor('h1');
|
||||
readonly button = this.locatorFor('button');
|
||||
readonly asyncCounter = this.locatorFor('#asyncCounter');
|
||||
readonly counter = this.locatorFor('#counter');
|
||||
readonly input = this.locatorFor('#input');
|
||||
readonly value = this.locatorFor('#value');
|
||||
readonly allLabels = this.locatorForAll('label');
|
||||
readonly allLists = this.locatorForAll(SubComponentHarness);
|
||||
readonly memo = this.locatorFor('textarea');
|
||||
// Allow null for element
|
||||
readonly nullItem = this.find('wrong locator', {allowNull: true});
|
||||
readonly nullItem = this.locatorForOptional('wrong locator');
|
||||
// Allow null for component harness
|
||||
readonly nullComponentHarness =
|
||||
this.find(SubComponentHarness, 'wrong locator', {allowNull: true});
|
||||
readonly errorItem = this.find('wrong locator', {allowNull: false});
|
||||
readonly nullComponentHarness = this.locatorForOptional(WrongComponentHarness);
|
||||
readonly errorItem = this.locatorFor('wrong locator');
|
||||
|
||||
readonly globalEl = this.find('.sibling', {global: true});
|
||||
readonly errorGlobalEl =
|
||||
this.find('wrong locator', {global: true, allowNull: false});
|
||||
readonly nullGlobalEl =
|
||||
this.find('wrong locator', {global: true, allowNull: true});
|
||||
readonly globalEl = this.documentRootLocatorFactory().locatorFor('.sibling');
|
||||
readonly errorGlobalEl = this.documentRootLocatorFactory().locatorFor('wrong locator');
|
||||
readonly nullGlobalEl = this.documentRootLocatorFactory().locatorForOptional('wrong locator');
|
||||
|
||||
private _button = this.find('button');
|
||||
private _testTools = this.find(SubComponentHarness, 'test-sub');
|
||||
readonly optionalDiv = this.locatorForOptional('div');
|
||||
readonly optionalSubComponent = this.locatorForOptional(SubComponentHarness);
|
||||
readonly errorSubComponent = this.locatorFor(WrongComponentHarness);
|
||||
|
||||
private _testTools = this.locatorFor(SubComponentHarness);
|
||||
|
||||
async increaseCounter(times: number) {
|
||||
const button = await this._button();
|
||||
const button = await this.button();
|
||||
for (let i = 0; i < times; i++) {
|
||||
await button.click();
|
||||
}
|
||||
|
||||
@@ -10,8 +10,11 @@ import {ComponentHarness} from '../../component-harness';
|
||||
import {TestElement} from '../../test-element';
|
||||
|
||||
export class SubComponentHarness extends ComponentHarness {
|
||||
readonly title = this.find('h2');
|
||||
readonly getItems = this.findAll('li');
|
||||
static readonly hostSelector = 'test-sub';
|
||||
|
||||
readonly title = this.locatorFor('h2');
|
||||
readonly getItems = this.locatorForAll('li');
|
||||
readonly globalElement = this.documentRootLocatorFactory().locatorFor('#username');
|
||||
|
||||
async getItem(index: number): Promise<TestElement> {
|
||||
const items = await this.getItems();
|
||||
|
||||
@@ -1,31 +1,106 @@
|
||||
import {browser, by, element} from 'protractor';
|
||||
|
||||
import {getElementFinder, load} from '../protractor';
|
||||
import {browser} from 'protractor';
|
||||
import {HarnessLoader} from '../component-harness';
|
||||
import {ProtractorHarnessEnvironment} from '../protractor';
|
||||
import {MainComponentHarness} from './harnesses/main-component-harness';
|
||||
import {SubComponentHarness} from './harnesses/sub-component-harness';
|
||||
|
||||
describe('Protractor Helper Test', () => {
|
||||
let harness: MainComponentHarness;
|
||||
|
||||
describe('ProtractorHarnessEnvironment', () => {
|
||||
beforeEach(async () => {
|
||||
await browser.get('/component-harness');
|
||||
harness = await load(MainComponentHarness, 'test-main');
|
||||
});
|
||||
|
||||
describe('Locator', () => {
|
||||
it('should be able to locate a element based on CSS selector', async () => {
|
||||
describe('HarnessLoader', () => {
|
||||
let loader: HarnessLoader;
|
||||
|
||||
beforeEach(async () => {
|
||||
loader = ProtractorHarnessEnvironment.loader();
|
||||
});
|
||||
|
||||
it('should create HarnessLoader', async () => {
|
||||
expect(loader).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should find required HarnessLoader for child element', async () => {
|
||||
const subcomponentsLoader = await loader.getChildLoader('.subcomponents');
|
||||
expect(subcomponentsLoader).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should error after failing to find required HarnessLoader for child element', async () => {
|
||||
try {
|
||||
await loader.getChildLoader('error');
|
||||
fail('Expected to throw');
|
||||
} catch (e) {
|
||||
expect(e.message)
|
||||
.toBe('Expected to find element matching selector: "error", but none was found');
|
||||
}
|
||||
});
|
||||
|
||||
it('should find all HarnessLoaders for child elements', async () => {
|
||||
const loaders = await loader.getAllChildLoaders('.subcomponents,.counters');
|
||||
expect(loaders.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should get first matching component for required harness', async () => {
|
||||
const harness = await loader.getHarness(SubComponentHarness);
|
||||
expect(harness).not.toBeNull();
|
||||
expect(await (await harness.title()).text()).toBe('List of test tools');
|
||||
});
|
||||
|
||||
it('should throw if no matching component found for required harness', async () => {
|
||||
const countersLoader = await loader.getChildLoader('.counters');
|
||||
try {
|
||||
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');
|
||||
}
|
||||
});
|
||||
|
||||
it('should get all matching components for all harnesses', async () => {
|
||||
const harnesses = await loader.getAllHarnesses(SubComponentHarness);
|
||||
expect(harnesses.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ComponentHarness', () => {
|
||||
let harness: MainComponentHarness;
|
||||
|
||||
beforeEach(async () => {
|
||||
harness = await ProtractorHarnessEnvironment.loader().getHarness(MainComponentHarness);
|
||||
});
|
||||
|
||||
it('should locate a required element based on CSS selector', async () => {
|
||||
const title = await harness.title();
|
||||
expect(await title.text()).toBe('Main Component');
|
||||
});
|
||||
|
||||
it('should be able to locate all elements based on CSS selector',
|
||||
async () => {
|
||||
const labels = await harness.allLabels();
|
||||
expect(labels.length).toBe(2);
|
||||
expect(await labels[0].text()).toBe('Count:');
|
||||
expect(await labels[1].text()).toBe('AsyncCounter:');
|
||||
});
|
||||
it('should throw when failing to locate a required element based on CSS selector', async () => {
|
||||
try {
|
||||
await harness.errorItem();
|
||||
fail('Expected to throw');
|
||||
} catch (e) {
|
||||
expect(e.message).toBe(
|
||||
'Expected to find element matching selector: "wrong locator", but none was found');
|
||||
}
|
||||
});
|
||||
|
||||
it('should be able to locate the sub harnesses', async () => {
|
||||
it('should locate an optional element based on CSS selector', async () => {
|
||||
const present = await harness.optionalDiv();
|
||||
const missing = await harness.nullItem();
|
||||
expect(present).not.toBeNull();
|
||||
expect(await present!.text()).toBe('Hello Yi from Angular 2!');
|
||||
expect(missing).toBeNull();
|
||||
});
|
||||
|
||||
it('should locate all elements based on CSS selector', async () => {
|
||||
const labels = await harness.allLabels();
|
||||
expect(labels.length).toBe(2);
|
||||
expect(await labels[0].text()).toBe('Count:');
|
||||
expect(await labels[1].text()).toBe('AsyncCounter:');
|
||||
});
|
||||
|
||||
it('should locate required sub harnesses', async () => {
|
||||
const items = await harness.getTestTools();
|
||||
expect(items.length).toBe(3);
|
||||
expect(await items[0].text()).toBe('Protractor');
|
||||
@@ -33,7 +108,25 @@ describe('Protractor Helper Test', () => {
|
||||
expect(await items[2].text()).toBe('Other');
|
||||
});
|
||||
|
||||
it('should be able to locate all sub harnesses', async () => {
|
||||
it('should throw when failing to locate required sub harnesses', async () => {
|
||||
try {
|
||||
await harness.errorSubComponent();
|
||||
fail('Expected to throw');
|
||||
} catch (e) {
|
||||
expect(e.message).toBe(
|
||||
'Expected to find element matching selector: "wrong-selector", but none was found');
|
||||
}
|
||||
});
|
||||
|
||||
it('should locate optional sub harnesses', async () => {
|
||||
const present = await harness.optionalSubComponent();
|
||||
const missing = await harness.nullComponentHarness();
|
||||
expect(present).not.toBeNull();
|
||||
expect(await (await present!.title()).text()).toBe('List of test tools');
|
||||
expect(missing).toBeNull();
|
||||
});
|
||||
|
||||
it('should locate all sub harnesses', async () => {
|
||||
const alllists = await harness.allLists();
|
||||
const items1 = await alllists[0].getItems();
|
||||
const items2 = await alllists[1].getItems();
|
||||
@@ -47,9 +140,27 @@ describe('Protractor Helper Test', () => {
|
||||
expect(await items2[1].text()).toBe('Integration Test');
|
||||
expect(await items2[2].text()).toBe('Performance Test');
|
||||
});
|
||||
|
||||
it('should wait for async operation to complete', async () => {
|
||||
const asyncCounter = await harness.asyncCounter();
|
||||
expect(await asyncCounter.text()).toBe('5');
|
||||
await harness.increaseCounter(3);
|
||||
expect(await asyncCounter.text()).toBe('8');
|
||||
});
|
||||
|
||||
it('can get elements outside of host', async () => {
|
||||
const globalEl = await harness.globalEl();
|
||||
expect(await globalEl.text()).toBe('I am a sibling!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test element', () => {
|
||||
describe('TestElement', () => {
|
||||
let harness: MainComponentHarness;
|
||||
|
||||
beforeEach(async () => {
|
||||
harness = await ProtractorHarnessEnvironment.loader().getHarness(MainComponentHarness);
|
||||
});
|
||||
|
||||
it('should be able to clear', async () => {
|
||||
const input = await harness.input();
|
||||
await input.sendKeys('Yi');
|
||||
@@ -79,8 +190,7 @@ describe('Protractor Helper Test', () => {
|
||||
const input = await harness.input();
|
||||
await input.sendKeys('Yi');
|
||||
expect(await input.getAttribute('id'))
|
||||
.toBe(await browser.driver.switchTo().activeElement().getAttribute(
|
||||
'id'));
|
||||
.toBe(await browser.driver.switchTo().activeElement().getAttribute('id'));
|
||||
});
|
||||
|
||||
it('should be able to hover', async () => {
|
||||
@@ -106,78 +216,16 @@ describe('Protractor Helper Test', () => {
|
||||
const title = await harness.title();
|
||||
expect(await title.getCssValue('height')).toBe('50px');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Async operation', () => {
|
||||
it('should wait for async opeartion to complete', async () => {
|
||||
const asyncCounter = await harness.asyncCounter();
|
||||
expect(await asyncCounter.text()).toBe('5');
|
||||
await harness.increaseCounter(3);
|
||||
expect(await asyncCounter.text()).toBe('8');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Allow null', () => {
|
||||
it('should allow element to be null when setting allowNull', async () => {
|
||||
expect(await harness.nullItem()).toBe(null);
|
||||
});
|
||||
|
||||
it('should allow main harness to be null when setting allowNull',
|
||||
async () => {
|
||||
const nullMainHarness = await load(
|
||||
MainComponentHarness, 'harness not present', {allowNull: true});
|
||||
expect(nullMainHarness).toBe(null);
|
||||
});
|
||||
|
||||
it('should allow sub-harness to be null when setting allowNull',
|
||||
async () => {
|
||||
expect(await harness.nullComponentHarness()).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with the global option', () => {
|
||||
it('should find an element outside the root of the harness', async () => {
|
||||
const globalEl = await harness.globalEl();
|
||||
expect(await globalEl.text()).toBe('I am a sibling!');
|
||||
});
|
||||
|
||||
it('should return null for a selector that does not exist', async () => {
|
||||
expect(await harness.nullGlobalEl()).toBeNull();
|
||||
});
|
||||
|
||||
it('should throw an error for a selctor that does not exist ' +
|
||||
'with allowNull = false',
|
||||
async () => {
|
||||
try {
|
||||
await harness.errorGlobalEl();
|
||||
fail('Should throw error');
|
||||
} catch (err) {
|
||||
expect(err.message)
|
||||
.toBe(
|
||||
'Cannot find element based on the CSS selector: wrong locator');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Throw error', () => {
|
||||
it('should show the correct error', async () => {
|
||||
try {
|
||||
await harness.errorItem();
|
||||
fail('Should throw error');
|
||||
} catch (err) {
|
||||
expect(err.message)
|
||||
.toBe(
|
||||
'Cannot find element based on the CSS selector: wrong locator');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('getElementFinder', () => {
|
||||
it('should return the element finder', async () => {
|
||||
const mainElement = await element(by.css('test-main'));
|
||||
const elementFromHarness = getElementFinder(harness.host());
|
||||
expect(await elementFromHarness.getId())
|
||||
.toBe(await mainElement.getId());
|
||||
it('should focus and blur element', async () => {
|
||||
let button = await harness.button();
|
||||
expect(await (await browser.switchTo().activeElement()).getText())
|
||||
.not.toBe(await button.text());
|
||||
await button.focus();
|
||||
expect(await (await browser.switchTo().activeElement()).getText()).toBe(await button.text());
|
||||
await button.blur();
|
||||
expect(await (await browser.switchTo().activeElement()).getText())
|
||||
.not.toBe(await button.text());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
<h1 style="height: 50px">Main Component</h1>
|
||||
<div id="username">Hello {{username}} from Angular 2!</div>
|
||||
<button (click)="click()">Up</button><br>
|
||||
<label>Count:</label>
|
||||
<div id="counter">{{counter}}</div>
|
||||
<label>AsyncCounter:</label>
|
||||
<div id="asyncCounter">{{asyncCounter}}</div>
|
||||
<input [(ngModel)]="input" id="input" aria-label="input">
|
||||
<div id="value">Input: {{input}}</div>
|
||||
<textarea id="memo" aria-label="memo">{{memo}}</textarea>
|
||||
<test-sub title="test tools" [items]="testTools"></test-sub>
|
||||
<test-sub title="test methods" [items]="testMethods"></test-sub>
|
||||
<div class="counters">
|
||||
<button (click)="click()">Up</button><br>
|
||||
<label>Count:</label>
|
||||
<div id="counter">{{counter}}</div>
|
||||
<label>AsyncCounter:</label>
|
||||
<div id="asyncCounter">{{asyncCounter}}</div>
|
||||
</div>
|
||||
<div class="inputs">
|
||||
<input [(ngModel)]="input" id="input" aria-label="input">
|
||||
<div id="value">Input: {{input}}</div>
|
||||
<textarea id="memo" aria-label="memo">{{memo}}</textarea>
|
||||
</div>
|
||||
<div class="subcomponents">
|
||||
<test-sub title="test tools" [items]="testTools"></test-sub>
|
||||
<test-sub title="test methods" [items]="testMethods"></test-sub>
|
||||
</div>
|
||||
|
||||
@@ -1,40 +1,122 @@
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {getNativeElement, load} from '../testbed';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {HarnessLoader} from '../component-harness';
|
||||
import {TestbedHarnessEnvironment} from '../testbed/index';
|
||||
import {MainComponentHarness} from './harnesses/main-component-harness';
|
||||
|
||||
import {SubComponentHarness} from './harnesses/sub-component-harness';
|
||||
import {TestComponentsModule} from './test-components-module';
|
||||
import {TestMainComponent} from './test-main-component';
|
||||
|
||||
describe('Testbed Helper Test', () => {
|
||||
let harness: MainComponentHarness;
|
||||
let fixture: ComponentFixture<{}>;
|
||||
beforeEach(async(() => {
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
imports: [TestComponentsModule],
|
||||
})
|
||||
.compileComponents()
|
||||
.then(() => {
|
||||
fixture = TestBed.createComponent(TestMainComponent);
|
||||
harness = load(MainComponentHarness, fixture);
|
||||
});
|
||||
}));
|
||||
function activeElementText() {
|
||||
return document.activeElement && (document.activeElement as HTMLElement).innerText || '';
|
||||
}
|
||||
|
||||
describe('Locator', () => {
|
||||
it('should be able to locate a element based on CSS selector', async () => {
|
||||
describe('TestbedHarnessEnvironment', () => {
|
||||
let fixture: ComponentFixture<{}>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({imports: [TestComponentsModule]}).compileComponents();
|
||||
fixture = TestBed.createComponent(TestMainComponent);
|
||||
});
|
||||
|
||||
describe('HarnessLoader', () => {
|
||||
let loader: HarnessLoader;
|
||||
|
||||
beforeEach(async () => {
|
||||
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||
});
|
||||
|
||||
it('should create HarnessLoader from fixture', async () => {
|
||||
expect(loader).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should create ComponentHarness for fixture', async () => {
|
||||
const harness =
|
||||
await TestbedHarnessEnvironment.harnessForFixture(fixture, MainComponentHarness);
|
||||
expect(harness).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should find required HarnessLoader for child element', async () => {
|
||||
const subcomponentsLoader = await loader.getChildLoader('.subcomponents');
|
||||
expect(subcomponentsLoader).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should error after failing to find required HarnessLoader for child element', async () => {
|
||||
try {
|
||||
await loader.getChildLoader('error');
|
||||
fail('Expected to throw');
|
||||
} catch (e) {
|
||||
expect(e.message)
|
||||
.toBe('Expected to find element matching selector: "error", but none was found');
|
||||
}
|
||||
});
|
||||
|
||||
it('should find all HarnessLoaders for child elements', async () => {
|
||||
const loaders = await loader.getAllChildLoaders('.subcomponents,.counters');
|
||||
expect(loaders.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should get first matching component for required harness', async () => {
|
||||
const harness = await loader.getHarness(SubComponentHarness);
|
||||
expect(harness).not.toBeNull();
|
||||
expect(await (await harness.title()).text()).toBe('List of test tools');
|
||||
});
|
||||
|
||||
it('should throw if no matching component found for required harness', async () => {
|
||||
const countersLoader = await loader.getChildLoader('.counters');
|
||||
try {
|
||||
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');
|
||||
}
|
||||
});
|
||||
|
||||
it('should get all matching components for all harnesses', async () => {
|
||||
const harnesses = await loader.getAllHarnesses(SubComponentHarness);
|
||||
expect(harnesses.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ComponentHarness', () => {
|
||||
let harness: MainComponentHarness;
|
||||
|
||||
beforeEach(async () => {
|
||||
harness =
|
||||
await TestbedHarnessEnvironment.harnessForFixture(fixture, MainComponentHarness);
|
||||
});
|
||||
|
||||
it('should locate a required element based on CSS selector', async () => {
|
||||
const title = await harness.title();
|
||||
expect(await title.text()).toBe('Main Component');
|
||||
});
|
||||
|
||||
it('should be able to locate all elements based on CSS selector',
|
||||
async () => {
|
||||
const labels = await harness.allLabels();
|
||||
expect(labels.length).toBe(2);
|
||||
expect(await labels[0].text()).toBe('Count:');
|
||||
expect(await labels[1].text()).toBe('AsyncCounter:');
|
||||
});
|
||||
it('should throw when failing to locate a required element based on CSS selector', async () => {
|
||||
try {
|
||||
await harness.errorItem();
|
||||
fail('Expected to throw');
|
||||
} catch (e) {
|
||||
expect(e.message).toBe(
|
||||
'Expected to find element matching selector: "wrong locator", but none was found');
|
||||
}
|
||||
});
|
||||
|
||||
it('should be able to locate the sub harnesses', async () => {
|
||||
it('should locate an optional element based on CSS selector', async () => {
|
||||
const present = await harness.optionalDiv();
|
||||
const missing = await harness.nullItem();
|
||||
expect(present).not.toBeNull();
|
||||
expect(await present!.text()).toBe('Hello Yi from Angular 2!');
|
||||
expect(missing).toBeNull();
|
||||
});
|
||||
|
||||
it('should locate all elements based on CSS selector', async () => {
|
||||
const labels = await harness.allLabels();
|
||||
expect(labels.length).toBe(2);
|
||||
expect(await labels[0].text()).toBe('Count:');
|
||||
expect(await labels[1].text()).toBe('AsyncCounter:');
|
||||
});
|
||||
|
||||
it('should locate required sub harnesses', async () => {
|
||||
const items = await harness.getTestTools();
|
||||
expect(items.length).toBe(3);
|
||||
expect(await items[0].text()).toBe('Protractor');
|
||||
@@ -42,7 +124,25 @@ describe('Testbed Helper Test', () => {
|
||||
expect(await items[2].text()).toBe('Other');
|
||||
});
|
||||
|
||||
it('should be able to locate all sub harnesses', async () => {
|
||||
it('should throw when failing to locate required sub harnesses', async () => {
|
||||
try {
|
||||
await harness.errorSubComponent();
|
||||
fail('Expected to throw');
|
||||
} catch (e) {
|
||||
expect(e.message).toBe(
|
||||
'Expected to find element matching selector: "wrong-selector", but none was found');
|
||||
}
|
||||
});
|
||||
|
||||
it('should locate optional sub harnesses', async () => {
|
||||
const present = await harness.optionalSubComponent();
|
||||
const missing = await harness.nullComponentHarness();
|
||||
expect(present).not.toBeNull();
|
||||
expect(await (await present!.title()).text()).toBe('List of test tools');
|
||||
expect(missing).toBeNull();
|
||||
});
|
||||
|
||||
it('should locate all sub harnesses', async () => {
|
||||
const alllists = await harness.allLists();
|
||||
const items1 = await alllists[0].getItems();
|
||||
const items2 = await alllists[1].getItems();
|
||||
@@ -56,9 +156,31 @@ describe('Testbed Helper Test', () => {
|
||||
expect(await items2[1].text()).toBe('Integration Test');
|
||||
expect(await items2[2].text()).toBe('Performance Test');
|
||||
});
|
||||
|
||||
it('should wait for async operation to complete', async () => {
|
||||
const asyncCounter = await harness.asyncCounter();
|
||||
expect(await asyncCounter.text()).toBe('5');
|
||||
await harness.increaseCounter(3);
|
||||
expect(await asyncCounter.text()).toBe('8');
|
||||
});
|
||||
|
||||
it('can get elements outside of host', async () => {
|
||||
const subcomponents = await harness.allLists();
|
||||
expect(subcomponents[0]).not.toBeNull();
|
||||
const globalEl = await subcomponents[0]!.globalElement();
|
||||
expect(globalEl).not.toBeNull();
|
||||
expect(await globalEl.text()).toBe('Hello Yi from Angular 2!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test element', () => {
|
||||
describe('TestElement', () => {
|
||||
let harness: MainComponentHarness;
|
||||
|
||||
beforeEach(async () => {
|
||||
harness =
|
||||
await TestbedHarnessEnvironment.harnessForFixture(fixture, MainComponentHarness);
|
||||
});
|
||||
|
||||
it('should be able to clear', async () => {
|
||||
const input = await harness.input();
|
||||
await input.sendKeys('Yi');
|
||||
@@ -87,8 +209,7 @@ describe('Testbed Helper Test', () => {
|
||||
it('focuses the element before sending key', async () => {
|
||||
const input = await harness.input();
|
||||
await input.sendKeys('Yi');
|
||||
expect(await input.getAttribute('id'))
|
||||
.toBe(document.activeElement!.id);
|
||||
expect(await input.getAttribute('id')).toBe(document.activeElement!.id);
|
||||
});
|
||||
|
||||
it('should be able to hover', async () => {
|
||||
@@ -114,43 +235,14 @@ describe('Testbed Helper Test', () => {
|
||||
const title = await harness.title();
|
||||
expect(await title.getCssValue('height')).toBe('50px');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Async operation', () => {
|
||||
it('should wait for async opeartion to complete', async () => {
|
||||
const asyncCounter = await harness.asyncCounter();
|
||||
expect(await asyncCounter.text()).toBe('5');
|
||||
await harness.increaseCounter(3);
|
||||
expect(await asyncCounter.text()).toBe('8');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Allow null', () => {
|
||||
it('should allow element to be null when setting allowNull', async () => {
|
||||
expect(await harness.nullItem()).toBe(null);
|
||||
});
|
||||
|
||||
it('should allow harness to be null when setting allowNull', async () => {
|
||||
expect(await harness.nullComponentHarness()).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Throw error', () => {
|
||||
it('should show the correct error', async () => {
|
||||
try {
|
||||
await harness.errorItem();
|
||||
fail('Should throw error');
|
||||
} catch (err) {
|
||||
expect(err.message)
|
||||
.toBe(
|
||||
'Cannot find element based on the CSS selector: wrong locator');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNativeElement', () => {
|
||||
it('should return the native element', async () => {
|
||||
expect(getNativeElement(harness.host())).toBe(fixture.nativeElement);
|
||||
it('should focus and blur element', async () => {
|
||||
let button = await harness.button();
|
||||
expect(activeElementText()).not.toBe(await button.text());
|
||||
await button.focus();
|
||||
expect(activeElementText()).toBe(await button.text());
|
||||
await button.blur();
|
||||
expect(activeElementText()).not.toBe(await button.text());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user