Skip to content
This repository has been archived by the owner on Jan 3, 2019. It is now read-only.

Commit

Permalink
feat(all): improve api usability
Browse files Browse the repository at this point in the history
BREAKING CHANGE: New component factory interface
  • Loading branch information
clebert committed Apr 4, 2018
1 parent 400057c commit 361c7f0
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 157 deletions.
20 changes: 5 additions & 15 deletions @pageobject/base/src/Component.test.ts
@@ -1,4 +1,4 @@
import {Adapter, Component, Effect, Locator, Operator} from '.';
import {Adapter, Component, Effect, Operator} from '.';

class TestAdapter implements Adapter<HTMLElement> {
public async findElements(
Expand All @@ -9,7 +9,7 @@ class TestAdapter implements Adapter<HTMLElement> {
}
}

class TestComponent extends Component<HTMLElement> {
abstract class TestComponent extends Component<HTMLElement> {
public readonly type = this.constructor.name;

public getID(): Effect<string> {
Expand Down Expand Up @@ -53,21 +53,11 @@ class Descriptor<TComponent extends TestComponent> {
}

class A extends TestComponent {
public static create(
adapter: Adapter<HTMLElement>,
locator?: Locator<HTMLElement, A>
): A {
return new A(adapter, locator, A, '.a');
}
public readonly selector: string = '.a';
}

class B extends TestComponent {
public static create(
adapter: Adapter<HTMLElement>,
locator?: Locator<HTMLElement, B>
): B {
return new B(adapter, locator, B, '.b');
}
public readonly selector: string = '.b';
}

function describeExistingVariants<TComponent extends TestComponent>(
Expand Down Expand Up @@ -293,7 +283,7 @@ describe('Component', () => {
<div class="a" id="A2">3</div>
`;

const a = A.create(new TestAdapter());
const a = new A(new TestAdapter());

describeTests(a, undefined, 1);
describeTests(a, undefined, 2);
Expand Down
66 changes: 33 additions & 33 deletions @pageobject/base/src/Component.ts
Expand Up @@ -29,38 +29,34 @@ export interface ComponentFactory<
TElement,
TComponent extends Component<TElement>
> {
create(
new (
adapter: Adapter<TElement>,
locator?: Locator<TElement, TComponent>
): TComponent;
}

export class Component<TElement> implements Describable {
export abstract class Component<TElement> implements Describable {
public abstract readonly selector: string;

public readonly description: string;

private readonly _adapter: Adapter<TElement>;
private readonly _locator: Locator<TElement, this>;
private readonly _ownFactory: ComponentFactory<TElement, this>;
private readonly _selector: string;

protected constructor(
public constructor(
adapter: Adapter<TElement>,
locator: Locator<TElement, any> = {}, // tslint:disable-line no-any
ownFactory: ComponentFactory<TElement, any>, // tslint:disable-line no-any
selector: string
locator: Locator<TElement, any> = {} // tslint:disable-line no-any
) {
this._adapter = adapter;
this._locator = locator;
this._ownFactory = ownFactory;
this._selector = selector;

this.description = this._describe();
}

public select<TDescendant extends Component<TElement>>(
descendantFactory: ComponentFactory<TElement, TDescendant>
Descendant: ComponentFactory<TElement, TDescendant>
): TDescendant {
return descendantFactory.create(this._adapter, {parent: this});
return new Descendant(this._adapter, {parent: this});
}

public nth(position: number): this {
Expand All @@ -72,29 +68,31 @@ export class Component<TElement> implements Describable {
throw new Error('Position is already set');
}

return this._ownFactory.create(this._adapter, {...this._locator, position});
const Self = this.constructor as ComponentFactory<TElement, this>;

return new Self(this._adapter, {...this._locator, position});
}

public where<TValue>(
accessor: Accessor<TElement, this, TValue>,
operator: Operator<TValue>
): this {
const {filters} = this._locator;
const Self = this.constructor as ComponentFactory<TElement, this>;

return this._ownFactory.create(this._adapter, {
return new Self(this._adapter, {
...this._locator,
filters: [...(filters || []), {accessor, operator}]
filters: [...(this._locator.filters || []), {accessor, operator}]
});
}

public getElementCount(): Effect<number> {
const trigger = async () => (await this.findElements()).length;
const trigger = async () => (await this._findElements()).length;

return {context: this, description: 'getElementCount()', trigger};
}

protected async findElement(): Promise<TElement> {
const elements = await this.findElements();
const elements = await this._findElements();

if (elements.length === 0) {
throw new Error('Element not found');
Expand All @@ -107,19 +105,6 @@ export class Component<TElement> implements Describable {
return elements[0];
}

protected async findElements(): Promise<TElement[]> {
const elements = await this._filterElements();
const {position} = this._locator;

if (position) {
const index = position - 1;

return index < elements.length ? [elements[index]] : [];
}

return elements;
}

private _describe(): string {
const {filters, parent, position} = this._locator;

Expand All @@ -144,7 +129,7 @@ export class Component<TElement> implements Describable {
const {filters, parent} = this._locator;

const elements = await this._adapter.findElements(
this._selector,
this.selector,
parent ? await parent.findElement() : undefined
);

Expand All @@ -154,7 +139,9 @@ export class Component<TElement> implements Describable {

const results = await Promise.all(
elements.map(async element => {
const instance = this._ownFactory.create({
const Self = this.constructor as ComponentFactory<TElement, this>;

const instance = new Self({
findElements: async () => [element]
});

Expand All @@ -168,4 +155,17 @@ export class Component<TElement> implements Describable {

return elements.filter((element, index) => results[index]);
}

private async _findElements(): Promise<TElement[]> {
const elements = await this._filterElements();
const {position} = this._locator;

if (position) {
const index = position - 1;

return index < elements.length ? [elements[index]] : [];
}

return elements;
}
}
11 changes: 3 additions & 8 deletions @pageobject/web/src/WebComponent.test.ts
@@ -1,13 +1,8 @@
import {Adapter, Effect, Locator} from '@pageobject/base';
import {Adapter, Effect} from '@pageobject/base';
import {WebComponent, WebElement} from '.';

class TestComponent extends WebComponent {
public static create(
adapter: Adapter<WebElement>,
locator?: Locator<WebElement, TestComponent>
): TestComponent {
return new TestComponent(adapter, locator, TestComponent, ':root');
}
public readonly selector: string = ':root';
}

class TestAdapter implements Adapter<WebElement> {
Expand Down Expand Up @@ -39,7 +34,7 @@ describe('WebComponent', () => {

beforeEach(() => {
adapter = new TestAdapter();
component = TestComponent.create(adapter);
component = new TestComponent(adapter);
element = new TestElement();
htmlElement = new TestHTMLElement();

Expand Down
2 changes: 1 addition & 1 deletion @pageobject/web/src/WebComponent.ts
Expand Up @@ -12,7 +12,7 @@ export interface WebElement {
): Promise<TResult>;
}

export class WebComponent extends Component<WebElement> {
export abstract class WebComponent extends Component<WebElement> {
public click(): Effect<void> {
const trigger = async () => (await this.findElement()).click();

Expand Down
2 changes: 1 addition & 1 deletion docs/api/base/assets/js/search.js

Large diffs are not rendered by default.

0 comments on commit 361c7f0

Please sign in to comment.