Skip to content
This repository has been archived by the owner on Mar 6, 2024. It is now read-only.

RFC: Improve the type system around options in widgets #334

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cypress/integration/find-cypress-widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
DataExporterWidgetObject,
FindableWidget,
} from '@vcd/ui-components';
import { FindCypressWidgetOptions } from '@vcd/ui-components';
import { FindElementOptions } from '@vcd/ui-components';
import Cypress from 'cypress';

type Chainable = Cypress.Chainable;
Expand All @@ -27,11 +27,11 @@ type Chainable = Cypress.Chainable;
*/
export function findCypressWidget<W extends BaseWidgetObject<Chainable>>(
widgetConstructor: FindableWidget<Chainable, W>,
findOptions?: FindCypressWidgetOptions
findOptions?: FindElementOptions<Chainable>
): W {
return new CypressWidgetObjectFinder<Chainable>().find(widgetConstructor, findOptions) as W;
}

export function findDataExporter(options?: FindCypressWidgetOptions): DataExporterWidgetObject<Chainable> {
export function findDataExporter(options?: FindElementOptions<Chainable>): DataExporterWidgetObject<Chainable> {
return findCypressWidget<DataExporterWidgetObject<Chainable>>(DataExporterWidgetObject, options);
}
7 changes: 5 additions & 2 deletions projects/components/CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [2.0.0-dev.8]

### Added
- Find widget by data ui attribute.

### Change
- Widget find/action options are now type safe

## [2.0.0-dev.8]

### Added
- `clear` is now a method in a widget object.
- Added functionality to disable an option in `FormSelectComponent`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,6 @@ import { By } from '@angular/platform-browser';
import { BaseWidgetObject, FindableWidget, FindElementOptions } from '../widget-object';
import { AngularWidgetObjectElement, TestElement } from './angular-widget-object-element';

/**
* Adds Angular specific options for finding widgets
*/
export interface FindAngularWidgetOptions extends FindElementOptions {
ancestor?: DebugElement;
}

/**
* Knows how to find and instantiate Angular Widgets objects.
*/
Expand Down Expand Up @@ -49,7 +42,7 @@ export class AngularWidgetObjectFinder<H = unknown> {
*/
public find<W extends BaseWidgetObject<TestElement>>(
widgetConstructor: FindableWidget<TestElement, W>,
findOptions: FindAngularWidgetOptions = {}
findOptions: FindElementOptions<TestElement> = {}
): W {
const { ancestor } = findOptions;

Expand All @@ -60,7 +53,7 @@ export class AngularWidgetObjectFinder<H = unknown> {
if (findOptions?.cssSelector) {
query = query + findOptions.cssSelector;
}
const parentQuery: FindAngularWidgetOptions = {
const parentQuery: FindElementOptions<TestElement> = {
cssSelector: query,
dataUiSelector: findOptions?.dataUiSelector,
text: findOptions?.text,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@ import { DebugElement, Injector, Type } from '@angular/core';
import { ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { SelectorUtil } from '../selector-util';
import { BaseWidgetObject, FindableWidget, FindElementOptions, WidgetObjectElement } from '../widget-object';
import { AngularWidgetObjectFinder, FindAngularWidgetOptions } from './angular-widget-finder';
import {
AngularWidgetActionOptions,
BaseWidgetObject,
FindableWidget,
FindElementOptions,
WidgetObjectElement,
} from '../widget-object';
import { AngularWidgetObjectFinder } from './angular-widget-finder';

/**
* Angular implementation of the Widget Object's internal HTML Element wrapper
Expand All @@ -21,8 +27,8 @@ export class AngularWidgetObjectElement implements WidgetObjectElement<TestEleme
/**
* @inheritdoc
*/
get(selector: string | FindElementOptions): AngularWidgetObjectElement {
const cssSelector = SelectorUtil.extractSelector(selector);
get(selector: string | FindElementOptions<TestElement>): AngularWidgetObjectElement {
const cssSelector = SelectorUtil.extractSelector<TestElement>(selector);
const elements = this.testElement.elements;
let matches = [].concat(...elements.map((element) => element.queryAll(By.css(cssSelector))));
if (typeof selector !== 'string') {
Expand All @@ -34,23 +40,20 @@ export class AngularWidgetObjectElement implements WidgetObjectElement<TestEleme
}
return new AngularWidgetObjectElement(new TestElement(matches, this.testElement.fixture));
}
/**
* @inheritdoc
*/
getByText(cssSelector: string, value: string): AngularWidgetObjectElement {
const elements = this.testElement.elements;
let nextElements = [].concat(...elements.map((element) => element.queryAll(By.css(cssSelector))));
nextElements = nextElements.filter((el) => el.nativeElement.textContent.includes(value));
return new AngularWidgetObjectElement(new TestElement(nextElements, this.testElement.fixture));
}

/**
* @inheritdoc
*/
parents(cssSelector: string): AngularWidgetObjectElement {
parents(cssSelector: string | FindElementOptions<TestElement>): AngularWidgetObjectElement {
let selector: string;
if (typeof cssSelector === 'string') {
selector = cssSelector;
} else {
selector = SelectorUtil.extractSelector<TestElement>(cssSelector);
}
return new AngularWidgetObjectElement(
new TestElement(
this.testElement.elements.map((el) => this.findParent(cssSelector, el.parent)),
this.testElement.elements.map((el) => this.findParent(selector, el.parent)),
this.testElement.fixture
)
);
Expand All @@ -59,33 +62,33 @@ export class AngularWidgetObjectElement implements WidgetObjectElement<TestEleme
/**
* @inheritdoc
*/
click(): void {
click(options?: AngularWidgetActionOptions): void {
this.testElement.click();
}

/**
* @inheritdoc
*/
clear(): void {
clear(options?: AngularWidgetActionOptions): void {
this.testElement.clear();
}

/**
* @inheritdoc
*/
check(options?: unknown): void {}
check(options?: AngularWidgetActionOptions): void {}

/**
* @inheritdoc
*/
uncheck(options?: unknown): void {}
uncheck(options?: AngularWidgetActionOptions): void {}

/**
* @inheritdoc
*/
select(value: string, options?: unknown): void {}
select(value: string, options?: AngularWidgetActionOptions): void {}

type(value: string): void {
type(value: string, options?: AngularWidgetActionOptions): void {
const inputEl = this.testElement.elements[0].nativeElement as HTMLInputElement;
inputEl.value = String(value);
inputEl.dispatchEvent(new Event('change'));
Expand Down Expand Up @@ -118,7 +121,7 @@ export class AngularWidgetObjectElement implements WidgetObjectElement<TestEleme
*/
findWidget<W extends BaseWidgetObject<TestElement>>(
widgetCtor: FindableWidget<TestElement, W>,
findOptions: FindAngularWidgetOptions
findOptions: FindElementOptions<TestElement>
): W {
return new AngularWidgetObjectFinder(this.testElement.fixture).find(widgetCtor, {
...findOptions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,12 @@
*/

import { IdGenerator } from '../../../id-generator/id-generator';
import { BaseWidgetObject, FindableWidget, FindElementOptions } from '../widget-object';
import { BaseWidgetObject, CypressWidgetActionOptions, FindableWidget, FindElementOptions } from '../widget-object';
import { CypressWidgetObjectElement } from './cypress-widget-object-element';

declare const cy;
const idGenerator = new IdGenerator('cy-id');

export interface FindCypressWidgetOptions extends FindElementOptions {
ancestor?: string;
options?: { timeout: number };
}

/**
* Knows how to find and construct Cypress widget objects within the DOM.
*
Expand All @@ -34,19 +29,20 @@ export class CypressWidgetObjectFinder<T> {
*/
public find<W extends BaseWidgetObject<T>>(
widgetConstructor: FindableWidget<T, W>,
findOptions?: FindCypressWidgetOptions
findOptions?: FindElementOptions<T>
): W {
const id = idGenerator.generate();

const options: CypressWidgetActionOptions = findOptions?.options;
const ancestor = findOptions?.ancestor
? cy.get(findOptions?.ancestor, { timeout: findOptions?.options?.timeout })
: cy.get('body', { timeout: findOptions?.options?.timeout });
? cy.get(findOptions?.ancestor, { timeout: options?.timeout })
: cy.get('body', { timeout: options?.timeout });
const parentWidget = new CypressWidgetObjectElement(ancestor, false, undefined);
let query = widgetConstructor.tagName;
if (findOptions?.cssSelector) {
query = query + findOptions.cssSelector;
}
const parentQuery: FindCypressWidgetOptions = {
const parentQuery: FindElementOptions<T> = {
cssSelector: query,
dataUiSelector: findOptions?.dataUiSelector,
text: findOptions?.text,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ describe('CypressWidgetObjectElement', () => {
it('clears the given input', () => {
const clearSpy = spyOn(cy, 'clear').and.callThrough();
const widget = new CypressWidgetObjectElement(cy, true, '1');
widget.clear({ a: 'test' });
expect(clearSpy).toHaveBeenCalledWith({ a: 'test' });
widget.clear({ timeout: 1 });
expect(clearSpy).toHaveBeenCalledWith({ timeout: 1 });
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import { SelectorUtil } from '../selector-util';
import {
BaseWidgetObject,
CypressWidgetActionOptions,
ElementActions,
FindableWidget,
FindElementOptions,
WidgetActionOptions,
WidgetObjectElement,
} from '../widget-object';
import { CypressWidgetObjectFinder, FindCypressWidgetOptions } from './cypress-widget-finder';
import { CypressWidgetObjectFinder } from './cypress-widget-finder';

declare const cy;

Expand All @@ -24,15 +26,15 @@ declare const cy;
* [this PR](https://github.com/vmware/vmware-cloud-director-ui-components/pull/248)
* we could not load the Cypress types in our library.
*/
export class CypressWidgetObjectElement<T extends ElementActions> implements WidgetObjectElement<T> {
export class CypressWidgetObjectElement<T extends ElementActions<T>> implements WidgetObjectElement<T> {
constructor(private chainable: T, private isRoot: boolean, private alias: string) {}

/**
* @inheritdoc
*/
get(selector: string | FindElementOptions): CypressWidgetObjectElement<T> {
get(selector: string | FindElementOptions<T>): CypressWidgetObjectElement<T> {
const root = this.getBase();
const cssSelector = SelectorUtil.extractSelector(selector);
const cssSelector = SelectorUtil.extractSelector<T>(selector);
let chainable: any;
if (typeof selector === 'string') {
chainable = root.find(selector);
Expand All @@ -50,13 +52,13 @@ export class CypressWidgetObjectElement<T extends ElementActions> implements Wid
/**
* @inheritdoc
*/
parents(selector: string | FindElementOptions): CypressWidgetObjectElement<T> {
parents(selector: string | FindElementOptions<T>): CypressWidgetObjectElement<T> {
const root = this.getBase();
if (typeof selector === 'string') {
return new CypressWidgetObjectElement(root.parents(selector), false, this.alias);
}
return new CypressWidgetObjectElement(
root.parents(SelectorUtil.extractSelector(selector), selector.options),
root.parents(SelectorUtil.extractSelector<T>(selector), selector.options),
false,
this.alias
);
Expand All @@ -72,50 +74,53 @@ export class CypressWidgetObjectElement<T extends ElementActions> implements Wid
/**
* @inheritdoc
*/
click(options?: unknown): void {
click(options?: WidgetActionOptions<T>): void {
this.chainable.click(options);
}

/**
* @inheritdoc
*/
type(value: string, options: unknown): void {
type(value: string, options: WidgetActionOptions<T>): void {
this.chainable.type(value, options);
}

/**
* @inheritdoc
*/
select(text: string, options: unknown): void {
select(text: string, options: WidgetActionOptions<T>): void {
this.chainable.select(text, options);
}

/**
* @inheritdoc
*/
check(options?: unknown): void {
check(options?: WidgetActionOptions<T>): void {
this.chainable.check(options);
}

/**
* @inheritdoc
*/
uncheck(options?: unknown): void {
uncheck(options?: WidgetActionOptions<T>): void {
this.chainable.uncheck(options);
}

/**
* @inheritdoc
*/
clear(options?: unknown): void {
clear(options?: WidgetActionOptions<T>): void {
this.chainable.clear(options);
}

/**
* @inheritdoc
*/
findWidget<W extends BaseWidgetObject<T>>(widget: FindableWidget<T, W>, findOptions: FindCypressWidgetOptions): W {
return new CypressWidgetObjectFinder<T>().find(widget, { ancestor: '@' + this.alias, ...findOptions });
findWidget<W extends BaseWidgetObject<T>>(widget: FindableWidget<T, W>, findOptions: FindElementOptions<T>): W {
return new CypressWidgetObjectFinder<T>().find(widget, {
ancestor: '@' + this.alias,
...findOptions,
} as FindElementOptions<T>);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions projects/components/src/utils/test/widget-object/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

export { BaseWidgetObject, FindableWidget } from './widget-object';
export { CypressWidgetObjectFinder, FindCypressWidgetOptions } from './cypress/cypress-widget-finder';
export { CypressWidgetObjectFinder } from './cypress/cypress-widget-finder';
export { AngularWidgetObjectFinder } from './angular/angular-widget-finder';
export { TestElement } from './angular/angular-widget-object-element';
export { FindElementOptions } from './widget-object';
export { FindElementOptions, WidgetActionOptions } from './widget-object';
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class SelectorUtil {
/**
* Extracts the selector from the parameter passed
*/
static extractSelector(selector: string | FindElementOptions): string {
static extractSelector<T>(selector: string | FindElementOptions<T>): string {
if (typeof selector === 'string') {
return selector;
} else {
Expand Down
Loading