Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Start adding docs via typedoc #659

Merged
merged 14 commits into from
Dec 4, 2020
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
yarn-error.log
/packages/*/dist
/packages/*/docs
3 changes: 3 additions & 0 deletions packages/bigtest/API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# BigTest

This is cool stuff {@link createInteractor}, yeah!
Comment on lines +1 to +3
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is just a placeholder, we'll want to expand this to link to the most relevant parts of the documentation.

1 change: 1 addition & 0 deletions packages/bigtest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@frontside/typescript": "^1.1.0",
"@types/node": "^13.13.4",
"ts-node": "*",
"typedoc": "^0.20.0-beta.8",
"typescript": "3.9.7"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/bigtest/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from '@bigtest/interactor';
export * from '@bigtest/suite';
export { test, TestBuilder } from '@bigtest/suite';
7 changes: 7 additions & 0 deletions packages/bigtest/typedoc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"out": "docs",
"name": "BigTest",
"entryPoints": "src/index.ts",
"readme": "API.md",
"includeVersion": true
}
9 changes: 9 additions & 0 deletions packages/interactor/API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Interactors

## Built in interactors

We provide a set of interactors built in.

## Custom interactors

You can create your own interactors with {@link createInteractor}.
2 changes: 2 additions & 0 deletions packages/interactor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
"express": "^4.17.1",
"jsdom": "^16.2.2",
"mocha": "^6.2.2",
"typedoc": "^0.20.0-beta.8",
"typescript": "3.9.7",
"ts-node": "*",
"wait-on": "^5.2.0"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/interactor/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Interaction } from './interaction';
import { Page } from './page';

/**
* @hidden
*/
export const App = {
visit(path = '/'): Interaction<void> {
console.warn('App.visit is deprecated, please use Page.visit instead');
Expand Down
25 changes: 22 additions & 3 deletions packages/interactor/src/create-interactor.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
import { bigtestGlobals } from '@bigtest/globals';
import { InteractorSpecification, FilterParams, Filters, Actions, InteractorInstance, LocatorFn } from './specification';
import { InteractorSpecification, InteractorBuilder, InteractorConstructor, FilterParams, Filters, Actions, InteractorInstance, LocatorFn } from './specification';
import { Locator } from './locator';
import { Filter } from './filter';
import { Interactor } from './interactor';
import { interaction } from './interaction';

const defaultLocator: LocatorFn<Element> = (element) => element.textContent || "";

export function createInteractor<E extends Element>(interactorName: string) {
return function<F extends Filters<E> = {}, A extends Actions<E> = {}>(specification: InteractorSpecification<E, F, A>) {

/**
* Create a custom interactor. Due to TypeScript inference issues, this creates an
* {@link InteractorBuilder}, which you will need to create the actual
* interactor. See {@link InteractorSpecification} for detailed breakdown of
* available options for the builder.
*
* ### Creating a simple interactor
*
* ``` typescript
* let Paragraph = createInteractor('paragraph')({ selector: 'p' });
* ```
*
* Note the double function call!
*
* @param interactorName The human readable name of the interactor, used mainly for debugging purposes and error messages
* @typeParam E The type of DOM Element that this interactor operates on. By specifying the element type, actions and filters defined for the interactor can be type checked against the actual element type.
* @returns You will need to call the returned builder to create an interactor.
*/
export function createInteractor<E extends Element>(interactorName: string): InteractorBuilder<E> {
return function<F extends Filters<E> = {}, A extends Actions<E> = {}>(specification: InteractorSpecification<E, F, A>): InteractorConstructor<E, F, A> {
let InteractorClass = class extends Interactor<E, F, A> {};

for(let [actionName, action] of Object.entries(specification.actions || {})) {
Expand Down
35 changes: 34 additions & 1 deletion packages/interactor/src/definitions/button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ function isButtonElement(element: HTMLInputElement | HTMLButtonElement): element
return element.tagName === 'BUTTON';
}

export const Button = createInteractor<HTMLInputElement | HTMLButtonElement>('button')({
const ButtonInteractor = createInteractor<HTMLInputElement | HTMLButtonElement>('button')({
selector: 'button,input[type=button],input[type=submit],input[type=reset],input[type=image]',
locator(element) {
if(isButtonElement(element)) {
Expand All @@ -32,3 +32,36 @@ export const Button = createInteractor<HTMLInputElement | HTMLButtonElement>('bu
blur: perform((element) => { element.blur(); }),
},
});

/**
* Call this {@link InteractorConstructor} to initialize a button {@link Interactor}.
* The button interactor can be used to interact with buttons on the page and
* to assert on their state.
*
* The button is located by the visible text on the button.
*
* ### Example
*
* ``` typescript
* await Button('Submit').click();
* await Button('Submit').is({ disabled: true });
* await Button({ id: 'submit-button', disabled: true }).exists();
* ```
*
* ### Filters
*
* - `title`: *string* – Filter by title
* - `id`: *string* – Filter by id
* - `visible`: *boolean* – Filter by visibility. Defaults to `true`. See {@link isVisible}.
* - `disabled`: *boolean* – Filter by whether the button is disabled. Defaults to `false`.
* - `focused`: *boolean* – Filter by whether the button is focused. See {@link focused}.
*
* ### Actions
*
* - `click()`: *{@link Interaction}* – Click on the button
* - `focus()`: *{@link Interaction}* – Move focus to the button
* - `blur()`: *{@link Interaction}* – Move focus away from the button
*
* @category Interactor
*/
export const Button = ButtonInteractor;
38 changes: 37 additions & 1 deletion packages/interactor/src/definitions/check-box.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createInteractor, perform, focused } from '../index';
import { isVisible } from 'element-is-visible';

export const CheckBox = createInteractor<HTMLInputElement>('check box')({
const CheckBoxInteractor = createInteractor<HTMLInputElement>('check box')({
selector: 'input[type=checkbox]',
locator: (element) => element.labels ? (Array.from(element.labels)[0]?.textContent || '') : '',
filters: {
Expand All @@ -26,3 +26,39 @@ export const CheckBox = createInteractor<HTMLInputElement>('check box')({
toggle: perform((element) => { element.click(); }),
},
});

/**
* Call this {@link InteractorConstructor} to initialize a checkbox {@link Interactor}.
* The checkbox interactor can be used to interact with checkboxes on the page and
* to assert on their state.
*
* The checkbox is located by the text of its label.
*
* ### Example
*
* ``` typescript
* await CheckBox('Submit').click();
* await CheckBox('Submit').is({ disabled: true });
* await CheckBox({ id: 'submit-button', disabled: true }).exists();
* ```
*
* ### Filters
*
* - `title`: *string* – Filter by title
* - `id`: *string* – Filter by id
* - `visible`: *boolean* – Filter by visibility. Defaults to `true`. See {@link isVisible}.
* - `valid`: *boolean* – Filter by whether the checkbox is valid.
* - `checked`: *boolean* – Filter by whether the checkbox is checked.
* - `disabled`: *boolean* – Filter by whether the checkbox is disabled. Defaults to `false`.
* - `focused`: *boolean* – Filter by whether the checkbox is focused. See {@link focused}.
*
* ### Actions
*
* - `click()`: *{@link Interaction}* – Click on the checkbox
* - `check()`: *{@link Interaction}* – Check the checkbox if it is not checked
* - `uncheck()`: *{@link Interaction}* – Uncheck the checkbox if it is checked
* - `toggle()`: *{@link Interaction}* – Toggle the checkbox
*
* @category Interactor
*/
export const CheckBox = CheckBoxInteractor;
22 changes: 21 additions & 1 deletion packages/interactor/src/definitions/heading.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
import { createInteractor } from '../create-interactor';
import { isVisible } from 'element-is-visible';

export const Heading = createInteractor<HTMLHeadingElement>('heading')({
const HeadingInteractor = createInteractor<HTMLHeadingElement>('heading')({
selector: 'h1,h2,h3,h4,h5,h6',
filters: {
level: (element) => parseInt(element.tagName[1]),
visible: { apply: isVisible, default: true },
}
});

/**
* Call this {@link InteractorConstructor} to initialize a heading {@link Interactor}.
* The heading interactor can be used to assert on the state of headings on the page,
* represented by the `h1` through `h6` tags.
*
* ### Example
*
* ``` typescript
* await Heading('Welcome!').exists();
* ```
*
* ### Filters
*
* - `level`: *number* – The level of the heading, for example, the level of `h3` is `3`.
* - `visible`: *boolean* – Filter by visibility. Defaults to `true`. See {@link isVisible}.
*
* @category Interactor
*/
export const Heading = HeadingInteractor;
33 changes: 32 additions & 1 deletion packages/interactor/src/definitions/link.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createInteractor, perform, focused } from '../index';
import { isVisible } from 'element-is-visible';

export const Link = createInteractor<HTMLLinkElement>('link')({
const LinkInteractor = createInteractor<HTMLLinkElement>('link')({
selector: 'a[href]',
filters: {
title: (element) => element.title,
Expand All @@ -14,3 +14,34 @@ export const Link = createInteractor<HTMLLinkElement>('link')({
click: perform((element) => { element.click(); })
},
});

/**
* Call this {@link InteractorConstructor} to initialize a link {@link Interactor}.
* The link interactor can be used to interact with links on the page and
* to assert on their state.
*
* The link is located by its text.
*
* ### Example
*
* ``` typescript
* await Link('Home').click();
* await Link('Home').has({ href: '/' });
* await Link({ id: 'home-link', href: '/' }).exists();
* ```
*
* ### Filters
*
* - `title`: *string* – Filter by title
* - `id`: *string* – Filter by id
* - `href`: *string* – The value of the href attribute that the link points to
* - `visible`: *boolean* – Filter by visibility. Defaults to `true`. See {@link isVisible}.
* - `focused`: *boolean* – Filter by whether the link is focused. See {@link focused}.
*
* ### Actions
*
* - `click()`: *{@link Interaction}* – Click on the link
*
* @category Interactor
*/
export const Link = LinkInteractor;
68 changes: 54 additions & 14 deletions packages/interactor/src/definitions/multi-select.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { createInteractor, perform } from '../index';
import { createInteractor, perform, focused } from '../index';
import { isVisible } from 'element-is-visible';
import { dispatchChange, dispatchInput } from '../dispatch';
import { getSelect } from '../get-select';

const SelectOption = createInteractor<HTMLOptionElement>('option')({
const MultiSelectOption = createInteractor<HTMLOptionElement>('option')({
selector: 'option',
locator: (element) => element.label,
filters: {
Expand Down Expand Up @@ -43,35 +43,75 @@ const SelectOption = createInteractor<HTMLOptionElement>('option')({
},
});

export const MultiSelect = createInteractor<HTMLSelectElement>('select box')({
const MultiSelectInteractor = createInteractor<HTMLSelectElement>('select box')({
selector: 'select[multiple]',
locator: (element) => element.labels ? (Array.from(element.labels)[0]?.textContent || '') : '',
filters: {
title: (element) => element.title,
id: (element) => element.id,
valid: (element) => element.validity.valid,
values: (element) => Array.from(element.selectedOptions).map((o) => o.label),
visible: {
apply: (element) => isVisible(element) || (element.labels && Array.from(element.labels).some(isVisible)),
default: true
},
visible: { apply: isVisible, default: true },
disabled: {
apply: (element) => element.disabled,
default: false
}
},
focused
},
actions: {
click: perform((element) => { element.click(); }),
focus: perform((element) => { element.focus(); }),
blur: perform((element) => { element.blur(); }),
choose: async (interactor, value: string) => {
await interactor.find(SelectOption(value)).choose();
choose: async (interactor, text: string) => {
await interactor.find(MultiSelectOption(text)).choose();
},
select: async (interactor, value: string) => {
await interactor.find(SelectOption(value)).select();
select: async (interactor, text: string) => {
await interactor.find(MultiSelectOption(text)).select();
},
deselect: async (interactor, value: string) => {
await interactor.find(SelectOption(value)).deselect();
deselect: async (interactor, text: string) => {
await interactor.find(MultiSelectOption(text)).deselect();
},
},
});

/**
* Call this {@link InteractorConstructor} to initialize an {@link Interactor}
* for select boxes with multiple select. The multi select interactor can be
* used to interact with select boxes with the `multiple` attribute and to
* assert on their state.
*
* See {@link Select} for an interactor for single select boxes.
*
* The multi select is located by the text of its label.
*
* ### Example
*
* ``` typescript
* await MultiSelect('Language').select('English');
* await MultiSelect('Language').select('German');
* await MultiSelect('Language').deselect('Swedish');
* await MultiSelect('Language').has({ values: ['English', 'German'] });
* ```
*
* ### Filters
*
* - `title`: *string* – Filter by title
* - `id`: *string* – Filter by id
* - `valid`: *boolean* – Filter by whether the checkbox is valid.
* - `values`: *string[]* – Filter by the text of the selected options.
* - `visible`: *boolean* – Filter by visibility. Defaults to `true`. See {@link isVisible}.
* - `disabled`: *boolean* – Filter by whether the checkbox is disabled. Defaults to `false`.
* - `focused`: *boolean* – Filter by whether the checkbox is focused. See {@link focused}.
*
* ### Actions
*
* - `click()`: *{@link Interaction}* – Click on the multi select
* - `focus()`: *{@link Interaction}* – Move focus to the multi select
* - `blur()`: *{@link Interaction}* – Move focus away from the multi select
* - `choose(text: string)`: *{@link Interaction}* – Choose the option with the given text from the multi select. Will deselect all other selected options.
* - `select(text: string)`: *{@link Interaction}* – Add the option with the given text to the selection.
* - `deselect(text: string)`: *{@link Interaction}* – Remove the option with the given text from the selection.
*
* @category Interactor
*/
export const MultiSelect = MultiSelectInteractor;
Loading