From 0a4070189a50c475a2eee3800f3ce7a127d8a992 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Wed, 5 Jun 2019 13:32:34 -0700 Subject: [PATCH 01/11] refactor(cdk-experimental/testing): refactor harness API --- .../testing/component-harness.ts | 291 ++++++++---------- src/cdk-experimental/testing/protractor.ts | 116 +------ src/cdk-experimental/testing/testbed.ts | 102 ++---- 3 files changed, 170 insertions(+), 339 deletions(-) diff --git a/src/cdk-experimental/testing/component-harness.ts b/src/cdk-experimental/testing/component-harness.ts index e1c385561cd3..d3d1ff91281e 100644 --- a/src/cdk-experimental/testing/component-harness.ts +++ b/src/cdk-experimental/testing/component-harness.ts @@ -8,66 +8,120 @@ 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 of the given type when called. */ +export type AsyncFn = () => Promise; + +export interface HarnessEnvironment { + findRequired(selector: string): Promise; + findOptional(selector: string): Promise; + findAll(selector: string): Promise; + requiredHarness(harness: ComponentHarnessConstructor): Promise; + optionalHarness(harness: ComponentHarnessConstructor): + Promise; + allHarnesses(harness: ComponentHarnessConstructor): Promise; +} + +export interface LocatorFactory { + rootElement(): TestElement; + requiredLocator(selector: string): AsyncFn; + requiredLocator(harness: ComponentHarnessConstructor): AsyncFn; + optionalLocator(selector: string): AsyncFn; + optionalLocator(harness: ComponentHarnessConstructor): + AsyncFn; + allLocator(selector: string): AsyncFn; + allLocator(harness: ComponentHarnessConstructor): AsyncFn; } /** Interface that is used to find elements in the DOM and create harnesses for them. */ -export interface HarnessLocator { - /** - * Get the host element of locator. - */ - host(): TestElement; - - /** - * Search the first matched test element. - * @param selector The CSS selector of the test elements. - * @param options Optional, extra searching options - */ - querySelector(selector: string, options?: QueryOptions): Promise; - - /** - * Search all matched test elements under current root by CSS selector. - * @param selector The CSS selector of the test elements. - */ - querySelectorAll(selector: string): Promise; - - /** - * 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 - */ - load( - componentHarness: ComponentHarnessConstructor, root: string, - options?: QueryOptions): Promise; - - /** - * 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( - componentHarness: ComponentHarnessConstructor, root: string): Promise; +export abstract class AbstractHarnessEnvironment implements HarnessEnvironment, LocatorFactory { + protected constructor(protected rawRootElement: E) {} + + abstract findAll(selector: string): Promise; + + protected abstract createTestElement(element: E): TestElement; + + protected abstract createHarness( + harnessType: ComponentHarnessConstructor, element: E): T; + + protected abstract getAllRawElements(selector: string): Promise; + + rootElement(): TestElement { + return this.createTestElement(this.rawRootElement); + } + + requiredLocator(selector: string): AsyncFn; + requiredLocator(harness: ComponentHarnessConstructor): AsyncFn; + requiredLocator( + arg: string | ComponentHarnessConstructor): AsyncFn { + return async () => { + const result = await this._createTestElementOrHarness(arg); + if (result) { + return result; + } + const selector = typeof arg === 'string' ? arg : arg.hostSelector; + throw Error(`Expected to find element matching selector: "${selector}", but none was found`); + }; + } + + optionalLocator(selector: string): AsyncFn; + optionalLocator(harness: ComponentHarnessConstructor): + AsyncFn; + optionalLocator( + arg: string | ComponentHarnessConstructor): AsyncFn { + return async () => { + return this._createTestElementOrHarness(arg); + }; + } + + allLocator(selector: string): AsyncFn; + allLocator(harness: ComponentHarnessConstructor): AsyncFn; + allLocator( + arg: string | ComponentHarnessConstructor): AsyncFn { + 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.createHarness(arg, e)); + } + }; + } + + requiredHarness(harness: ComponentHarnessConstructor): Promise { + return this.requiredLocator(harness)(); + } + + optionalHarness(harness: ComponentHarnessConstructor): + Promise { + return this.optionalLocator(harness)(); + } + + allHarnesses(harness: ComponentHarnessConstructor): Promise { + return this.allLocator(harness)(); + } + + async findRequired(selector: string): Promise { + const environment = (await this.findAll(selector))[0]; + if (!environment) { + throw Error(`Expected to find element matching selector: "${selector}", but none was found`); + } + return environment; + } + + async findOptional(selector: string): Promise { + return (await this.findAll(selector))[0] || null; + } + + private async _createTestElementOrHarness( + arg: string | ComponentHarnessConstructor): Promise { + if (typeof arg === 'string') { + const element = (await this.getAllRawElements(arg))[0]; + return this.createTestElement(element) || null; + } else { + const element = (await this.getAllRawElements(arg.hostSelector))[0]; + return this.createHarness(arg, element) || null; + } + } } /** @@ -77,114 +131,37 @@ export interface HarnessLocator { * harness. */ export abstract class ComponentHarness { - constructor(private readonly locator: HarnessLocator) {} + constructor(private readonly locatorFacotry: LocatorFactory) {} - /** - * Get the host element of component harness. - */ - host(): TestElement { - return this.locator.host(); + async host() { + return this.locatorFacotry.rootElement(); } - /** - * Generate a function to find the first matched test element by CSS - * selector. - * @param selector The CSS selector of the test element. - */ - protected find(selector: string): () => Promise; - - /** - * 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; - - /** - * 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; - - /** - * 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( - componentHarness: ComponentHarnessConstructor, - root: string): () => Promise; - - /** - * 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( - componentHarness: ComponentHarnessConstructor, root: string, - options: QueryOptions & {allowNull: true}): () => Promise; - - /** - * 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( - componentHarness: ComponentHarnessConstructor, root: string, - options: QueryOptions): () => Promise; - - protected find( - selectorOrComponentHarness: string|ComponentHarnessConstructor, - selectorOrOptions?: string|QueryOptions, - options?: QueryOptions): () => Promise { - 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 requiredLocator(selector: string): AsyncFn; + protected requiredLocator(harness: ComponentHarnessConstructor): + AsyncFn; + protected requiredLocator(arg: any): any { + return this.locatorFacotry.requiredLocator(arg); } - /** - * 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. - */ - protected findAll(selector: string): () => Promise; - - /** - * 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. - */ - protected findAll( - componentHarness: ComponentHarnessConstructor, - root: string): () => Promise; - - protected findAll( - selectorOrComponentHarness: string|ComponentHarnessConstructor, - root?: string): () => Promise { - 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 optionalLocator(selector: string): AsyncFn; + protected optionalLocator(harness: ComponentHarnessConstructor): + AsyncFn; + protected optionalLocator(arg: any): any { + return this.locatorFacotry.optionalLocator(arg); + } + + protected allLocator(selector: string): AsyncFn; + protected allLocator(harness: ComponentHarnessConstructor): + AsyncFn; + protected allLocator(arg: any): any { + return this.locatorFacotry.allLocator(arg); } } /** Constructor for a ComponentHarness subclass. */ export interface ComponentHarnessConstructor { - new(locator: HarnessLocator): T; + new(environment: HarnessEnvironment): T; + + hostSelector: string; } diff --git a/src/cdk-experimental/testing/protractor.ts b/src/cdk-experimental/testing/protractor.ts index f3fd769d243d..deb7ab62682c 100644 --- a/src/cdk-experimental/testing/protractor.ts +++ b/src/cdk-experimental/testing/protractor.ts @@ -9,96 +9,35 @@ import {browser, by, element as protractorElement, ElementFinder} from 'protractor'; import { + AbstractHarnessEnvironment, ComponentHarness, ComponentHarnessConstructor, - HarnessLocator, - QueryOptions + HarnessEnvironment, } 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( - componentHarness: ComponentHarnessConstructor, - rootSelector: string): Promise; - -/** - * 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( - componentHarness: ComponentHarnessConstructor, rootSelector?: string, - options?: QueryOptions): Promise; - -export async function load( - componentHarness: ComponentHarnessConstructor, rootSelector = 'body', - options?: QueryOptions): Promise { - 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; +export class ProtractorHarnessEnvironment extends AbstractHarnessEnvironment { + static root(): ProtractorHarnessEnvironment { + return new ProtractorHarnessEnvironment(protractorElement(by.css('body'))); } - 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); + async findAll(selector: string): Promise { + return (await this.getAllRawElements(selector)).map(e => new ProtractorHarnessEnvironment(e)); } - host(): TestElement { - return this._root; + protected createTestElement(element: ElementFinder): TestElement { + return new ProtractorElement(element); } - async querySelector(selector: string, options?: QueryOptions): Promise { - const finder = await getElement(selector, this._rootFinder, options); - return finder && new ProtractorElement(finder); + protected createHarness( + harnessType: ComponentHarnessConstructor, element: ElementFinder): T { + return new harnessType(new ProtractorHarnessEnvironment(element)); } - async querySelectorAll(selector: string): Promise { - const elementFinders = this._rootFinder.all(by.css(selector)); + protected async getAllRawElements(selector: string): Promise { + const elementFinders = this.rawRootElement.all(by.css(selector)); return elementFinders.reduce( - (result: TestElement[], el: ElementFinder) => - el ? result.concat([new ProtractorElement(el)]) : result, - []); - } - - async load( - componentHarness: ComponentHarnessConstructor, selector: string, - options?: QueryOptions): Promise { - const root = await getElement(selector, this._rootFinder, options); - return root && new componentHarness(new ProtractorLocator(root)); - } - - async loadAll( - componentHarness: ComponentHarnessConstructor, - rootSelector: string): Promise { - const roots = this._rootFinder.all(by.css(rootSelector)); - return roots.reduce( - (result: T[], el: ElementFinder) => - el ? result.concat(new componentHarness(new ProtractorLocator(el))) : result, - []); + (result: ElementFinder[], el: ElementFinder) => el ? result.concat([el]) : result, []); } } @@ -143,28 +82,3 @@ class ProtractorElement implements TestElement { 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 { - 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; -} diff --git a/src/cdk-experimental/testing/testbed.ts b/src/cdk-experimental/testing/testbed.ts index d2731cf60b13..aea5a044564c 100644 --- a/src/cdk-experimental/testing/testbed.ts +++ b/src/cdk-experimental/testing/testbed.ts @@ -16,82 +16,47 @@ import { import {ComponentFixture} from '@angular/core/testing'; import { + AbstractHarnessEnvironment, ComponentHarness, ComponentHarnessConstructor, - HarnessLocator, - QueryOptions + HarnessEnvironment } 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( - componentHarness: ComponentHarnessConstructor, - 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) { - this._rootElement = new UnitTestElement(_root, this._stabilize); +export class TestbedHarnessEnvironment extends AbstractHarnessEnvironment { + constructor(rawRootElement: Element, private _stabilize: () => Promise) { + super(rawRootElement); } - host(): TestElement { - return this._rootElement; + static root(fixture: ComponentFixture): TestbedHarnessEnvironment { + const stabilize = async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }; + return new TestbedHarnessEnvironment(fixture.nativeElement, stabilize); } - async querySelector(selector: string, options?: QueryOptions): Promise { - await this._stabilize(); - const e = getElement(selector, this._root, options); - return e && new UnitTestElement(e, this._stabilize); + async findAll(selector: string): Promise { + return (await this.getAllRawElements(selector)) + .map(e => new TestbedHarnessEnvironment(e, this._stabilize)); } - async querySelectorAll(selector: string): Promise { - await this._stabilize(); - return Array.prototype.map.call( - this._root.querySelectorAll(selector), - (e: Element) => new UnitTestElement(e, this._stabilize)); + protected createTestElement(element: Element): TestElement { + return new UnitTestElement(element, this._stabilize); } - async load( - componentHarness: ComponentHarnessConstructor, selector: string, - options?: QueryOptions): Promise { - await this._stabilize(); - const root = getElement(selector, this._root, options); - return root && new componentHarness(new UnitTestLocator(root, this._stabilize)); + protected createHarness( + harnessType: ComponentHarnessConstructor, element: Element): T { + return new harnessType(new TestbedHarnessEnvironment(element, this._stabilize)); } - async loadAll( - componentHarness: ComponentHarnessConstructor, - rootSelector: string): Promise { + protected async getAllRawElements(selector: string): Promise { await this._stabilize(); - return Array.prototype.map.call( - this._root.querySelectorAll(rootSelector), - (e: Element) => new componentHarness(new UnitTestLocator(e, this._stabilize))); + return Array.prototype.slice.call(this.rawRootElement.querySelectorAll(selector)); } } @@ -180,28 +145,3 @@ class UnitTestElement implements TestElement { 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; -} From 71fbadef5d7bac9deb8d507e7b05d315ca2e3ea4 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Wed, 5 Jun 2019 15:48:13 -0700 Subject: [PATCH 02/11] e2e tests working --- .../testing/component-harness.ts | 56 ++++++++++++------- src/cdk-experimental/testing/protractor.ts | 24 ++++++-- src/cdk-experimental/testing/testbed.ts | 25 +++++++-- .../tests/harnesses/main-component-harness.ts | 45 ++++++++------- .../tests/harnesses/sub-component-harness.ts | 6 +- .../testing/tests/protractor.e2e.spec.ts | 27 +++------ 6 files changed, 111 insertions(+), 72 deletions(-) diff --git a/src/cdk-experimental/testing/component-harness.ts b/src/cdk-experimental/testing/component-harness.ts index d3d1ff91281e..8c72b6da334f 100644 --- a/src/cdk-experimental/testing/component-harness.ts +++ b/src/cdk-experimental/testing/component-harness.ts @@ -22,6 +22,7 @@ export interface HarnessEnvironment { } export interface LocatorFactory { + documentRootLocatorFactory(): LocatorFactory; rootElement(): TestElement; requiredLocator(selector: string): AsyncFn; requiredLocator(harness: ComponentHarnessConstructor): AsyncFn; @@ -36,13 +37,17 @@ export interface LocatorFactory { export abstract class AbstractHarnessEnvironment implements HarnessEnvironment, LocatorFactory { protected constructor(protected rawRootElement: E) {} - abstract findAll(selector: string): Promise; + abstract documentRootLocatorFactory(): LocatorFactory; protected abstract createTestElement(element: E): TestElement; protected abstract createHarness( harnessType: ComponentHarnessConstructor, element: E): T; + protected abstract createEnvironment(element: E): HarnessEnvironment; + + protected abstract getRawElement(selector: string): Promise; + protected abstract getAllRawElements(selector: string): Promise; rootElement(): TestElement { @@ -54,9 +59,16 @@ export abstract class AbstractHarnessEnvironment implements HarnessEnvironmen requiredLocator( arg: string | ComponentHarnessConstructor): AsyncFn { return async () => { - const result = await this._createTestElementOrHarness(arg); - if (result) { - return result; + 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.createHarness(arg, element); + } } const selector = typeof arg === 'string' ? arg : arg.hostSelector; throw Error(`Expected to find element matching selector: "${selector}", but none was found`); @@ -69,7 +81,13 @@ export abstract class AbstractHarnessEnvironment implements HarnessEnvironmen optionalLocator( arg: string | ComponentHarnessConstructor): AsyncFn { return async () => { - return this._createTestElementOrHarness(arg); + 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.createHarness(arg, element) : null; + } }; } @@ -101,26 +119,20 @@ export abstract class AbstractHarnessEnvironment implements HarnessEnvironmen } async findRequired(selector: string): Promise { - const environment = (await this.findAll(selector))[0]; - if (!environment) { - throw Error(`Expected to find element matching selector: "${selector}", but none was found`); + const element = await this.getRawElement(selector); + if (element) { + return this.createEnvironment(element); } - return environment; + throw Error(`Expected to find element matching selector: "${selector}", but none was found`); } async findOptional(selector: string): Promise { - return (await this.findAll(selector))[0] || null; + const element = await this.getRawElement(selector); + return element ? this.createEnvironment(element) : null; } - private async _createTestElementOrHarness( - arg: string | ComponentHarnessConstructor): Promise { - if (typeof arg === 'string') { - const element = (await this.getAllRawElements(arg))[0]; - return this.createTestElement(element) || null; - } else { - const element = (await this.getAllRawElements(arg.hostSelector))[0]; - return this.createHarness(arg, element) || null; - } + async findAll(selector: string): Promise { + return (await this.getAllRawElements(selector)).map(e => this.createEnvironment(e)); } } @@ -137,6 +149,10 @@ export abstract class ComponentHarness { return this.locatorFacotry.rootElement(); } + protected documentRootLocatorFactory(): LocatorFactory { + return this.locatorFacotry.documentRootLocatorFactory(); + } + protected requiredLocator(selector: string): AsyncFn; protected requiredLocator(harness: ComponentHarnessConstructor): AsyncFn; @@ -161,7 +177,7 @@ export abstract class ComponentHarness { /** Constructor for a ComponentHarness subclass. */ export interface ComponentHarnessConstructor { - new(environment: HarnessEnvironment): T; + new(locatorFactory: LocatorFactory): T; hostSelector: string; } diff --git a/src/cdk-experimental/testing/protractor.ts b/src/cdk-experimental/testing/protractor.ts index deb7ab62682c..88146e75daf1 100644 --- a/src/cdk-experimental/testing/protractor.ts +++ b/src/cdk-experimental/testing/protractor.ts @@ -13,16 +13,21 @@ import { ComponentHarness, ComponentHarnessConstructor, HarnessEnvironment, + LocatorFactory, } from './component-harness'; import {TestElement} from './test-element'; export class ProtractorHarnessEnvironment extends AbstractHarnessEnvironment { - static root(): ProtractorHarnessEnvironment { + protected constructor(rawRootElement: ElementFinder) { + super(rawRootElement); + } + + static create(): HarnessEnvironment { return new ProtractorHarnessEnvironment(protractorElement(by.css('body'))); } - async findAll(selector: string): Promise { - return (await this.getAllRawElements(selector)).map(e => new ProtractorHarnessEnvironment(e)); + documentRootLocatorFactory(): LocatorFactory { + return new ProtractorHarnessEnvironment(protractorElement(by.css('body'))); } protected createTestElement(element: ElementFinder): TestElement { @@ -34,9 +39,18 @@ export class ProtractorHarnessEnvironment extends AbstractHarnessEnvironment { + const element = this.rawRootElement.element(by.css(selector)); + return await element.isPresent() ? element : null; + } + protected async getAllRawElements(selector: string): Promise { - const elementFinders = this.rawRootElement.all(by.css(selector)); - return elementFinders.reduce( + const elements = this.rawRootElement.all(by.css(selector)); + return elements.reduce( (result: ElementFinder[], el: ElementFinder) => el ? result.concat([el]) : result, []); } } diff --git a/src/cdk-experimental/testing/testbed.ts b/src/cdk-experimental/testing/testbed.ts index aea5a044564c..297fc4ffface 100644 --- a/src/cdk-experimental/testing/testbed.ts +++ b/src/cdk-experimental/testing/testbed.ts @@ -19,7 +19,8 @@ import { AbstractHarnessEnvironment, ComponentHarness, ComponentHarnessConstructor, - HarnessEnvironment + HarnessEnvironment, + LocatorFactory } from './component-harness'; import {TestElement} from './test-element'; @@ -28,11 +29,11 @@ import {TestElement} from './test-element'; * Note that, this locator is exposed for internal usage, please do not use it. */ export class TestbedHarnessEnvironment extends AbstractHarnessEnvironment { - constructor(rawRootElement: Element, private _stabilize: () => Promise) { + protected constructor(rawRootElement: Element, private _stabilize: () => Promise) { super(rawRootElement); } - static root(fixture: ComponentFixture): TestbedHarnessEnvironment { + static create(fixture: ComponentFixture): TestbedHarnessEnvironment { const stabilize = async () => { fixture.detectChanges(); await fixture.whenStable(); @@ -40,9 +41,12 @@ export class TestbedHarnessEnvironment extends AbstractHarnessEnvironment { - return (await this.getAllRawElements(selector)) - .map(e => new TestbedHarnessEnvironment(e, this._stabilize)); + documentRootLocatorFactory(): LocatorFactory { + let element = this.rawRootElement; + while (element.parentElement) { + element = element.parentElement; + } + return new TestbedHarnessEnvironment(element, this._stabilize); } protected createTestElement(element: Element): TestElement { @@ -54,6 +58,15 @@ export class TestbedHarnessEnvironment extends AbstractHarnessEnvironment { + await this._stabilize(); + return this.rawRootElement.querySelector(selector) || null; + } + protected async getAllRawElements(selector: string): Promise { await this._stabilize(); return Array.prototype.slice.call(this.rawRootElement.querySelectorAll(selector)); diff --git a/src/cdk-experimental/testing/tests/harnesses/main-component-harness.ts b/src/cdk-experimental/testing/tests/harnesses/main-component-harness.ts index b31421814c90..5a2523fb4b30 100644 --- a/src/cdk-experimental/testing/tests/harnesses/main-component-harness.ts +++ b/src/cdk-experimental/testing/tests/harnesses/main-component-harness.ts @@ -10,30 +10,33 @@ 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.requiredLocator('h1'); + readonly asyncCounter = this.requiredLocator('#asyncCounter'); + readonly counter = this.requiredLocator('#counter'); + readonly input = this.requiredLocator('#input'); + readonly value = this.requiredLocator('#value'); + readonly allLabels = this.allLocator('label'); + readonly allLists = this.allLocator(SubComponentHarness); + readonly memo = this.requiredLocator('textarea'); // Allow null for element - readonly nullItem = this.find('wrong locator', {allowNull: true}); + readonly nullItem = this.optionalLocator('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 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}); - - private _button = this.find('button'); - private _testTools = this.find(SubComponentHarness, 'test-sub'); + readonly nullComponentHarness = this.optionalLocator(WrongComponentHarness); + readonly errorItem = this.requiredLocator('wrong locator'); + + readonly globalEl = this.documentRootLocatorFactory().requiredLocator('.sibling'); + readonly errorGlobalEl = this.documentRootLocatorFactory().requiredLocator('wrong locator'); + readonly nullGlobalEl = this.documentRootLocatorFactory().optionalLocator('wrong locator'); + + private _button = this.requiredLocator('button'); + private _testTools = this.requiredLocator(SubComponentHarness); async increaseCounter(times: number) { const button = await this._button(); diff --git a/src/cdk-experimental/testing/tests/harnesses/sub-component-harness.ts b/src/cdk-experimental/testing/tests/harnesses/sub-component-harness.ts index a70ffd3418a3..64d8e5e9e4cf 100644 --- a/src/cdk-experimental/testing/tests/harnesses/sub-component-harness.ts +++ b/src/cdk-experimental/testing/tests/harnesses/sub-component-harness.ts @@ -10,8 +10,10 @@ 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.requiredLocator('h2'); + readonly getItems = this.allLocator('li'); async getItem(index: number): Promise { const items = await this.getItems(); diff --git a/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts b/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts index cc70b96618da..cf7076faa4e8 100644 --- a/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts +++ b/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts @@ -1,14 +1,14 @@ -import {browser, by, element} from 'protractor'; +import {browser} from 'protractor'; -import {getElementFinder, load} from '../protractor'; -import {MainComponentHarness} from './harnesses/main-component-harness'; +import {ProtractorHarnessEnvironment} from '../protractor'; +import {MainComponentHarness, WrongComponentHarness} from './harnesses/main-component-harness'; describe('Protractor Helper Test', () => { let harness: MainComponentHarness; beforeEach(async () => { await browser.get('/component-harness'); - harness = await load(MainComponentHarness, 'test-main'); + harness = await ProtractorHarnessEnvironment.create().requiredHarness(MainComponentHarness); }); describe('Locator', () => { @@ -122,10 +122,10 @@ describe('Protractor Helper Test', () => { expect(await harness.nullItem()).toBe(null); }); - it('should allow main harness to be null when setting allowNull', + it('should allow main harness to be null when setting using optionalHarness', async () => { - const nullMainHarness = await load( - MainComponentHarness, 'harness not present', {allowNull: true}); + const nullMainHarness = + await ProtractorHarnessEnvironment.create().optionalHarness(WrongComponentHarness); expect(nullMainHarness).toBe(null); }); @@ -154,7 +154,7 @@ describe('Protractor Helper Test', () => { } catch (err) { expect(err.message) .toBe( - 'Cannot find element based on the CSS selector: wrong locator'); + 'Expected to find element matching selector: "wrong locator", but none was found'); } }); }); @@ -167,17 +167,8 @@ describe('Protractor Helper Test', () => { } catch (err) { expect(err.message) .toBe( - 'Cannot find element based on the CSS selector: wrong locator'); + 'Expected to find element matching selector: "wrong locator", but none was found'); } }); }); - - 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()); - }); - }); }); From afee790a03e9e5fa013ea7e9968923b2f2fd2c72 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 6 Jun 2019 08:44:39 -0700 Subject: [PATCH 03/11] unit tests working --- src/cdk-experimental/testing/testbed.ts | 13 ++++++++- .../testing/tests/testbed.spec.ts | 28 ++++++------------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/cdk-experimental/testing/testbed.ts b/src/cdk-experimental/testing/testbed.ts index 297fc4ffface..0ac52afcded6 100644 --- a/src/cdk-experimental/testing/testbed.ts +++ b/src/cdk-experimental/testing/testbed.ts @@ -33,7 +33,7 @@ export class TestbedHarnessEnvironment extends AbstractHarnessEnvironment): TestbedHarnessEnvironment { + static create(fixture: ComponentFixture): HarnessEnvironment { const stabilize = async () => { fixture.detectChanges(); await fixture.whenStable(); @@ -41,6 +41,17 @@ export class TestbedHarnessEnvironment extends AbstractHarnessEnvironment( + fixture: ComponentFixture, harnessType: ComponentHarnessConstructor): Promise { + const stabilize = async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }; + const environment = new TestbedHarnessEnvironment(fixture.nativeElement, stabilize); + await environment._stabilize(); + return environment.createHarness(harnessType, fixture.nativeElement); + } + documentRootLocatorFactory(): LocatorFactory { let element = this.rawRootElement; while (element.parentElement) { diff --git a/src/cdk-experimental/testing/tests/testbed.spec.ts b/src/cdk-experimental/testing/tests/testbed.spec.ts index 57a418e4b303..14d367c4a4d4 100644 --- a/src/cdk-experimental/testing/tests/testbed.spec.ts +++ b/src/cdk-experimental/testing/tests/testbed.spec.ts @@ -1,5 +1,5 @@ -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; -import {getNativeElement, load} from '../testbed'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {TestbedHarnessEnvironment} from '../testbed'; import {MainComponentHarness} from './harnesses/main-component-harness'; import {TestComponentsModule} from './test-components-module'; @@ -8,17 +8,11 @@ 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); - }); - })); + beforeEach(async () => { + await TestBed.configureTestingModule({imports: [TestComponentsModule]}).compileComponents(); + fixture = TestBed.createComponent(TestMainComponent); + harness = await TestbedHarnessEnvironment.harnessForFixtureRoot(fixture, MainComponentHarness); + }); describe('Locator', () => { it('should be able to locate a element based on CSS selector', async () => { @@ -143,14 +137,8 @@ describe('Testbed Helper Test', () => { } catch (err) { expect(err.message) .toBe( - 'Cannot find element based on the CSS selector: wrong locator'); + 'Expected to find element matching selector: "wrong locator", but none was found'); } }); }); - - describe('getNativeElement', () => { - it('should return the native element', async () => { - expect(getNativeElement(harness.host())).toBe(fixture.nativeElement); - }); - }); }); From 24c318ba21de1e57ce5550cb7b46a47f07d0c4e4 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 6 Jun 2019 08:58:56 -0700 Subject: [PATCH 04/11] rename some things --- .../testing/component-harness.ts | 32 +++++++++---------- src/cdk-experimental/testing/protractor.ts | 8 ++--- src/cdk-experimental/testing/testbed.ts | 10 +++--- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/cdk-experimental/testing/component-harness.ts b/src/cdk-experimental/testing/component-harness.ts index 8c72b6da334f..15a6b6a64c17 100644 --- a/src/cdk-experimental/testing/component-harness.ts +++ b/src/cdk-experimental/testing/component-harness.ts @@ -11,10 +11,10 @@ import {TestElement} from './test-element'; /** An async function that returns a promise of the given type when called. */ export type AsyncFn = () => Promise; -export interface HarnessEnvironment { - findRequired(selector: string): Promise; - findOptional(selector: string): Promise; - findAll(selector: string): Promise; +export interface HarnessLoader { + findRequired(selector: string): Promise; + findOptional(selector: string): Promise; + findAll(selector: string): Promise; requiredHarness(harness: ComponentHarnessConstructor): Promise; optionalHarness(harness: ComponentHarnessConstructor): Promise; @@ -34,17 +34,17 @@ export interface LocatorFactory { } /** Interface that is used to find elements in the DOM and create harnesses for them. */ -export abstract class AbstractHarnessEnvironment implements HarnessEnvironment, LocatorFactory { +export abstract class AbstractHarnessEnvironment implements HarnessLoader, LocatorFactory { protected constructor(protected rawRootElement: E) {} abstract documentRootLocatorFactory(): LocatorFactory; protected abstract createTestElement(element: E): TestElement; - protected abstract createHarness( + protected abstract createComponentHarness( harnessType: ComponentHarnessConstructor, element: E): T; - protected abstract createEnvironment(element: E): HarnessEnvironment; + protected abstract createHarnessLoader(element: E): HarnessLoader; protected abstract getRawElement(selector: string): Promise; @@ -67,7 +67,7 @@ export abstract class AbstractHarnessEnvironment implements HarnessEnvironmen } else { const element = await this.getRawElement(arg.hostSelector); if (element) { - return this.createHarness(arg, element); + return this.createComponentHarness(arg, element); } } const selector = typeof arg === 'string' ? arg : arg.hostSelector; @@ -86,7 +86,7 @@ export abstract class AbstractHarnessEnvironment implements HarnessEnvironmen return element ? this.createTestElement(element) : null; } else { const element = await this.getRawElement(arg.hostSelector); - return element ? this.createHarness(arg, element) : null; + return element ? this.createComponentHarness(arg, element) : null; } }; } @@ -100,7 +100,7 @@ export abstract class AbstractHarnessEnvironment implements HarnessEnvironmen return (await this.getAllRawElements(arg)).map(e => this.createTestElement(e)); } else { return (await this.getAllRawElements(arg.hostSelector)) - .map(e => this.createHarness(arg, e)); + .map(e => this.createComponentHarness(arg, e)); } }; } @@ -118,21 +118,21 @@ export abstract class AbstractHarnessEnvironment implements HarnessEnvironmen return this.allLocator(harness)(); } - async findRequired(selector: string): Promise { + async findRequired(selector: string): Promise { const element = await this.getRawElement(selector); if (element) { - return this.createEnvironment(element); + return this.createHarnessLoader(element); } throw Error(`Expected to find element matching selector: "${selector}", but none was found`); } - async findOptional(selector: string): Promise { + async findOptional(selector: string): Promise { const element = await this.getRawElement(selector); - return element ? this.createEnvironment(element) : null; + return element ? this.createHarnessLoader(element) : null; } - async findAll(selector: string): Promise { - return (await this.getAllRawElements(selector)).map(e => this.createEnvironment(e)); + async findAll(selector: string): Promise { + return (await this.getAllRawElements(selector)).map(e => this.createHarnessLoader(e)); } } diff --git a/src/cdk-experimental/testing/protractor.ts b/src/cdk-experimental/testing/protractor.ts index 88146e75daf1..8491cad0f2b7 100644 --- a/src/cdk-experimental/testing/protractor.ts +++ b/src/cdk-experimental/testing/protractor.ts @@ -12,7 +12,7 @@ import { AbstractHarnessEnvironment, ComponentHarness, ComponentHarnessConstructor, - HarnessEnvironment, + HarnessLoader, LocatorFactory, } from './component-harness'; import {TestElement} from './test-element'; @@ -22,7 +22,7 @@ export class ProtractorHarnessEnvironment extends AbstractHarnessEnvironment( + protected createComponentHarness( harnessType: ComponentHarnessConstructor, element: ElementFinder): T { return new harnessType(new ProtractorHarnessEnvironment(element)); } - protected createEnvironment(element: ElementFinder): HarnessEnvironment { + protected createHarnessLoader(element: ElementFinder): HarnessLoader { return new ProtractorHarnessEnvironment(element); } diff --git a/src/cdk-experimental/testing/testbed.ts b/src/cdk-experimental/testing/testbed.ts index 0ac52afcded6..9f710ec50d37 100644 --- a/src/cdk-experimental/testing/testbed.ts +++ b/src/cdk-experimental/testing/testbed.ts @@ -19,7 +19,7 @@ import { AbstractHarnessEnvironment, ComponentHarness, ComponentHarnessConstructor, - HarnessEnvironment, + HarnessLoader, LocatorFactory } from './component-harness'; import {TestElement} from './test-element'; @@ -33,7 +33,7 @@ export class TestbedHarnessEnvironment extends AbstractHarnessEnvironment): HarnessEnvironment { + static create(fixture: ComponentFixture): HarnessLoader { const stabilize = async () => { fixture.detectChanges(); await fixture.whenStable(); @@ -49,7 +49,7 @@ export class TestbedHarnessEnvironment extends AbstractHarnessEnvironment( + protected createComponentHarness( harnessType: ComponentHarnessConstructor, element: Element): T { return new harnessType(new TestbedHarnessEnvironment(element, this._stabilize)); } - protected createEnvironment(element: Element): HarnessEnvironment { + protected createHarnessLoader(element: Element): HarnessLoader { return new TestbedHarnessEnvironment(element, this._stabilize); } From 8651520213008d4a63f0a52f5dfb37843b3181db Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 6 Jun 2019 09:30:29 -0700 Subject: [PATCH 05/11] split up files --- .../testing/component-harness.ts | 103 ----------- .../testing/harness-environment.ts | 119 ++++++++++++ .../testing/protractor/index.ts | 10 + .../testing/protractor/protractor-element.ts | 52 ++++++ .../protractor-harness-environment.ts} | 52 +----- src/cdk-experimental/testing/public-api.ts | 7 +- src/cdk-experimental/testing/testbed.ts | 171 ------------------ src/cdk-experimental/testing/testbed/index.ts | 10 + .../testbed/testbed-harness-environment.ts | 78 ++++++++ .../testing/testbed/unit-test-element.ts | 102 +++++++++++ .../testing/tests/protractor.e2e.spec.ts | 1 - .../testing/tests/testbed.spec.ts | 1 - 12 files changed, 379 insertions(+), 327 deletions(-) create mode 100644 src/cdk-experimental/testing/harness-environment.ts create mode 100644 src/cdk-experimental/testing/protractor/index.ts create mode 100644 src/cdk-experimental/testing/protractor/protractor-element.ts rename src/cdk-experimental/testing/{protractor.ts => protractor/protractor-harness-environment.ts} (60%) delete mode 100644 src/cdk-experimental/testing/testbed.ts create mode 100644 src/cdk-experimental/testing/testbed/index.ts create mode 100644 src/cdk-experimental/testing/testbed/testbed-harness-environment.ts create mode 100644 src/cdk-experimental/testing/testbed/unit-test-element.ts diff --git a/src/cdk-experimental/testing/component-harness.ts b/src/cdk-experimental/testing/component-harness.ts index 15a6b6a64c17..2cc958b3e69a 100644 --- a/src/cdk-experimental/testing/component-harness.ts +++ b/src/cdk-experimental/testing/component-harness.ts @@ -33,109 +33,6 @@ export interface LocatorFactory { allLocator(harness: ComponentHarnessConstructor): AsyncFn; } -/** Interface that is used to find elements in the DOM and create harnesses for them. */ -export abstract class AbstractHarnessEnvironment implements HarnessLoader, LocatorFactory { - protected constructor(protected rawRootElement: E) {} - - abstract documentRootLocatorFactory(): LocatorFactory; - - protected abstract createTestElement(element: E): TestElement; - - protected abstract createComponentHarness( - harnessType: ComponentHarnessConstructor, element: E): T; - - protected abstract createHarnessLoader(element: E): HarnessLoader; - - protected abstract getRawElement(selector: string): Promise; - - protected abstract getAllRawElements(selector: string): Promise; - - rootElement(): TestElement { - return this.createTestElement(this.rawRootElement); - } - - requiredLocator(selector: string): AsyncFn; - requiredLocator(harness: ComponentHarnessConstructor): AsyncFn; - requiredLocator( - arg: string | ComponentHarnessConstructor): AsyncFn { - 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`); - }; - } - - optionalLocator(selector: string): AsyncFn; - optionalLocator(harness: ComponentHarnessConstructor): - AsyncFn; - optionalLocator( - arg: string | ComponentHarnessConstructor): AsyncFn { - 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; - } - }; - } - - allLocator(selector: string): AsyncFn; - allLocator(harness: ComponentHarnessConstructor): AsyncFn; - allLocator( - arg: string | ComponentHarnessConstructor): AsyncFn { - 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)); - } - }; - } - - requiredHarness(harness: ComponentHarnessConstructor): Promise { - return this.requiredLocator(harness)(); - } - - optionalHarness(harness: ComponentHarnessConstructor): - Promise { - return this.optionalLocator(harness)(); - } - - allHarnesses(harness: ComponentHarnessConstructor): Promise { - return this.allLocator(harness)(); - } - - async findRequired(selector: string): Promise { - const element = await this.getRawElement(selector); - if (element) { - return this.createHarnessLoader(element); - } - throw Error(`Expected to find element matching selector: "${selector}", but none was found`); - } - - async findOptional(selector: string): Promise { - const element = await this.getRawElement(selector); - return element ? this.createHarnessLoader(element) : null; - } - - async findAll(selector: string): Promise { - return (await this.getAllRawElements(selector)).map(e => this.createHarnessLoader(e)); - } -} - /** * Base Component Harness * This base component harness provides the basic ability to locate element and diff --git a/src/cdk-experimental/testing/harness-environment.ts b/src/cdk-experimental/testing/harness-environment.ts new file mode 100644 index 000000000000..df1a131d2fd8 --- /dev/null +++ b/src/cdk-experimental/testing/harness-environment.ts @@ -0,0 +1,119 @@ +/** + * @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'; + +/** Interface that is used to find elements in the DOM and create harnesses for them. */ +export abstract class AbstractHarnessEnvironment implements HarnessLoader, LocatorFactory { + protected constructor(protected rawRootElement: E) {} + + abstract documentRootLocatorFactory(): LocatorFactory; + + rootElement(): TestElement { + return this.createTestElement(this.rawRootElement); + } + + requiredLocator(selector: string): AsyncFn; + requiredLocator(harness: ComponentHarnessConstructor): AsyncFn; + requiredLocator( + arg: string | ComponentHarnessConstructor): AsyncFn { + 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`); + }; + } + + optionalLocator(selector: string): AsyncFn; + optionalLocator(harness: ComponentHarnessConstructor): + AsyncFn; + optionalLocator( + arg: string | ComponentHarnessConstructor): AsyncFn { + 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; + } + }; + } + + allLocator(selector: string): AsyncFn; + allLocator(harness: ComponentHarnessConstructor): AsyncFn; + allLocator( + arg: string | ComponentHarnessConstructor): AsyncFn { + 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)); + } + }; + } + + requiredHarness(harness: ComponentHarnessConstructor): Promise { + return this.requiredLocator(harness)(); + } + + optionalHarness(harness: ComponentHarnessConstructor): + Promise { + return this.optionalLocator(harness)(); + } + + allHarnesses(harness: ComponentHarnessConstructor): Promise { + return this.allLocator(harness)(); + } + + async findRequired(selector: string): Promise { + const element = await this.getRawElement(selector); + if (element) { + return this.createHarnessLoader(element); + } + throw Error(`Expected to find element matching selector: "${selector}", but none was found`); + } + + async findOptional(selector: string): Promise { + const element = await this.getRawElement(selector); + return element ? this.createHarnessLoader(element) : null; + } + + async findAll(selector: string): Promise { + return (await this.getAllRawElements(selector)).map(e => this.createHarnessLoader(e)); + } + + protected abstract createTestElement(element: E): TestElement; + + protected abstract createComponentHarness( + harnessType: ComponentHarnessConstructor, element: E): T; + + protected abstract createHarnessLoader(element: E): HarnessLoader; + + protected abstract getRawElement(selector: string): Promise; + + protected abstract getAllRawElements(selector: string): Promise; +} diff --git a/src/cdk-experimental/testing/protractor/index.ts b/src/cdk-experimental/testing/protractor/index.ts new file mode 100644 index 000000000000..7b4ab8f52b30 --- /dev/null +++ b/src/cdk-experimental/testing/protractor/index.ts @@ -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'; diff --git a/src/cdk-experimental/testing/protractor/protractor-element.ts b/src/cdk-experimental/testing/protractor/protractor-element.ts new file mode 100644 index 000000000000..3a43d2128d16 --- /dev/null +++ b/src/cdk-experimental/testing/protractor/protractor-element.ts @@ -0,0 +1,52 @@ +/** + * @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'; + +export class ProtractorElement implements TestElement { + constructor(readonly element: ElementFinder) {} + + async blur(): Promise { + return this.element['blur'](); + } + + async clear(): Promise { + return this.element.clear(); + } + + async click(): Promise { + return this.element.click(); + } + + async focus(): Promise { + return this.element['focus'](); + } + + async getCssValue(property: string): Promise { + return this.element.getCssValue(property); + } + + async hover(): Promise { + return browser.actions() + .mouseMove(await this.element.getWebElement()) + .perform(); + } + + async sendKeys(keys: string): Promise { + return this.element.sendKeys(keys); + } + + async text(): Promise { + return this.element.getText(); + } + + async getAttribute(name: string): Promise { + return this.element.getAttribute(name); + } +} diff --git a/src/cdk-experimental/testing/protractor.ts b/src/cdk-experimental/testing/protractor/protractor-harness-environment.ts similarity index 60% rename from src/cdk-experimental/testing/protractor.ts rename to src/cdk-experimental/testing/protractor/protractor-harness-environment.ts index 8491cad0f2b7..aba680888114 100644 --- a/src/cdk-experimental/testing/protractor.ts +++ b/src/cdk-experimental/testing/protractor/protractor-harness-environment.ts @@ -6,16 +6,16 @@ * found in the LICENSE file at https://angular.io/license */ -import {browser, by, element as protractorElement, ElementFinder} from 'protractor'; - +import {by, element as protractorElement, ElementFinder} from 'protractor'; import { - AbstractHarnessEnvironment, ComponentHarness, ComponentHarnessConstructor, HarnessLoader, LocatorFactory, -} from './component-harness'; -import {TestElement} from './test-element'; +} from '../component-harness'; +import {AbstractHarnessEnvironment} from '../harness-environment'; +import {TestElement} from '../test-element'; +import {ProtractorElement} from './protractor-element'; export class ProtractorHarnessEnvironment extends AbstractHarnessEnvironment { protected constructor(rawRootElement: ElementFinder) { @@ -54,45 +54,3 @@ export class ProtractorHarnessEnvironment extends AbstractHarnessEnvironment el ? result.concat([el]) : result, []); } } - -class ProtractorElement implements TestElement { - constructor(readonly element: ElementFinder) {} - - async blur(): Promise { - return this.element['blur'](); - } - - async clear(): Promise { - return this.element.clear(); - } - - async click(): Promise { - return this.element.click(); - } - - async focus(): Promise { - return this.element['focus'](); - } - - async getCssValue(property: string): Promise { - return this.element.getCssValue(property); - } - - async hover(): Promise { - return browser.actions() - .mouseMove(await this.element.getWebElement()) - .perform(); - } - - async sendKeys(keys: string): Promise { - return this.element.sendKeys(keys); - } - - async text(): Promise { - return this.element.getText(); - } - - async getAttribute(name: string): Promise { - return this.element.getAttribute(name); - } -} diff --git a/src/cdk-experimental/testing/public-api.ts b/src/cdk-experimental/testing/public-api.ts index bedfe9b759cb..d90b5ede9b41 100644 --- a/src/cdk-experimental/testing/public-api.ts +++ b/src/cdk-experimental/testing/public-api.ts @@ -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'; diff --git a/src/cdk-experimental/testing/testbed.ts b/src/cdk-experimental/testing/testbed.ts deleted file mode 100644 index 9f710ec50d37..000000000000 --- a/src/cdk-experimental/testing/testbed.ts +++ /dev/null @@ -1,171 +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 { - AbstractHarnessEnvironment, - ComponentHarness, - ComponentHarnessConstructor, - HarnessLoader, - LocatorFactory -} from './component-harness'; -import {TestElement} from './test-element'; - -/** - * Locator implementation for testbed. - * Note that, this locator is exposed for internal usage, please do not use it. - */ -export class TestbedHarnessEnvironment extends AbstractHarnessEnvironment { - protected constructor(rawRootElement: Element, private _stabilize: () => Promise) { - super(rawRootElement); - } - - static create(fixture: ComponentFixture): HarnessLoader { - const stabilize = async () => { - fixture.detectChanges(); - await fixture.whenStable(); - }; - return new TestbedHarnessEnvironment(fixture.nativeElement, stabilize); - } - - static async harnessForFixtureRoot( - fixture: ComponentFixture, harnessType: ComponentHarnessConstructor): Promise { - const stabilize = async () => { - fixture.detectChanges(); - await fixture.whenStable(); - }; - const environment = new TestbedHarnessEnvironment(fixture.nativeElement, stabilize); - await environment._stabilize(); - return environment.createComponentHarness(harnessType, fixture.nativeElement); - } - - documentRootLocatorFactory(): LocatorFactory { - let element = this.rawRootElement; - while (element.parentElement) { - element = element.parentElement; - } - return new TestbedHarnessEnvironment(element, this._stabilize); - } - - protected createTestElement(element: Element): TestElement { - return new UnitTestElement(element, this._stabilize); - } - - protected createComponentHarness( - harnessType: ComponentHarnessConstructor, element: Element): T { - return new harnessType(new TestbedHarnessEnvironment(element, this._stabilize)); - } - - protected createHarnessLoader(element: Element): HarnessLoader { - return new TestbedHarnessEnvironment(element, this._stabilize); - } - - protected async getRawElement(selector: string): Promise { - await this._stabilize(); - return this.rawRootElement.querySelector(selector) || null; - } - - protected async getAllRawElements(selector: string): Promise { - await this._stabilize(); - return Array.prototype.slice.call(this.rawRootElement.querySelectorAll(selector)); - } -} - -class UnitTestElement implements TestElement { - constructor(readonly element: Element, private _stabilize: () => Promise) {} - - async blur(): Promise { - await this._stabilize(); - triggerBlur(this.element as HTMLElement); - await this._stabilize(); - } - - async clear(): Promise { - 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 { - await this._stabilize(); - dispatchMouseEvent(this.element, 'click'); - await this._stabilize(); - } - - async focus(): Promise { - await this._stabilize(); - triggerFocus(this.element as HTMLElement); - await this._stabilize(); - } - - async getCssValue(property: string): Promise { - 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 { - await this._stabilize(); - dispatchMouseEvent(this.element, 'mouseenter'); - await this._stabilize(); - } - - async sendKeys(keys: string): Promise { - 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 { - await this._stabilize(); - return this.element.textContent || ''; - } - - async getAttribute(name: string): Promise { - 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' ; - } -} diff --git a/src/cdk-experimental/testing/testbed/index.ts b/src/cdk-experimental/testing/testbed/index.ts new file mode 100644 index 000000000000..0422b3097bf3 --- /dev/null +++ b/src/cdk-experimental/testing/testbed/index.ts @@ -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'; diff --git a/src/cdk-experimental/testing/testbed/testbed-harness-environment.ts b/src/cdk-experimental/testing/testbed/testbed-harness-environment.ts new file mode 100644 index 000000000000..2481532d6030 --- /dev/null +++ b/src/cdk-experimental/testing/testbed/testbed-harness-environment.ts @@ -0,0 +1,78 @@ +/** + * @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, + LocatorFactory +} from '../component-harness'; +import {AbstractHarnessEnvironment} from '../harness-environment'; +import {TestElement} from '../test-element'; +import {UnitTestElement} from './unit-test-element'; + +/** + * Locator implementation for testbed. + * Note that, this locator is exposed for internal usage, please do not use it. + */ +export class TestbedHarnessEnvironment extends AbstractHarnessEnvironment { + protected constructor(rawRootElement: Element, private _stabilize: () => Promise) { + super(rawRootElement); + } + + static create(fixture: ComponentFixture): HarnessLoader { + const stabilize = async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }; + return new TestbedHarnessEnvironment(fixture.nativeElement, stabilize); + } + + static async harnessForFixtureRoot( + fixture: ComponentFixture, harnessType: ComponentHarnessConstructor): Promise { + const stabilize = async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }; + const environment = new TestbedHarnessEnvironment(fixture.nativeElement, stabilize); + await environment._stabilize(); + return environment.createComponentHarness(harnessType, fixture.nativeElement); + } + + documentRootLocatorFactory(): LocatorFactory { + let element = this.rawRootElement; + while (element.parentElement) { + element = element.parentElement; + } + return new TestbedHarnessEnvironment(element, this._stabilize); + } + + protected createTestElement(element: Element): TestElement { + return new UnitTestElement(element, this._stabilize); + } + + protected createComponentHarness( + harnessType: ComponentHarnessConstructor, element: Element): T { + return new harnessType(new TestbedHarnessEnvironment(element, this._stabilize)); + } + + protected createHarnessLoader(element: Element): HarnessLoader { + return new TestbedHarnessEnvironment(element, this._stabilize); + } + + protected async getRawElement(selector: string): Promise { + await this._stabilize(); + return this.rawRootElement.querySelector(selector) || null; + } + + protected async getAllRawElements(selector: string): Promise { + await this._stabilize(); + return Array.prototype.slice.call(this.rawRootElement.querySelectorAll(selector)); + } +} diff --git a/src/cdk-experimental/testing/testbed/unit-test-element.ts b/src/cdk-experimental/testing/testbed/unit-test-element.ts new file mode 100644 index 000000000000..10bd6cf60a92 --- /dev/null +++ b/src/cdk-experimental/testing/testbed/unit-test-element.ts @@ -0,0 +1,102 @@ +/** + * @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' ; +} + +export class UnitTestElement implements TestElement { + constructor(readonly element: Element, private _stabilize: () => Promise) {} + + async blur(): Promise { + await this._stabilize(); + triggerBlur(this.element as HTMLElement); + await this._stabilize(); + } + + async clear(): Promise { + 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 { + await this._stabilize(); + dispatchMouseEvent(this.element, 'click'); + await this._stabilize(); + } + + async focus(): Promise { + await this._stabilize(); + triggerFocus(this.element as HTMLElement); + await this._stabilize(); + } + + async getCssValue(property: string): Promise { + 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 { + await this._stabilize(); + dispatchMouseEvent(this.element, 'mouseenter'); + await this._stabilize(); + } + + async sendKeys(keys: string): Promise { + 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 { + await this._stabilize(); + return this.element.textContent || ''; + } + + async getAttribute(name: string): Promise { + 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; + } +} diff --git a/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts b/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts index cf7076faa4e8..a7d8ba476b8f 100644 --- a/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts +++ b/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts @@ -1,5 +1,4 @@ import {browser} from 'protractor'; - import {ProtractorHarnessEnvironment} from '../protractor'; import {MainComponentHarness, WrongComponentHarness} from './harnesses/main-component-harness'; diff --git a/src/cdk-experimental/testing/tests/testbed.spec.ts b/src/cdk-experimental/testing/tests/testbed.spec.ts index 14d367c4a4d4..7fe9f43bdd32 100644 --- a/src/cdk-experimental/testing/tests/testbed.spec.ts +++ b/src/cdk-experimental/testing/tests/testbed.spec.ts @@ -1,7 +1,6 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {TestbedHarnessEnvironment} from '../testbed'; import {MainComponentHarness} from './harnesses/main-component-harness'; - import {TestComponentsModule} from './test-components-module'; import {TestMainComponent} from './test-main-component'; From 585d0ce4f6f13c734debfec58a6b6979c9630d1c Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 6 Jun 2019 14:56:12 -0700 Subject: [PATCH 06/11] add tsdoc comments --- .../testing/component-harness.ts | 228 ++++++++++++++++-- .../testing/harness-environment.ts | 30 ++- .../testing/protractor/protractor-element.ts | 1 + .../protractor-harness-environment.ts | 6 +- .../testbed/testbed-harness-environment.ts | 16 +- .../testing/testbed/unit-test-element.ts | 1 + 6 files changed, 255 insertions(+), 27 deletions(-) diff --git a/src/cdk-experimental/testing/component-harness.ts b/src/cdk-experimental/testing/component-harness.ts index 2cc958b3e69a..c2351400ca86 100644 --- a/src/cdk-experimental/testing/component-harness.ts +++ b/src/cdk-experimental/testing/component-harness.ts @@ -8,65 +8,254 @@ import {TestElement} from './test-element'; -/** An async function that returns a promise of the given type when called. */ +/** An async function that returns a promise when called. */ export type AsyncFn = () => Promise; +/** + * Interface used to load ComponentHarness objects. This interface is used by test authors to + * instantiate `ComponentHarness`es. + */ export interface HarnessLoader { + /** + * 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. + */ findRequired(selector: string): Promise; + + /** + * 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, null is returned. + * @param selector The selector for the root element of the new `HarnessLoader` + * @return A `HarnessLoader` rooted at the element matching the given selector, or null if no + * matching element was found. + */ findOptional(selector: string): Promise; + + /** + * 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. + */ findAll(selector: string): Promise; - requiredHarness(harness: ComponentHarnessConstructor): Promise; - optionalHarness(harness: ComponentHarnessConstructor): + + /** + * 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. + */ + requiredHarness(harnessType: ComponentHarnessConstructor): + Promise; + + /** + * 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, null is returned. + * @param harnessType The type of harness to create + * @return An instance of the given harness type, or null if none is found. + */ + optionalHarness(harnessType: ComponentHarnessConstructor): Promise; - allHarnesses(harness: ComponentHarnessConstructor): Promise; + + /** + * 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. + */ + allHarnesses(harnessType: ComponentHarnessConstructor): + Promise; } +/** + * 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; + + /** Gets 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 + */ requiredLocator(selector: string): AsyncFn; - requiredLocator(harness: ComponentHarnessConstructor): AsyncFn; + + /** + * 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. + */ + requiredLocator(harnessType: ComponentHarnessConstructor): + AsyncFn; + + /** + * 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. + */ optionalLocator(selector: string): AsyncFn; - optionalLocator(harness: ComponentHarnessConstructor): + + /** + * 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. + */ + optionalLocator(harnessType: ComponentHarnessConstructor): AsyncFn; + + /** + * 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 + */ allLocator(selector: string): AsyncFn; - allLocator(harness: ComponentHarnessConstructor): AsyncFn; + + /** + * 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. + */ + allLocator(harnessType: ComponentHarnessConstructor): AsyncFn; } /** - * 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. + * 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 locatorFacotry: LocatorFactory) {} - async host() { + /** Gets a `Promise` for the `TestElement` representing the host element of the component. */ + async host(): Promise { return this.locatorFacotry.rootElement(); } + /** + * 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 documentRootLocatorFactory(): LocatorFactory { return this.locatorFacotry.documentRootLocatorFactory(); } + /** + * 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 requiredLocator(selector: string): AsyncFn; - protected requiredLocator(harness: ComponentHarnessConstructor): - AsyncFn; + + /** + * 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 requiredLocator( + harnessType: ComponentHarnessConstructor): AsyncFn; + protected requiredLocator(arg: any): any { return this.locatorFacotry.requiredLocator(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 optionalLocator(selector: string): AsyncFn; - protected optionalLocator(harness: ComponentHarnessConstructor): - AsyncFn; + + /** + * 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 optionalLocator( + harnessType: ComponentHarnessConstructor): AsyncFn; + protected optionalLocator(arg: any): any { return this.locatorFacotry.optionalLocator(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 allLocator(selector: string): AsyncFn; - protected allLocator(harness: ComponentHarnessConstructor): + + /** + * 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 allLocator(harnessType: ComponentHarnessConstructor): AsyncFn; + protected allLocator(arg: any): any { return this.locatorFacotry.allLocator(arg); } @@ -76,5 +265,10 @@ export abstract class ComponentHarness { export interface ComponentHarnessConstructor { 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; } diff --git a/src/cdk-experimental/testing/harness-environment.ts b/src/cdk-experimental/testing/harness-environment.ts index df1a131d2fd8..93c5a8ee9f2f 100644 --- a/src/cdk-experimental/testing/harness-environment.ts +++ b/src/cdk-experimental/testing/harness-environment.ts @@ -15,16 +15,24 @@ import { } from './component-harness'; import {TestElement} from './test-element'; -/** Interface that is used to find elements in the DOM and create harnesses for them. */ -export abstract class AbstractHarnessEnvironment implements HarnessLoader, LocatorFactory { +/** + * 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 { protected constructor(protected rawRootElement: E) {} + // Part of the `HarnessLoader` interface, delegated to concrete implementation. abstract documentRootLocatorFactory(): LocatorFactory; + // Implemented as part of the `LocatorFactory` interface. rootElement(): TestElement { return this.createTestElement(this.rawRootElement); } + // Implemented as part of the `LocatorFactory` interface. requiredLocator(selector: string): AsyncFn; requiredLocator(harness: ComponentHarnessConstructor): AsyncFn; requiredLocator( @@ -46,6 +54,7 @@ export abstract class AbstractHarnessEnvironment implements HarnessLoader, Lo }; } + // Implemented as part of the `LocatorFactory` interface. optionalLocator(selector: string): AsyncFn; optionalLocator(harness: ComponentHarnessConstructor): AsyncFn; @@ -62,6 +71,7 @@ export abstract class AbstractHarnessEnvironment implements HarnessLoader, Lo }; } + // Implemented as part of the `LocatorFactory` interface. allLocator(selector: string): AsyncFn; allLocator(harness: ComponentHarnessConstructor): AsyncFn; allLocator( @@ -76,19 +86,23 @@ export abstract class AbstractHarnessEnvironment implements HarnessLoader, Lo }; } + // Implemented as part of the `HarnessLoader` interface. requiredHarness(harness: ComponentHarnessConstructor): Promise { return this.requiredLocator(harness)(); } + // Implemented as part of the `HarnessLoader` interface. optionalHarness(harness: ComponentHarnessConstructor): Promise { return this.optionalLocator(harness)(); } + // Implemented as part of the `HarnessLoader` interface. allHarnesses(harness: ComponentHarnessConstructor): Promise { return this.allLocator(harness)(); } + // Implemented as part of the `HarnessLoader` interface. async findRequired(selector: string): Promise { const element = await this.getRawElement(selector); if (element) { @@ -97,23 +111,35 @@ export abstract class AbstractHarnessEnvironment implements HarnessLoader, Lo throw Error(`Expected to find element matching selector: "${selector}", but none was found`); } + // Implemented as part of the `HarnessLoader` interface. async findOptional(selector: string): Promise { const element = await this.getRawElement(selector); return element ? this.createHarnessLoader(element) : null; } + // Implemented as part of the `HarnessLoader` interface. async findAll(selector: string): Promise { return (await this.getAllRawElements(selector)).map(e => this.createHarnessLoader(e)); } + /** Creates a `TestElement` from a raw element. */ protected abstract createTestElement(element: E): TestElement; + /** Creates a `ComponentHarness` for the given harness type with the given raw host element. */ protected abstract createComponentHarness( harnessType: ComponentHarnessConstructor, element: E): T; + /** Creates a `HarnessLoader` rooted at the given raw element. */ protected abstract createHarnessLoader(element: E): HarnessLoader; + /** + * 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; + /** + * Gets a list of all elements matching the given selector under this environment's root element. + */ protected abstract getAllRawElements(selector: string): Promise; } diff --git a/src/cdk-experimental/testing/protractor/protractor-element.ts b/src/cdk-experimental/testing/protractor/protractor-element.ts index 3a43d2128d16..1bb68c47776c 100644 --- a/src/cdk-experimental/testing/protractor/protractor-element.ts +++ b/src/cdk-experimental/testing/protractor/protractor-element.ts @@ -9,6 +9,7 @@ import {browser, ElementFinder} from 'protractor'; import {TestElement} from '../test-element'; +/** A `TestElement` implementation for Protractor. */ export class ProtractorElement implements TestElement { constructor(readonly element: ElementFinder) {} diff --git a/src/cdk-experimental/testing/protractor/protractor-harness-environment.ts b/src/cdk-experimental/testing/protractor/protractor-harness-environment.ts index aba680888114..a7d8f3c50f1d 100644 --- a/src/cdk-experimental/testing/protractor/protractor-harness-environment.ts +++ b/src/cdk-experimental/testing/protractor/protractor-harness-environment.ts @@ -13,15 +13,17 @@ import { HarnessLoader, LocatorFactory, } from '../component-harness'; -import {AbstractHarnessEnvironment} from '../harness-environment'; +import {HarnessEnvironment} from '../harness-environment'; import {TestElement} from '../test-element'; import {ProtractorElement} from './protractor-element'; -export class ProtractorHarnessEnvironment extends AbstractHarnessEnvironment { +/** A `HarnessEnvironment` implementation for Protractor. */ +export class ProtractorHarnessEnvironment extends HarnessEnvironment { protected constructor(rawRootElement: ElementFinder) { super(rawRootElement); } + /** Creates a `HarnessLoader` rooted at the document root. */ static create(): HarnessLoader { return new ProtractorHarnessEnvironment(protractorElement(by.css('body'))); } diff --git a/src/cdk-experimental/testing/testbed/testbed-harness-environment.ts b/src/cdk-experimental/testing/testbed/testbed-harness-environment.ts index 2481532d6030..bd8a8810e9a3 100644 --- a/src/cdk-experimental/testing/testbed/testbed-harness-environment.ts +++ b/src/cdk-experimental/testing/testbed/testbed-harness-environment.ts @@ -13,19 +13,17 @@ import { HarnessLoader, LocatorFactory } from '../component-harness'; -import {AbstractHarnessEnvironment} from '../harness-environment'; +import {HarnessEnvironment} from '../harness-environment'; import {TestElement} from '../test-element'; import {UnitTestElement} from './unit-test-element'; -/** - * Locator implementation for testbed. - * Note that, this locator is exposed for internal usage, please do not use it. - */ -export class TestbedHarnessEnvironment extends AbstractHarnessEnvironment { +/** A `HarnessEnvironment` implementation for Angular's Testbed. */ +export class TestbedHarnessEnvironment extends HarnessEnvironment { protected constructor(rawRootElement: Element, private _stabilize: () => Promise) { super(rawRootElement); } + /** Creates a `HarnessLoader` rooted at the given fixture's root element. */ static create(fixture: ComponentFixture): HarnessLoader { const stabilize = async () => { fixture.detectChanges(); @@ -34,6 +32,12 @@ export class TestbedHarnessEnvironment extends AbstractHarnessEnvironment( fixture: ComponentFixture, harnessType: ComponentHarnessConstructor): Promise { const stabilize = async () => { diff --git a/src/cdk-experimental/testing/testbed/unit-test-element.ts b/src/cdk-experimental/testing/testbed/unit-test-element.ts index 10bd6cf60a92..78ccfcfe7045 100644 --- a/src/cdk-experimental/testing/testbed/unit-test-element.ts +++ b/src/cdk-experimental/testing/testbed/unit-test-element.ts @@ -20,6 +20,7 @@ function isTextInput(element: Element): element is HTMLInputElement | HTMLTextAr element.nodeName.toLowerCase() === 'textarea' ; } +/** A `TestElement` implementation for unit tests. */ export class UnitTestElement implements TestElement { constructor(readonly element: Element, private _stabilize: () => Promise) {} From e80120519761d79cc81bb1e5ba893603d8f299bb Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 6 Jun 2019 16:48:45 -0700 Subject: [PATCH 07/11] add missing unit tests --- .../tests/harnesses/main-component-harness.ts | 4 + .../tests/harnesses/sub-component-harness.ts | 1 + .../testing/tests/test-main-component.html | 26 ++- .../testing/tests/testbed.spec.ts | 206 +++++++++++++----- 4 files changed, 178 insertions(+), 59 deletions(-) diff --git a/src/cdk-experimental/testing/tests/harnesses/main-component-harness.ts b/src/cdk-experimental/testing/tests/harnesses/main-component-harness.ts index 5a2523fb4b30..b8abbdbdc07e 100644 --- a/src/cdk-experimental/testing/tests/harnesses/main-component-harness.ts +++ b/src/cdk-experimental/testing/tests/harnesses/main-component-harness.ts @@ -35,6 +35,10 @@ export class MainComponentHarness extends ComponentHarness { readonly errorGlobalEl = this.documentRootLocatorFactory().requiredLocator('wrong locator'); readonly nullGlobalEl = this.documentRootLocatorFactory().optionalLocator('wrong locator'); + readonly optionalDiv = this.optionalLocator('div'); + readonly optionalSubComponent = this.optionalLocator(SubComponentHarness); + readonly errorSubComponent = this.requiredLocator(WrongComponentHarness); + private _button = this.requiredLocator('button'); private _testTools = this.requiredLocator(SubComponentHarness); diff --git a/src/cdk-experimental/testing/tests/harnesses/sub-component-harness.ts b/src/cdk-experimental/testing/tests/harnesses/sub-component-harness.ts index 64d8e5e9e4cf..2c5a3f214f0d 100644 --- a/src/cdk-experimental/testing/tests/harnesses/sub-component-harness.ts +++ b/src/cdk-experimental/testing/tests/harnesses/sub-component-harness.ts @@ -14,6 +14,7 @@ export class SubComponentHarness extends ComponentHarness { readonly title = this.requiredLocator('h2'); readonly getItems = this.allLocator('li'); + readonly globalElement = this.documentRootLocatorFactory().requiredLocator('#username'); async getItem(index: number): Promise { const items = await this.getItems(); diff --git a/src/cdk-experimental/testing/tests/test-main-component.html b/src/cdk-experimental/testing/tests/test-main-component.html index 6258c51786ef..38df12151213 100644 --- a/src/cdk-experimental/testing/tests/test-main-component.html +++ b/src/cdk-experimental/testing/tests/test-main-component.html @@ -1,12 +1,18 @@

Main Component

Hello {{username}} from Angular 2!
-
- -
{{counter}}
- -
{{asyncCounter}}
- -
Input: {{input}}
- - - +
+
+ +
{{counter}}
+ +
{{asyncCounter}}
+
+
+ +
Input: {{input}}
+ +
+
+ + +
diff --git a/src/cdk-experimental/testing/tests/testbed.spec.ts b/src/cdk-experimental/testing/tests/testbed.spec.ts index 7fe9f43bdd32..0f005c9f4d8e 100644 --- a/src/cdk-experimental/testing/tests/testbed.spec.ts +++ b/src/cdk-experimental/testing/tests/testbed.spec.ts @@ -1,33 +1,134 @@ +import {HarnessLoader} from '@angular/cdk-experimental/testing'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {TestbedHarnessEnvironment} from '../testbed'; 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; +describe('TestbedHarnessEnvironment', () => { let fixture: ComponentFixture<{}>; + beforeEach(async () => { await TestBed.configureTestingModule({imports: [TestComponentsModule]}).compileComponents(); fixture = TestBed.createComponent(TestMainComponent); - harness = await TestbedHarnessEnvironment.harnessForFixtureRoot(fixture, MainComponentHarness); }); - describe('Locator', () => { - it('should be able to locate a element based on CSS selector', async () => { + describe('HarnessLoader', () => { + let loader: HarnessLoader; + + beforeEach(async () => { + loader = TestbedHarnessEnvironment.create(fixture); + }); + + it('should create HarnessLoader from fixture', async () => { + expect(loader).not.toBeNull(); + }); + + it('should create ComponentHarness for fixture', async () => { + const harness = + await TestbedHarnessEnvironment.harnessForFixtureRoot(fixture, MainComponentHarness); + expect(harness).not.toBeNull(); + }); + + it('should find required HarnessLoader for child element', async () => { + const subcomponentsLoader = await loader.findRequired('.subcomponents'); + expect(subcomponentsLoader).not.toBeNull(); + }); + + it('should error after failing to find required HarnessLoader for child element', async () => { + try { + await loader.findRequired('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 optional HarnessLoader for child element', async () => { + const subcomponentsLoader = await loader.findOptional('.subcomponents'); + const nullLoader = await loader.findOptional('wrong-selector'); + expect(subcomponentsLoader).not.toBeNull(); + expect(nullLoader).toBeNull(); + }); + + it('should find all HarnessLoaders for child elements', async () => { + const loaders = await loader.findAll('.subcomponents,.counters'); + expect(loaders.length).toBe(2); + }); + + it('should get first matching component for required harness', async () => { + const harness = await loader.requiredHarness(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.findRequired('.counters'); + try { + await countersLoader.requiredHarness(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 first matching component for optional harness', async () => { + const countersLoader = await loader.findRequired('.counters'); + const harness1 = await loader.optionalHarness(SubComponentHarness); + const harness2 = await countersLoader.optionalHarness(SubComponentHarness); + expect(harness1).not.toBeNull(); + expect(await (await harness1!.title()).text()).toBe('List of test tools'); + expect(harness2).toBeNull(); + }); + + it('should get all matching components for all harnesses', async () => { + const harnesses = await loader.allHarnesses(SubComponentHarness); + expect(harnesses.length).toBe(2); + }); + }); + + describe('ComponentHarness', () => { + let harness: MainComponentHarness; + + beforeEach(async () => { + harness = + await TestbedHarnessEnvironment.harnessForFixtureRoot(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 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 be able to locate the sub harnesses', async () => { + 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'); @@ -35,7 +136,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(); @@ -49,9 +168,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 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'); + }); + + 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.harnessForFixtureRoot(fixture, MainComponentHarness); + }); + it('should be able to clear', async () => { const input = await harness.input(); await input.sendKeys('Yi'); @@ -80,8 +221,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 () => { @@ -108,36 +248,4 @@ describe('Testbed Helper Test', () => { 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( - 'Expected to find element matching selector: "wrong locator", but none was found'); - } - }); - }); }); From f103cf5a512573e3d1daf488827adfa47a996cff Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Fri, 7 Jun 2019 11:32:28 -0700 Subject: [PATCH 08/11] add missing e2e tests --- .../testing/tests/protractor.e2e.spec.ts | 229 +++++++++++------- .../testing/tests/testbed.spec.ts | 4 +- 2 files changed, 148 insertions(+), 85 deletions(-) diff --git a/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts b/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts index a7d8ba476b8f..aa60341d8f98 100644 --- a/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts +++ b/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts @@ -1,30 +1,122 @@ import {browser} from 'protractor'; +import {HarnessLoader} from '../component-harness'; import {ProtractorHarnessEnvironment} from '../protractor'; -import {MainComponentHarness, WrongComponentHarness} from './harnesses/main-component-harness'; - -describe('Protractor Helper Test', () => { - let harness: MainComponentHarness; +import {MainComponentHarness} from './harnesses/main-component-harness'; +import {SubComponentHarness} from './harnesses/sub-component-harness'; +describe('ProtractorHarnessEnvironment', () => { beforeEach(async () => { await browser.get('/component-harness'); - harness = await ProtractorHarnessEnvironment.create().requiredHarness(MainComponentHarness); }); - describe('Locator', () => { - it('should be able to locate a element based on CSS selector', async () => { + describe('HarnessLoader', () => { + let loader: HarnessLoader; + + beforeEach(async () => { + loader = ProtractorHarnessEnvironment.create(); + }); + + it('should create HarnessLoader', async () => { + expect(loader).not.toBeNull(); + }); + + it('should find required HarnessLoader for child element', async () => { + const subcomponentsLoader = await loader.findRequired('.subcomponents'); + expect(subcomponentsLoader).not.toBeNull(); + }); + + it('should error after failing to find required HarnessLoader for child element', async () => { + try { + await loader.findRequired('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 optional HarnessLoader for child element', async () => { + const subcomponentsLoader = await loader.findOptional('.subcomponents'); + const nullLoader = await loader.findOptional('wrong-selector'); + expect(subcomponentsLoader).not.toBeNull(); + expect(nullLoader).toBeNull(); + }); + + it('should find all HarnessLoaders for child elements', async () => { + const loaders = await loader.findAll('.subcomponents,.counters'); + expect(loaders.length).toBe(2); + }); + + it('should get first matching component for required harness', async () => { + const harness = await loader.requiredHarness(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.findRequired('.counters'); + try { + await countersLoader.requiredHarness(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 first matching component for optional harness', async () => { + const countersLoader = await loader.findRequired('.counters'); + const harness1 = await loader.optionalHarness(SubComponentHarness); + const harness2 = await countersLoader.optionalHarness(SubComponentHarness); + expect(harness1).not.toBeNull(); + expect(await (await harness1!.title()).text()).toBe('List of test tools'); + expect(harness2).toBeNull(); + }); + + it('should get all matching components for all harnesses', async () => { + const harnesses = await loader.allHarnesses(SubComponentHarness); + expect(harnesses.length).toBe(2); + }); + }); + + describe('ComponentHarness', () => { + let harness: MainComponentHarness; + + beforeEach(async () => { + harness = await ProtractorHarnessEnvironment.create().requiredHarness(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 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 be able to locate the sub harnesses', async () => { + it('should locate required sub harnesses', async () => { const items = await harness.getTestTools(); expect(items.length).toBe(3); expect(await items[0].text()).toBe('Protractor'); @@ -32,7 +124,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(); @@ -46,9 +156,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.create().requiredHarness(MainComponentHarness); + }); + it('should be able to clear', async () => { const input = await harness.input(); await input.sendKeys('Yi'); @@ -78,8 +206,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,68 +233,4 @@ describe('Protractor Helper Test', () => { 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 using optionalHarness', - async () => { - const nullMainHarness = - await ProtractorHarnessEnvironment.create().optionalHarness(WrongComponentHarness); - 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( - 'Expected to find element matching selector: "wrong locator", but none was found'); - } - }); - }); - - describe('Throw error', () => { - it('should show the correct error', async () => { - try { - await harness.errorItem(); - fail('Should throw error'); - } catch (err) { - expect(err.message) - .toBe( - 'Expected to find element matching selector: "wrong locator", but none was found'); - } - }); - }); }); diff --git a/src/cdk-experimental/testing/tests/testbed.spec.ts b/src/cdk-experimental/testing/tests/testbed.spec.ts index 0f005c9f4d8e..83b5bd97fea2 100644 --- a/src/cdk-experimental/testing/tests/testbed.spec.ts +++ b/src/cdk-experimental/testing/tests/testbed.spec.ts @@ -1,5 +1,5 @@ -import {HarnessLoader} from '@angular/cdk-experimental/testing'; import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '../component-harness'; import {TestbedHarnessEnvironment} from '../testbed'; import {MainComponentHarness} from './harnesses/main-component-harness'; import {SubComponentHarness} from './harnesses/sub-component-harness'; @@ -169,7 +169,7 @@ describe('TestbedHarnessEnvironment', () => { expect(await items2[2].text()).toBe('Performance Test'); }); - it('should wait for async opeartion to complete', async () => { + it('should wait for async operation to complete', async () => { const asyncCounter = await harness.asyncCounter(); expect(await asyncCounter.text()).toBe('5'); await harness.increaseCounter(3); From aba2e76238d8231db84fc3729ad793127554699e Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Mon, 10 Jun 2019 11:24:52 -0700 Subject: [PATCH 09/11] fix ci --- src/cdk-experimental/testing/tests/testbed.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cdk-experimental/testing/tests/testbed.spec.ts b/src/cdk-experimental/testing/tests/testbed.spec.ts index 83b5bd97fea2..21b15f271c8c 100644 --- a/src/cdk-experimental/testing/tests/testbed.spec.ts +++ b/src/cdk-experimental/testing/tests/testbed.spec.ts @@ -1,6 +1,6 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {HarnessLoader} from '../component-harness'; -import {TestbedHarnessEnvironment} from '../testbed'; +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'; From 6f3384f02a68f82a419dae3c0fccb04811b08332 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Wed, 12 Jun 2019 15:26:51 -0700 Subject: [PATCH 10/11] address feedback --- .../testing/component-harness.ts | 37 ++++++++--------- .../testing/harness-environment.ts | 25 ++++++------ .../testing/protractor/protractor-element.ts | 4 +- .../testbed/testbed-harness-environment.ts | 2 +- .../tests/harnesses/main-component-harness.ts | 40 +++++++++---------- .../tests/harnesses/sub-component-harness.ts | 6 +-- .../testing/tests/protractor.e2e.spec.ts | 11 +++++ .../testing/tests/testbed.spec.ts | 13 ++++++ 8 files changed, 82 insertions(+), 56 deletions(-) diff --git a/src/cdk-experimental/testing/component-harness.ts b/src/cdk-experimental/testing/component-harness.ts index c2351400ca86..2efc409d5d08 100644 --- a/src/cdk-experimental/testing/component-harness.ts +++ b/src/cdk-experimental/testing/component-harness.ts @@ -99,7 +99,7 @@ export interface LocatorFactory { * @return An asynchronous locator function that searches for elements with the given selector, * and either finds one or throws an error */ - requiredLocator(selector: string): AsyncFn; + locatorForRequired(selector: string): AsyncFn; /** * Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a @@ -110,7 +110,7 @@ export interface LocatorFactory { * @return An asynchronous locator function that searches components matching the given harness * type, and either returns a `ComponentHarness` for the component, or throws an error. */ - requiredLocator(harnessType: ComponentHarnessConstructor): + locatorForRequired(harnessType: ComponentHarnessConstructor): AsyncFn; /** @@ -122,7 +122,7 @@ export interface LocatorFactory { * @return An asynchronous locator function that searches for elements with the given selector, * and either finds one or returns null. */ - optionalLocator(selector: string): AsyncFn; + locatorForOptional(selector: string): AsyncFn; /** * Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a @@ -133,7 +133,7 @@ export interface LocatorFactory { * @return An asynchronous locator function that searches components matching the given harness * type, and either returns a `ComponentHarness` for the component, or null if none is found. */ - optionalLocator(harnessType: ComponentHarnessConstructor): + locatorForOptional(harnessType: ComponentHarnessConstructor): AsyncFn; /** @@ -144,7 +144,7 @@ export interface LocatorFactory { * @return An asynchronous locator function that searches for elements with the given selector, * and either finds one or throws an error */ - allLocator(selector: string): AsyncFn; + locatorForAll(selector: string): AsyncFn; /** * Creates an asynchronous locator function that can be used to find a list of @@ -155,7 +155,8 @@ export interface LocatorFactory { * @return An asynchronous locator function that searches components matching the given harness * type, and returns a list of `ComponentHarness`es. */ - allLocator(harnessType: ComponentHarnessConstructor): AsyncFn; + locatorForAll(harnessType: ComponentHarnessConstructor): + AsyncFn; } /** @@ -189,7 +190,7 @@ export abstract class ComponentHarness { * @return An asynchronous locator function that searches for elements with the given selector, * and either finds one or throws an error */ - protected requiredLocator(selector: string): AsyncFn; + protected locatorForRequired(selector: string): AsyncFn; /** * Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a @@ -200,11 +201,11 @@ export abstract class ComponentHarness { * @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 requiredLocator( + protected locatorForRequired( harnessType: ComponentHarnessConstructor): AsyncFn; - protected requiredLocator(arg: any): any { - return this.locatorFacotry.requiredLocator(arg); + protected locatorForRequired(arg: any): any { + return this.locatorFacotry.locatorForRequired(arg); } /** @@ -216,7 +217,7 @@ export abstract class ComponentHarness { * @return An asynchronous locator function that searches for elements with the given selector, * and either finds one or returns null. */ - protected optionalLocator(selector: string): AsyncFn; + protected locatorForOptional(selector: string): AsyncFn; /** * Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a @@ -227,11 +228,11 @@ export abstract class ComponentHarness { * @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 optionalLocator( + protected locatorForOptional( harnessType: ComponentHarnessConstructor): AsyncFn; - protected optionalLocator(arg: any): any { - return this.locatorFacotry.optionalLocator(arg); + protected locatorForOptional(arg: any): any { + return this.locatorFacotry.locatorForOptional(arg); } /** @@ -242,7 +243,7 @@ export abstract class ComponentHarness { * @return An asynchronous locator function that searches for elements with the given selector, * and either finds one or throws an error */ - protected allLocator(selector: string): AsyncFn; + protected locatorForAll(selector: string): AsyncFn; /** * Creates an asynchronous locator function that can be used to find a list of @@ -253,11 +254,11 @@ export abstract class ComponentHarness { * @return An asynchronous locator function that searches components matching the given harness * type, and returns a list of `ComponentHarness`es. */ - protected allLocator(harnessType: ComponentHarnessConstructor): + protected locatorForAll(harnessType: ComponentHarnessConstructor): AsyncFn; - protected allLocator(arg: any): any { - return this.locatorFacotry.allLocator(arg); + protected locatorForAll(arg: any): any { + return this.locatorFacotry.locatorForAll(arg); } } diff --git a/src/cdk-experimental/testing/harness-environment.ts b/src/cdk-experimental/testing/harness-environment.ts index 93c5a8ee9f2f..3c8033971332 100644 --- a/src/cdk-experimental/testing/harness-environment.ts +++ b/src/cdk-experimental/testing/harness-environment.ts @@ -33,9 +33,10 @@ export abstract class HarnessEnvironment implements HarnessLoader, LocatorFac } // Implemented as part of the `LocatorFactory` interface. - requiredLocator(selector: string): AsyncFn; - requiredLocator(harness: ComponentHarnessConstructor): AsyncFn; - requiredLocator( + locatorForRequired(selector: string): AsyncFn; + locatorForRequired(harness: ComponentHarnessConstructor): + AsyncFn; + locatorForRequired( arg: string | ComponentHarnessConstructor): AsyncFn { return async () => { if (typeof arg === 'string') { @@ -55,10 +56,10 @@ export abstract class HarnessEnvironment implements HarnessLoader, LocatorFac } // Implemented as part of the `LocatorFactory` interface. - optionalLocator(selector: string): AsyncFn; - optionalLocator(harness: ComponentHarnessConstructor): + locatorForOptional(selector: string): AsyncFn; + locatorForOptional(harness: ComponentHarnessConstructor): AsyncFn; - optionalLocator( + locatorForOptional( arg: string | ComponentHarnessConstructor): AsyncFn { return async () => { if (typeof arg === 'string') { @@ -72,9 +73,9 @@ export abstract class HarnessEnvironment implements HarnessLoader, LocatorFac } // Implemented as part of the `LocatorFactory` interface. - allLocator(selector: string): AsyncFn; - allLocator(harness: ComponentHarnessConstructor): AsyncFn; - allLocator( + locatorForAll(selector: string): AsyncFn; + locatorForAll(harness: ComponentHarnessConstructor): AsyncFn; + locatorForAll( arg: string | ComponentHarnessConstructor): AsyncFn { return async () => { if (typeof arg === 'string') { @@ -88,18 +89,18 @@ export abstract class HarnessEnvironment implements HarnessLoader, LocatorFac // Implemented as part of the `HarnessLoader` interface. requiredHarness(harness: ComponentHarnessConstructor): Promise { - return this.requiredLocator(harness)(); + return this.locatorForRequired(harness)(); } // Implemented as part of the `HarnessLoader` interface. optionalHarness(harness: ComponentHarnessConstructor): Promise { - return this.optionalLocator(harness)(); + return this.locatorForOptional(harness)(); } // Implemented as part of the `HarnessLoader` interface. allHarnesses(harness: ComponentHarnessConstructor): Promise { - return this.allLocator(harness)(); + return this.locatorForAll(harness)(); } // Implemented as part of the `HarnessLoader` interface. diff --git a/src/cdk-experimental/testing/protractor/protractor-element.ts b/src/cdk-experimental/testing/protractor/protractor-element.ts index 1bb68c47776c..1a89bde62bcb 100644 --- a/src/cdk-experimental/testing/protractor/protractor-element.ts +++ b/src/cdk-experimental/testing/protractor/protractor-element.ts @@ -14,7 +14,7 @@ export class ProtractorElement implements TestElement { constructor(readonly element: ElementFinder) {} async blur(): Promise { - return this.element['blur'](); + return browser.executeScript('arguments[0].blur()', this.element); } async clear(): Promise { @@ -26,7 +26,7 @@ export class ProtractorElement implements TestElement { } async focus(): Promise { - return this.element['focus'](); + return browser.executeScript('arguments[0].focus()', this.element); } async getCssValue(property: string): Promise { diff --git a/src/cdk-experimental/testing/testbed/testbed-harness-environment.ts b/src/cdk-experimental/testing/testbed/testbed-harness-environment.ts index bd8a8810e9a3..2f0037de05a7 100644 --- a/src/cdk-experimental/testing/testbed/testbed-harness-environment.ts +++ b/src/cdk-experimental/testing/testbed/testbed-harness-environment.ts @@ -77,6 +77,6 @@ export class TestbedHarnessEnvironment extends HarnessEnvironment { protected async getAllRawElements(selector: string): Promise { await this._stabilize(); - return Array.prototype.slice.call(this.rawRootElement.querySelectorAll(selector)); + return Array.from(this.rawRootElement.querySelectorAll(selector)); } } diff --git a/src/cdk-experimental/testing/tests/harnesses/main-component-harness.ts b/src/cdk-experimental/testing/tests/harnesses/main-component-harness.ts index b8abbdbdc07e..f76f5851146b 100644 --- a/src/cdk-experimental/testing/tests/harnesses/main-component-harness.ts +++ b/src/cdk-experimental/testing/tests/harnesses/main-component-harness.ts @@ -17,33 +17,33 @@ export class WrongComponentHarness extends ComponentHarness { export class MainComponentHarness extends ComponentHarness { static readonly hostSelector = 'test-main'; - readonly title = this.requiredLocator('h1'); - readonly asyncCounter = this.requiredLocator('#asyncCounter'); - readonly counter = this.requiredLocator('#counter'); - readonly input = this.requiredLocator('#input'); - readonly value = this.requiredLocator('#value'); - readonly allLabels = this.allLocator('label'); - readonly allLists = this.allLocator(SubComponentHarness); - readonly memo = this.requiredLocator('textarea'); + readonly title = this.locatorForRequired('h1'); + readonly button = this.locatorForRequired('button'); + readonly asyncCounter = this.locatorForRequired('#asyncCounter'); + readonly counter = this.locatorForRequired('#counter'); + readonly input = this.locatorForRequired('#input'); + readonly value = this.locatorForRequired('#value'); + readonly allLabels = this.locatorForAll('label'); + readonly allLists = this.locatorForAll(SubComponentHarness); + readonly memo = this.locatorForRequired('textarea'); // Allow null for element - readonly nullItem = this.optionalLocator('wrong locator'); + readonly nullItem = this.locatorForOptional('wrong locator'); // Allow null for component harness - readonly nullComponentHarness = this.optionalLocator(WrongComponentHarness); - readonly errorItem = this.requiredLocator('wrong locator'); + readonly nullComponentHarness = this.locatorForOptional(WrongComponentHarness); + readonly errorItem = this.locatorForRequired('wrong locator'); - readonly globalEl = this.documentRootLocatorFactory().requiredLocator('.sibling'); - readonly errorGlobalEl = this.documentRootLocatorFactory().requiredLocator('wrong locator'); - readonly nullGlobalEl = this.documentRootLocatorFactory().optionalLocator('wrong locator'); + readonly globalEl = this.documentRootLocatorFactory().locatorForRequired('.sibling'); + readonly errorGlobalEl = this.documentRootLocatorFactory().locatorForRequired('wrong locator'); + readonly nullGlobalEl = this.documentRootLocatorFactory().locatorForOptional('wrong locator'); - readonly optionalDiv = this.optionalLocator('div'); - readonly optionalSubComponent = this.optionalLocator(SubComponentHarness); - readonly errorSubComponent = this.requiredLocator(WrongComponentHarness); + readonly optionalDiv = this.locatorForOptional('div'); + readonly optionalSubComponent = this.locatorForOptional(SubComponentHarness); + readonly errorSubComponent = this.locatorForRequired(WrongComponentHarness); - private _button = this.requiredLocator('button'); - private _testTools = this.requiredLocator(SubComponentHarness); + private _testTools = this.locatorForRequired(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(); } diff --git a/src/cdk-experimental/testing/tests/harnesses/sub-component-harness.ts b/src/cdk-experimental/testing/tests/harnesses/sub-component-harness.ts index 2c5a3f214f0d..e05dc4842516 100644 --- a/src/cdk-experimental/testing/tests/harnesses/sub-component-harness.ts +++ b/src/cdk-experimental/testing/tests/harnesses/sub-component-harness.ts @@ -12,9 +12,9 @@ import {TestElement} from '../../test-element'; export class SubComponentHarness extends ComponentHarness { static readonly hostSelector = 'test-sub'; - readonly title = this.requiredLocator('h2'); - readonly getItems = this.allLocator('li'); - readonly globalElement = this.documentRootLocatorFactory().requiredLocator('#username'); + readonly title = this.locatorForRequired('h2'); + readonly getItems = this.locatorForAll('li'); + readonly globalElement = this.documentRootLocatorFactory().locatorForRequired('#username'); async getItem(index: number): Promise { const items = await this.getItems(); diff --git a/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts b/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts index aa60341d8f98..3ba83ed705ff 100644 --- a/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts +++ b/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts @@ -232,5 +232,16 @@ describe('ProtractorHarnessEnvironment', () => { const title = await harness.title(); expect(await title.getCssValue('height')).toBe('50px'); }); + + 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()); + }); }); }); diff --git a/src/cdk-experimental/testing/tests/testbed.spec.ts b/src/cdk-experimental/testing/tests/testbed.spec.ts index 21b15f271c8c..0475e6dab26b 100644 --- a/src/cdk-experimental/testing/tests/testbed.spec.ts +++ b/src/cdk-experimental/testing/tests/testbed.spec.ts @@ -6,6 +6,10 @@ import {SubComponentHarness} from './harnesses/sub-component-harness'; import {TestComponentsModule} from './test-components-module'; import {TestMainComponent} from './test-main-component'; +function activeElementText() { + return document.activeElement && (document.activeElement as HTMLElement).innerText || ''; +} + describe('TestbedHarnessEnvironment', () => { let fixture: ComponentFixture<{}>; @@ -247,5 +251,14 @@ describe('TestbedHarnessEnvironment', () => { const title = await harness.title(); expect(await title.getCssValue('height')).toBe('50px'); }); + + 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()); + }); }); }); From 3289d8ef1fd1c20fd960908a0e058f15957df5ab Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Fri, 14 Jun 2019 09:54:18 -0700 Subject: [PATCH 11/11] nits --- .../testing/component-harness.ts | 55 +++++----------- .../testing/harness-environment.ts | 64 +++++++++---------- .../protractor-harness-environment.ts | 20 ++---- .../testbed/testbed-harness-environment.ts | 49 +++++--------- .../testing/testbed/unit-test-element.ts | 2 +- .../tests/harnesses/main-component-harness.ts | 24 +++---- .../tests/harnesses/sub-component-harness.ts | 4 +- .../testing/tests/protractor.e2e.spec.ts | 36 +++-------- .../testing/tests/testbed.spec.ts | 38 ++++------- 9 files changed, 105 insertions(+), 187 deletions(-) diff --git a/src/cdk-experimental/testing/component-harness.ts b/src/cdk-experimental/testing/component-harness.ts index 2efc409d5d08..65c50e671002 100644 --- a/src/cdk-experimental/testing/component-harness.ts +++ b/src/cdk-experimental/testing/component-harness.ts @@ -24,17 +24,7 @@ export interface HarnessLoader { * @return A `HarnessLoader` rooted at the element matching the given selector. * @throws If a matching element can't be found. */ - findRequired(selector: string): Promise; - - /** - * 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, null is returned. - * @param selector The selector for the root element of the new `HarnessLoader` - * @return A `HarnessLoader` rooted at the element matching the given selector, or null if no - * matching element was found. - */ - findOptional(selector: string): Promise; + getChildLoader(selector: string): Promise; /** * Searches for all elements with the given selector under the current instances's root element, @@ -43,7 +33,7 @@ export interface HarnessLoader { * @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. */ - findAll(selector: string): Promise; + getAllChildLoaders(selector: string): Promise; /** * Searches for an instance of the component corresponding to the given harness type under the @@ -54,27 +44,16 @@ export interface HarnessLoader { * @return An instance of the given harness type * @throws If a matching component instance can't be found. */ - requiredHarness(harnessType: ComponentHarnessConstructor): + getHarness(harnessType: ComponentHarnessConstructor): Promise; - /** - * 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, null is returned. - * @param harnessType The type of harness to create - * @return An instance of the given harness type, or null if none is found. - */ - optionalHarness(harnessType: ComponentHarnessConstructor): - Promise; - /** * 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. */ - allHarnesses(harnessType: ComponentHarnessConstructor): + getAllHarnesses(harnessType: ComponentHarnessConstructor): Promise; } @@ -87,8 +66,8 @@ export interface LocatorFactory { /** Gets a locator factory rooted at the document root. */ documentRootLocatorFactory(): LocatorFactory; - /** Gets the root element of this `LocatorFactory` as a `TestElement`. */ - rootElement(): TestElement; + /** 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 @@ -99,7 +78,7 @@ export interface LocatorFactory { * @return An asynchronous locator function that searches for elements with the given selector, * and either finds one or throws an error */ - locatorForRequired(selector: string): AsyncFn; + locatorFor(selector: string): AsyncFn; /** * Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a @@ -110,7 +89,7 @@ export interface LocatorFactory { * @return An asynchronous locator function that searches components matching the given harness * type, and either returns a `ComponentHarness` for the component, or throws an error. */ - locatorForRequired(harnessType: ComponentHarnessConstructor): + locatorFor(harnessType: ComponentHarnessConstructor): AsyncFn; /** @@ -165,11 +144,11 @@ export interface LocatorFactory { * should be inherited when defining user's own harness. */ export abstract class ComponentHarness { - constructor(private readonly locatorFacotry: LocatorFactory) {} + constructor(private readonly locatorFactory: LocatorFactory) {} /** Gets a `Promise` for the `TestElement` representing the host element of the component. */ async host(): Promise { - return this.locatorFacotry.rootElement(); + return this.locatorFactory.rootElement; } /** @@ -178,7 +157,7 @@ export abstract class ComponentHarness { * appending to document.body). */ protected documentRootLocatorFactory(): LocatorFactory { - return this.locatorFacotry.documentRootLocatorFactory(); + return this.locatorFactory.documentRootLocatorFactory(); } /** @@ -190,7 +169,7 @@ export abstract class ComponentHarness { * @return An asynchronous locator function that searches for elements with the given selector, * and either finds one or throws an error */ - protected locatorForRequired(selector: string): AsyncFn; + protected locatorFor(selector: string): AsyncFn; /** * Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a @@ -201,11 +180,11 @@ export abstract class ComponentHarness { * @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 locatorForRequired( + protected locatorFor( harnessType: ComponentHarnessConstructor): AsyncFn; - protected locatorForRequired(arg: any): any { - return this.locatorFacotry.locatorForRequired(arg); + protected locatorFor(arg: any): any { + return this.locatorFactory.locatorFor(arg); } /** @@ -232,7 +211,7 @@ export abstract class ComponentHarness { harnessType: ComponentHarnessConstructor): AsyncFn; protected locatorForOptional(arg: any): any { - return this.locatorFacotry.locatorForOptional(arg); + return this.locatorFactory.locatorForOptional(arg); } /** @@ -258,7 +237,7 @@ export abstract class ComponentHarness { AsyncFn; protected locatorForAll(arg: any): any { - return this.locatorFacotry.locatorForAll(arg); + return this.locatorFactory.locatorForAll(arg); } } diff --git a/src/cdk-experimental/testing/harness-environment.ts b/src/cdk-experimental/testing/harness-environment.ts index 3c8033971332..ff243d9dd07e 100644 --- a/src/cdk-experimental/testing/harness-environment.ts +++ b/src/cdk-experimental/testing/harness-environment.ts @@ -22,21 +22,23 @@ import {TestElement} from './test-element'; * element type, `E`, used by the particular test environment. */ export abstract class HarnessEnvironment implements HarnessLoader, LocatorFactory { - protected constructor(protected rawRootElement: E) {} + // Implemented as part of the `LocatorFactory` interface. + rootElement: TestElement; - // Part of the `HarnessLoader` interface, delegated to concrete implementation. - abstract documentRootLocatorFactory(): LocatorFactory; + protected constructor(protected rawRootElement: E) { + this.rootElement = this.createTestElement(rawRootElement); + } // Implemented as part of the `LocatorFactory` interface. - rootElement(): TestElement { - return this.createTestElement(this.rawRootElement); + documentRootLocatorFactory(): LocatorFactory { + return this.createEnvironment(this.getDocumentRoot()); } // Implemented as part of the `LocatorFactory` interface. - locatorForRequired(selector: string): AsyncFn; - locatorForRequired(harness: ComponentHarnessConstructor): + locatorFor(selector: string): AsyncFn; + locatorFor(harnessType: ComponentHarnessConstructor): AsyncFn; - locatorForRequired( + locatorFor( arg: string | ComponentHarnessConstructor): AsyncFn { return async () => { if (typeof arg === 'string') { @@ -57,7 +59,7 @@ export abstract class HarnessEnvironment implements HarnessLoader, LocatorFac // Implemented as part of the `LocatorFactory` interface. locatorForOptional(selector: string): AsyncFn; - locatorForOptional(harness: ComponentHarnessConstructor): + locatorForOptional(harnessType: ComponentHarnessConstructor): AsyncFn; locatorForOptional( arg: string | ComponentHarnessConstructor): AsyncFn { @@ -74,7 +76,8 @@ export abstract class HarnessEnvironment implements HarnessLoader, LocatorFac // Implemented as part of the `LocatorFactory` interface. locatorForAll(selector: string): AsyncFn; - locatorForAll(harness: ComponentHarnessConstructor): AsyncFn; + locatorForAll(harnessType: ComponentHarnessConstructor): + AsyncFn; locatorForAll( arg: string | ComponentHarnessConstructor): AsyncFn { return async () => { @@ -88,50 +91,45 @@ export abstract class HarnessEnvironment implements HarnessLoader, LocatorFac } // Implemented as part of the `HarnessLoader` interface. - requiredHarness(harness: ComponentHarnessConstructor): Promise { - return this.locatorForRequired(harness)(); - } - - // Implemented as part of the `HarnessLoader` interface. - optionalHarness(harness: ComponentHarnessConstructor): - Promise { - return this.locatorForOptional(harness)(); + getHarness(harnessType: ComponentHarnessConstructor): + Promise { + return this.locatorFor(harnessType)(); } // Implemented as part of the `HarnessLoader` interface. - allHarnesses(harness: ComponentHarnessConstructor): Promise { - return this.locatorForAll(harness)(); + getAllHarnesses(harnessType: ComponentHarnessConstructor): + Promise { + return this.locatorForAll(harnessType)(); } // Implemented as part of the `HarnessLoader` interface. - async findRequired(selector: string): Promise { + async getChildLoader(selector: string): Promise { const element = await this.getRawElement(selector); if (element) { - return this.createHarnessLoader(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 findOptional(selector: string): Promise { - const element = await this.getRawElement(selector); - return element ? this.createHarnessLoader(element) : null; + async getAllChildLoaders(selector: string): Promise { + return (await this.getAllRawElements(selector)).map(e => this.createEnvironment(e)); } - // Implemented as part of the `HarnessLoader` interface. - async findAll(selector: string): Promise { - return (await this.getAllRawElements(selector)).map(e => this.createHarnessLoader(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 `ComponentHarness` for the given harness type with the given raw host element. */ - protected abstract createComponentHarness( - harnessType: ComponentHarnessConstructor, element: E): T; - /** Creates a `HarnessLoader` rooted at the given raw element. */ - protected abstract createHarnessLoader(element: E): HarnessLoader; + protected abstract createEnvironment(element: E): HarnessEnvironment; /** * Gets the first element matching the given selector under this environment's root element, or diff --git a/src/cdk-experimental/testing/protractor/protractor-harness-environment.ts b/src/cdk-experimental/testing/protractor/protractor-harness-environment.ts index a7d8f3c50f1d..3a8fec9a4774 100644 --- a/src/cdk-experimental/testing/protractor/protractor-harness-environment.ts +++ b/src/cdk-experimental/testing/protractor/protractor-harness-environment.ts @@ -7,12 +7,7 @@ */ import {by, element as protractorElement, ElementFinder} from 'protractor'; -import { - ComponentHarness, - ComponentHarnessConstructor, - HarnessLoader, - LocatorFactory, -} from '../component-harness'; +import {HarnessLoader} from '../component-harness'; import {HarnessEnvironment} from '../harness-environment'; import {TestElement} from '../test-element'; import {ProtractorElement} from './protractor-element'; @@ -24,24 +19,19 @@ export class ProtractorHarnessEnvironment extends HarnessEnvironment( - harnessType: ComponentHarnessConstructor, element: ElementFinder): T { - return new harnessType(new ProtractorHarnessEnvironment(element)); - } - - protected createHarnessLoader(element: ElementFinder): HarnessLoader { + protected createEnvironment(element: ElementFinder): HarnessEnvironment { return new ProtractorHarnessEnvironment(element); } diff --git a/src/cdk-experimental/testing/testbed/testbed-harness-environment.ts b/src/cdk-experimental/testing/testbed/testbed-harness-environment.ts index 2f0037de05a7..18c7b09950ea 100644 --- a/src/cdk-experimental/testing/testbed/testbed-harness-environment.ts +++ b/src/cdk-experimental/testing/testbed/testbed-harness-environment.ts @@ -7,29 +7,20 @@ */ import {ComponentFixture} from '@angular/core/testing'; -import { - ComponentHarness, - ComponentHarnessConstructor, - HarnessLoader, - LocatorFactory -} from '../component-harness'; +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 { - protected constructor(rawRootElement: Element, private _stabilize: () => Promise) { + protected constructor(rawRootElement: Element, private _fixture: ComponentFixture) { super(rawRootElement); } /** Creates a `HarnessLoader` rooted at the given fixture's root element. */ - static create(fixture: ComponentFixture): HarnessLoader { - const stabilize = async () => { - fixture.detectChanges(); - await fixture.whenStable(); - }; - return new TestbedHarnessEnvironment(fixture.nativeElement, stabilize); + static loader(fixture: ComponentFixture): HarnessLoader { + return new TestbedHarnessEnvironment(fixture.nativeElement, fixture); } /** @@ -38,36 +29,23 @@ export class TestbedHarnessEnvironment extends HarnessEnvironment { * of a fixture, as components do not have the correct selector when they are created as the root * of the fixture. */ - static async harnessForFixtureRoot( + static async harnessForFixture( fixture: ComponentFixture, harnessType: ComponentHarnessConstructor): Promise { - const stabilize = async () => { - fixture.detectChanges(); - await fixture.whenStable(); - }; - const environment = new TestbedHarnessEnvironment(fixture.nativeElement, stabilize); + const environment = new TestbedHarnessEnvironment(fixture.nativeElement, fixture); await environment._stabilize(); return environment.createComponentHarness(harnessType, fixture.nativeElement); } - documentRootLocatorFactory(): LocatorFactory { - let element = this.rawRootElement; - while (element.parentElement) { - element = element.parentElement; - } - return new TestbedHarnessEnvironment(element, this._stabilize); + protected getDocumentRoot(): Element { + return this._fixture.nativeElement; } protected createTestElement(element: Element): TestElement { - return new UnitTestElement(element, this._stabilize); + return new UnitTestElement(element, this._stabilize.bind(this)); } - protected createComponentHarness( - harnessType: ComponentHarnessConstructor, element: Element): T { - return new harnessType(new TestbedHarnessEnvironment(element, this._stabilize)); - } - - protected createHarnessLoader(element: Element): HarnessLoader { - return new TestbedHarnessEnvironment(element, this._stabilize); + protected createEnvironment(element: Element): HarnessEnvironment { + return new TestbedHarnessEnvironment(element, this._fixture); } protected async getRawElement(selector: string): Promise { @@ -79,4 +57,9 @@ export class TestbedHarnessEnvironment extends HarnessEnvironment { await this._stabilize(); return Array.from(this.rawRootElement.querySelectorAll(selector)); } + + private async _stabilize(): Promise { + this._fixture.detectChanges(); + await this._fixture.whenStable(); + } } diff --git a/src/cdk-experimental/testing/testbed/unit-test-element.ts b/src/cdk-experimental/testing/testbed/unit-test-element.ts index 78ccfcfe7045..b778ef307d0e 100644 --- a/src/cdk-experimental/testing/testbed/unit-test-element.ts +++ b/src/cdk-experimental/testing/testbed/unit-test-element.ts @@ -17,7 +17,7 @@ import {TestElement} from '../test-element'; function isTextInput(element: Element): element is HTMLInputElement | HTMLTextAreaElement { return element.nodeName.toLowerCase() === 'input' || - element.nodeName.toLowerCase() === 'textarea' ; + element.nodeName.toLowerCase() === 'textarea' ; } /** A `TestElement` implementation for unit tests. */ diff --git a/src/cdk-experimental/testing/tests/harnesses/main-component-harness.ts b/src/cdk-experimental/testing/tests/harnesses/main-component-harness.ts index f76f5851146b..fecd954fc8c9 100644 --- a/src/cdk-experimental/testing/tests/harnesses/main-component-harness.ts +++ b/src/cdk-experimental/testing/tests/harnesses/main-component-harness.ts @@ -17,30 +17,30 @@ export class WrongComponentHarness extends ComponentHarness { export class MainComponentHarness extends ComponentHarness { static readonly hostSelector = 'test-main'; - readonly title = this.locatorForRequired('h1'); - readonly button = this.locatorForRequired('button'); - readonly asyncCounter = this.locatorForRequired('#asyncCounter'); - readonly counter = this.locatorForRequired('#counter'); - readonly input = this.locatorForRequired('#input'); - readonly value = this.locatorForRequired('#value'); + 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.locatorForRequired('textarea'); + readonly memo = this.locatorFor('textarea'); // Allow null for element readonly nullItem = this.locatorForOptional('wrong locator'); // Allow null for component harness readonly nullComponentHarness = this.locatorForOptional(WrongComponentHarness); - readonly errorItem = this.locatorForRequired('wrong locator'); + readonly errorItem = this.locatorFor('wrong locator'); - readonly globalEl = this.documentRootLocatorFactory().locatorForRequired('.sibling'); - readonly errorGlobalEl = this.documentRootLocatorFactory().locatorForRequired('wrong locator'); + readonly globalEl = this.documentRootLocatorFactory().locatorFor('.sibling'); + readonly errorGlobalEl = this.documentRootLocatorFactory().locatorFor('wrong locator'); readonly nullGlobalEl = this.documentRootLocatorFactory().locatorForOptional('wrong locator'); readonly optionalDiv = this.locatorForOptional('div'); readonly optionalSubComponent = this.locatorForOptional(SubComponentHarness); - readonly errorSubComponent = this.locatorForRequired(WrongComponentHarness); + readonly errorSubComponent = this.locatorFor(WrongComponentHarness); - private _testTools = this.locatorForRequired(SubComponentHarness); + private _testTools = this.locatorFor(SubComponentHarness); async increaseCounter(times: number) { const button = await this.button(); diff --git a/src/cdk-experimental/testing/tests/harnesses/sub-component-harness.ts b/src/cdk-experimental/testing/tests/harnesses/sub-component-harness.ts index e05dc4842516..b34ab41f2f94 100644 --- a/src/cdk-experimental/testing/tests/harnesses/sub-component-harness.ts +++ b/src/cdk-experimental/testing/tests/harnesses/sub-component-harness.ts @@ -12,9 +12,9 @@ import {TestElement} from '../../test-element'; export class SubComponentHarness extends ComponentHarness { static readonly hostSelector = 'test-sub'; - readonly title = this.locatorForRequired('h2'); + readonly title = this.locatorFor('h2'); readonly getItems = this.locatorForAll('li'); - readonly globalElement = this.documentRootLocatorFactory().locatorForRequired('#username'); + readonly globalElement = this.documentRootLocatorFactory().locatorFor('#username'); async getItem(index: number): Promise { const items = await this.getItems(); diff --git a/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts b/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts index 3ba83ed705ff..ef240a8c6052 100644 --- a/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts +++ b/src/cdk-experimental/testing/tests/protractor.e2e.spec.ts @@ -13,7 +13,7 @@ describe('ProtractorHarnessEnvironment', () => { let loader: HarnessLoader; beforeEach(async () => { - loader = ProtractorHarnessEnvironment.create(); + loader = ProtractorHarnessEnvironment.loader(); }); it('should create HarnessLoader', async () => { @@ -21,13 +21,13 @@ describe('ProtractorHarnessEnvironment', () => { }); it('should find required HarnessLoader for child element', async () => { - const subcomponentsLoader = await loader.findRequired('.subcomponents'); + 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.findRequired('error'); + await loader.getChildLoader('error'); fail('Expected to throw'); } catch (e) { expect(e.message) @@ -35,28 +35,21 @@ describe('ProtractorHarnessEnvironment', () => { } }); - it('should find optional HarnessLoader for child element', async () => { - const subcomponentsLoader = await loader.findOptional('.subcomponents'); - const nullLoader = await loader.findOptional('wrong-selector'); - expect(subcomponentsLoader).not.toBeNull(); - expect(nullLoader).toBeNull(); - }); - it('should find all HarnessLoaders for child elements', async () => { - const loaders = await loader.findAll('.subcomponents,.counters'); + 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.requiredHarness(SubComponentHarness); + 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.findRequired('.counters'); + const countersLoader = await loader.getChildLoader('.counters'); try { - await countersLoader.requiredHarness(SubComponentHarness); + await countersLoader.getHarness(SubComponentHarness); fail('Expected to throw'); } catch (e) { expect(e.message) @@ -64,17 +57,8 @@ describe('ProtractorHarnessEnvironment', () => { } }); - it('should get first matching component for optional harness', async () => { - const countersLoader = await loader.findRequired('.counters'); - const harness1 = await loader.optionalHarness(SubComponentHarness); - const harness2 = await countersLoader.optionalHarness(SubComponentHarness); - expect(harness1).not.toBeNull(); - expect(await (await harness1!.title()).text()).toBe('List of test tools'); - expect(harness2).toBeNull(); - }); - it('should get all matching components for all harnesses', async () => { - const harnesses = await loader.allHarnesses(SubComponentHarness); + const harnesses = await loader.getAllHarnesses(SubComponentHarness); expect(harnesses.length).toBe(2); }); }); @@ -83,7 +67,7 @@ describe('ProtractorHarnessEnvironment', () => { let harness: MainComponentHarness; beforeEach(async () => { - harness = await ProtractorHarnessEnvironment.create().requiredHarness(MainComponentHarness); + harness = await ProtractorHarnessEnvironment.loader().getHarness(MainComponentHarness); }); it('should locate a required element based on CSS selector', async () => { @@ -174,7 +158,7 @@ describe('ProtractorHarnessEnvironment', () => { let harness: MainComponentHarness; beforeEach(async () => { - harness = await ProtractorHarnessEnvironment.create().requiredHarness(MainComponentHarness); + harness = await ProtractorHarnessEnvironment.loader().getHarness(MainComponentHarness); }); it('should be able to clear', async () => { diff --git a/src/cdk-experimental/testing/tests/testbed.spec.ts b/src/cdk-experimental/testing/tests/testbed.spec.ts index 0475e6dab26b..638b4df9cd7a 100644 --- a/src/cdk-experimental/testing/tests/testbed.spec.ts +++ b/src/cdk-experimental/testing/tests/testbed.spec.ts @@ -22,7 +22,7 @@ describe('TestbedHarnessEnvironment', () => { let loader: HarnessLoader; beforeEach(async () => { - loader = TestbedHarnessEnvironment.create(fixture); + loader = TestbedHarnessEnvironment.loader(fixture); }); it('should create HarnessLoader from fixture', async () => { @@ -31,18 +31,18 @@ describe('TestbedHarnessEnvironment', () => { it('should create ComponentHarness for fixture', async () => { const harness = - await TestbedHarnessEnvironment.harnessForFixtureRoot(fixture, MainComponentHarness); + await TestbedHarnessEnvironment.harnessForFixture(fixture, MainComponentHarness); expect(harness).not.toBeNull(); }); it('should find required HarnessLoader for child element', async () => { - const subcomponentsLoader = await loader.findRequired('.subcomponents'); + 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.findRequired('error'); + await loader.getChildLoader('error'); fail('Expected to throw'); } catch (e) { expect(e.message) @@ -50,28 +50,21 @@ describe('TestbedHarnessEnvironment', () => { } }); - it('should find optional HarnessLoader for child element', async () => { - const subcomponentsLoader = await loader.findOptional('.subcomponents'); - const nullLoader = await loader.findOptional('wrong-selector'); - expect(subcomponentsLoader).not.toBeNull(); - expect(nullLoader).toBeNull(); - }); - it('should find all HarnessLoaders for child elements', async () => { - const loaders = await loader.findAll('.subcomponents,.counters'); + 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.requiredHarness(SubComponentHarness); + 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.findRequired('.counters'); + const countersLoader = await loader.getChildLoader('.counters'); try { - await countersLoader.requiredHarness(SubComponentHarness); + await countersLoader.getHarness(SubComponentHarness); fail('Expected to throw'); } catch (e) { expect(e.message) @@ -79,17 +72,8 @@ describe('TestbedHarnessEnvironment', () => { } }); - it('should get first matching component for optional harness', async () => { - const countersLoader = await loader.findRequired('.counters'); - const harness1 = await loader.optionalHarness(SubComponentHarness); - const harness2 = await countersLoader.optionalHarness(SubComponentHarness); - expect(harness1).not.toBeNull(); - expect(await (await harness1!.title()).text()).toBe('List of test tools'); - expect(harness2).toBeNull(); - }); - it('should get all matching components for all harnesses', async () => { - const harnesses = await loader.allHarnesses(SubComponentHarness); + const harnesses = await loader.getAllHarnesses(SubComponentHarness); expect(harnesses.length).toBe(2); }); }); @@ -99,7 +83,7 @@ describe('TestbedHarnessEnvironment', () => { beforeEach(async () => { harness = - await TestbedHarnessEnvironment.harnessForFixtureRoot(fixture, MainComponentHarness); + await TestbedHarnessEnvironment.harnessForFixture(fixture, MainComponentHarness); }); it('should locate a required element based on CSS selector', async () => { @@ -194,7 +178,7 @@ describe('TestbedHarnessEnvironment', () => { beforeEach(async () => { harness = - await TestbedHarnessEnvironment.harnessForFixtureRoot(fixture, MainComponentHarness); + await TestbedHarnessEnvironment.harnessForFixture(fixture, MainComponentHarness); }); it('should be able to clear', async () => {