Skip to content

Commit

Permalink
feat: Add Typescript support
Browse files Browse the repository at this point in the history
  • Loading branch information
olivierwilkinson committed Jan 19, 2021
1 parent c1ef5c1 commit 4193c7c
Show file tree
Hide file tree
Showing 12 changed files with 173 additions and 45 deletions.
9 changes: 7 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
{
"extends": "./node_modules/kcd-scripts/eslint.js",
"parser": "@typescript-eslint/parser",
"extends": [
"plugin:@typescript-eslint/recommended",
"./node_modules/kcd-scripts/eslint.js"
],
"rules": {
"babel/new-cap": "off",
"func-names": "off",
"babel/no-unused-expressions": "off",
"prefer-arrow-callback": "off",
"testing-library/no-await-sync-query": "off",
"testing-library/no-dom-import": "off",
"testing-library/prefer-screen-queries": "off"
"testing-library/prefer-screen-queries": "off",
"@typescript-eslint/no-var-requires": "off"
},
"overrides": [
{
Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,37 @@ it('lets you configure queries', async () => {
})
```

### Typescript

All the above methods are fully typed. To use the Browser and Element commands
added by `setupBrowser` the global `WebdriverIO` namespace will need to be
modified. Add the following to a typescript module:

```
import {WebdriverIOQueries} from 'webdriverio-testing-library';
declare global {
namespace WebdriverIO {
interface Browser extends WebdriverIOQueries {}
interface Element extends WebdriverIOQueries {}
}
}
```

If you are using the `@wdio/sync` framework you will need to use the
`WebdriverIOQueriesSync` type to extend the interfaces:

```
import {WebdriverIOQueriesSync} from 'webdriverio-testing-library';
declare global {
namespace WebdriverIO {
interface Browser extends WebdriverIOQueriesSync {}
interface Element extends WebdriverIOQueriesSync {}
}
}
```

## Other Solutions

I'm not aware of any, if you are please [make a pull request][prs] and add it
Expand Down
13 changes: 8 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@
"version": "1.1.0",
"description": "",
"main": "dist/index.js",
"typings": "typings",
"types": "dist/index.d.ts",
"scripts": {
"add-contributor": "kcd-scripts contributors add",
"build": "kcd-scripts build",
"build": "tsc -p tsconfig.build.json",
"lint": "kcd-scripts lint",
"test:unit": "kcd-scripts test --no-watch --config=jest.config.js",
"validate": "kcd-scripts validate build,lint,test",
"test": "wdio wdio.conf.js",
"semantic-release": "semantic-release"
},
"files": [
"dist",
"typings"
"dist"
],
"keywords": [],
"author": "",
Expand All @@ -28,15 +27,19 @@
"webdriverio": "*"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.14.0",
"@typescript-eslint/parser": "^4.14.0",
"@wdio/cli": "^6.11.3",
"@wdio/local-runner": "^6.12.0",
"@wdio/mocha-framework": "^6.11.0",
"@wdio/spec-reporter": "^6.11.0",
"@wdio/sync": "^6.11.0",
"chromedriver": "^87.0.5",
"eslint": "^6.5.1",
"eslint": "^7.6.0",
"kcd-scripts": "^5.0.0",
"semantic-release": "^17.0.2",
"ts-node": "^9.1.1",
"typescript": "^4.1.3",
"wdio-chromedriver-service": "^6.0.4",
"webdriverio": "^6.12.0"
},
Expand Down
67 changes: 42 additions & 25 deletions src/index.js → src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
const path = require('path')
const fs = require('fs')
const {queries: baseQueries} = require('@testing-library/dom')
/* eslint-disable @typescript-eslint/no-implied-eval babel/no-invalid-this */

import path from 'path'
import fs from 'fs'
import {queries as baseQueries} from '@testing-library/dom'
import {Element, BrowserObject, MultiRemoteBrowserObject} from 'webdriverio'

import {Config, QueryName, WebdriverIOQueries} from './types'

declare global {
interface Window {
TestingLibraryDom: typeof baseQueries & {
configure: typeof configure
}
}
}

const DOM_TESTING_LIBRARY_UMD_PATH = path.join(
require.resolve('@testing-library/dom'),
Expand All @@ -11,9 +24,9 @@ const DOM_TESTING_LIBRARY_UMD = fs
.readFileSync(DOM_TESTING_LIBRARY_UMD_PATH)
.toString()

let _config
let _config: Partial<Config>

async function injectDOMTestingLibrary(container) {
async function injectDOMTestingLibrary(container: Element) {
await container.execute(DOM_TESTING_LIBRARY_UMD)

if (_config) {
Expand All @@ -23,7 +36,7 @@ async function injectDOMTestingLibrary(container) {
}
}

function serializeArgs(args) {
function serializeArgs(args: any[]) {
return args.map((arg) => {
if (arg instanceof RegExp) {
return {RegExp: arg.toString()}
Expand All @@ -35,10 +48,12 @@ function serializeArgs(args) {
})
}

function executeQuery([query, container, ...args], done) {
const deserializedArgs = args.map((arg) => {
function executeQuery(
[query, container, ...args]: [QueryName, HTMLElement, ...any[]],
done: (result: any) => void,
) {
const [matcher, options, waitForOptions] = args.map((arg) => {
if (arg && arg.RegExp) {
// eslint-disable-next-line
return eval(arg.RegExp)
}
if (arg && arg.Undefined) {
Expand All @@ -48,7 +63,12 @@ function executeQuery([query, container, ...args], done) {
})

Promise.resolve(
window.TestingLibraryDom[query](container, ...deserializedArgs),
window.TestingLibraryDom[query](
container,
matcher,
options,
waitForOptions,
),
)
.then(done)
.catch((e) => done(e.message))
Expand All @@ -62,18 +82,18 @@ Element. There are valid WebElement JSONs that exclude the key but can be turned
into Elements, such as { ELEMENT: elementId }; this can happen in setups that
aren't generated by @wdio/cli.
*/
function createElement(container, elementValue) {
function createElement(container: Element, elementValue: any) {
return container.$({
'element-6066-11e4-a52e-4f735466cecf': '',
...elementValue,
})
}

function createQuery(element, queryName) {
return async (...args) => {
function createQuery(element: Element, queryName: string) {
return async (...args: any[]) => {
await injectDOMTestingLibrary(element)

const result = await element.executeAsync(executeQuery, [
const result = await element.executeAsync<any[], any[]>(executeQuery, [
queryName,
element,
...serializeArgs(args),
Expand All @@ -95,17 +115,17 @@ function createQuery(element, queryName) {
}
}

function within(element) {
function within(element: Element) {
return Object.keys(baseQueries).reduce(
(queries, queryName) => ({
...queries,
[queryName]: createQuery(element, queryName),
}),
{},
)
) as WebdriverIOQueries
}

async function setupBrowser(browser) {
async function setupBrowser(browser: BrowserObject | MultiRemoteBrowserObject) {
const body = await browser.$('body')
const queries = within(body)

Expand All @@ -117,8 +137,8 @@ async function setupBrowser(browser) {
browser.addCommand(
queryName,
function (...args) {
// eslint-disable-next-line babel/no-invalid-this
return within(this)[queryName](...args)
// @ts-expect-error
return within(this as Element)[queryName](...args)
},
true,
)
Expand All @@ -127,12 +147,9 @@ async function setupBrowser(browser) {
return queries
}

function configure(config) {
function configure(config: Partial<Config>) {
_config = config
}

module.exports = {
within,
setupBrowser,
configure,
}
export * from './types'
export {within, setupBrowser, configure}
43 changes: 43 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
Config as BaseConfig,
BoundFunction as BoundFunctionBase,
queries,
} from '@testing-library/dom'
import {Element} from 'webdriverio'

export type Config = Pick<BaseConfig, 'testIdAttribute'>

export type WebdriverIOQueryReturnType<T> = T extends Promise<HTMLElement>
? Element
: T extends HTMLElement
? Element
: T extends Promise<HTMLElement[]>
? Element[]
: T extends HTMLElement[]
? Element[]
: T extends null
? null
: never

export type WebdriverIOBoundFunction<T> = (
...params: Parameters<BoundFunctionBase<T>>
) => Promise<WebdriverIOQueryReturnType<ReturnType<BoundFunctionBase<T>>>>

export type WebdriverIOBoundFunctionSync<T> = (
...params: Parameters<BoundFunctionBase<T>>
) => WebdriverIOQueryReturnType<ReturnType<BoundFunctionBase<T>>>

export type WebdriverIOBoundFunctions<T> = {
[P in keyof T]: WebdriverIOBoundFunction<T[P]>
}

export type WebdriverIOBoundFunctionsSync<T> = {
[P in keyof T]: WebdriverIOBoundFunctionSync<T[P]>
}

export type WebdriverIOQueries = WebdriverIOBoundFunctions<typeof queries>
export type WebdriverIOQueriesSync = WebdriverIOBoundFunctionsSync<
typeof queries
>

export type QueryName = keyof typeof queries
4 changes: 2 additions & 2 deletions test/configure.e2e.js → test/configure.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const {setupBrowser, configure} = require('../src')
import {setupBrowser, configure} from '../src';

describe('configure', () => {
beforeEach(() => {
configure({testIdAttribute: 'data-automation-id'})
})
afterEach(() => {
configure(null)
configure({})
})

it('supports alternative testIdAttribute', async () => {
Expand Down
4 changes: 2 additions & 2 deletions test/queries.e2e.js → test/queries.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const {setupBrowser} = require('../src')
import {setupBrowser} from '../src';

describe('queries', () => {
it('queryBy resolves with matching element', async () => {
const {queryByText} = await setupBrowser(browser)

const button = await queryByText('Unique Button Text')
expect(await button.getText()).toEqual('Unique Button Text')
expect(await button?.getText()).toEqual('Unique Button Text')
})

it('queryBy resolves with null when there are no matching elements', async () => {
Expand Down
18 changes: 13 additions & 5 deletions test/setupBrowser.e2e.js → test/setupBrowser.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
const {queries: baseQueries} = require('@testing-library/dom')
import {queries as baseQueries} from '@testing-library/dom'

const {setupBrowser} = require('../src')
import {setupBrowser} from '../src'
import { WebdriverIOQueries } from '../src/types'

declare global {
namespace WebdriverIO {
interface Browser extends WebdriverIOQueries {}
interface Element extends WebdriverIOQueries {}
}
}

describe('setupBrowser', () => {
it('resolves with all queries', async () => {
Expand Down Expand Up @@ -36,15 +44,15 @@ describe('setupBrowser', () => {
})

it('adds queries as browser commands', async () => {
await setupBrowser(browser);
await setupBrowser(browser)

expect(await browser.getByText('Page Heading')).toBeDefined()
})

it('adds queries as element commands scoped to element', async () => {
await setupBrowser(browser);
await setupBrowser(browser)

const nested = await browser.$('*[data-testid="nested"]');
const nested = await browser.$('*[data-testid="nested"]')
const button = await nested.getByText('Button Text')
await button.click()

Expand Down
2 changes: 1 addition & 1 deletion test/within.e2e.js → test/within.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const {within, setupBrowser} = require('../src')
import {within, setupBrowser} from '../src';

describe('within', () => {
it('scopes queries to element', async () => {
Expand Down
7 changes: 7 additions & 0 deletions tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": true
},
"exclude": ["test", "dist"]
}
12 changes: 12 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"baseUrl": ".",
"target": "es2019",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"declaration": true,
"outDir": "./dist",
"skipLibCheck": true
}
}
Loading

0 comments on commit 4193c7c

Please sign in to comment.