Skip to content

Commit 678f4da

Browse files
authored
feat(TS): move types from DefinitelyTyped (#530)
1 parent b31c0b9 commit 678f4da

20 files changed

+581
-3
lines changed

package.json

+6-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"version": "0.0.0-semantically-released",
44
"description": "Simple and complete DOM testing utilities that encourage good testing practices.",
55
"main": "dist/index.js",
6+
"types": "types/index.d.ts",
67
"module": "dist/@testing-library/dom.esm.js",
78
"umd:main": "dist/@testing-library/dom.umd.js",
89
"source": "src/index.js",
@@ -29,19 +30,21 @@
2930
"test": "kcd-scripts test",
3031
"test:debug": "node --inspect-brk ./node_modules/.bin/jest --watch --runInBand",
3132
"test:update": "npm test -- --updateSnapshot --coverage",
32-
"validate": "kcd-scripts validate"
33+
"validate": "kcd-scripts validate",
34+
"typecheck": "dtslint ./types/"
3335
},
3436
"files": [
35-
"dist"
37+
"dist",
38+
"types"
3639
],
3740
"dependencies": {
3841
"@babel/runtime": "^7.9.6",
39-
"@types/testing-library__dom": "^7.0.2",
4042
"aria-query": "^4.0.2",
4143
"dom-accessibility-api": "^0.4.3",
4244
"pretty-format": "^26.0.1"
4345
},
4446
"devDependencies": {
47+
"dtslint": "^3.4.2",
4548
"@testing-library/jest-dom": "^5.5.0",
4649
"jest-in-case": "^1.0.2",
4750
"jest-serializer-ansi": "^1.0.3",

types/__tests__/type-tests.ts

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import {
2+
fireEvent,
3+
isInaccessible,
4+
queries,
5+
screen,
6+
waitFor,
7+
waitForElementToBeRemoved,
8+
} from '../index'
9+
10+
const {
11+
getByText,
12+
queryByText,
13+
findByText,
14+
getAllByText,
15+
queryAllByText,
16+
findAllByText,
17+
queryAllByRole,
18+
queryByRole,
19+
findByRole,
20+
} = queries
21+
22+
async function testQueries() {
23+
// element queries
24+
const element = document.createElement('div')
25+
getByText(element, 'foo')
26+
queryByText(element, 'foo')
27+
await findByText(element, 'foo')
28+
await findByText(element, 'foo', undefined, {timeout: 10})
29+
getAllByText(element, 'bar')
30+
queryAllByText(element, 'bar')
31+
await findAllByText(element, 'bar')
32+
await findAllByText(element, 'bar', undefined, {timeout: 10})
33+
34+
// screen queries
35+
screen.getByText('foo')
36+
screen.queryByText('foo')
37+
await screen.findByText('foo')
38+
await screen.findByText('foo', undefined, {timeout: 10})
39+
screen.debug(screen.getAllByText('bar'))
40+
screen.queryAllByText('bar')
41+
await screen.findAllByText('bar')
42+
await screen.findAllByText('bar', undefined, {timeout: 10})
43+
}
44+
45+
async function testByRole() {
46+
const element = document.createElement('button')
47+
element.setAttribute('aria-hidden', 'true')
48+
49+
console.assert(queryByRole(element, 'button') === null)
50+
console.assert(queryByRole(element, 'button', {hidden: true}) !== null)
51+
52+
console.assert(screen.queryByRole('button') === null)
53+
console.assert(screen.queryByRole('button', {hidden: true}) !== null)
54+
55+
console.assert(
56+
(await findByRole(element, 'button', undefined, {timeout: 10})) === null,
57+
)
58+
console.assert(
59+
(await findByRole(element, 'button', {hidden: true}, {timeout: 10})) !==
60+
null,
61+
)
62+
63+
console.assert(
64+
queryAllByRole(document.body, 'progressbar', {queryFallbacks: true})
65+
.length === 1,
66+
)
67+
68+
// `name` option
69+
console.assert(queryByRole(element, 'button', {name: 'Logout'}) === null)
70+
console.assert(queryByRole(element, 'button', {name: /^Log/}) === null)
71+
console.assert(
72+
queryByRole(element, 'button', {
73+
name: (name, element) =>
74+
name === 'Login' && element.hasAttribute('disabled'),
75+
}) === null,
76+
)
77+
}
78+
79+
function testA11yHelper() {
80+
const element = document.createElement('svg')
81+
console.assert(!isInaccessible(element))
82+
}
83+
84+
function eventTest() {
85+
fireEvent.popState(window, {
86+
location: 'http://www.example.com/?page=1',
87+
state: {page: 1},
88+
})
89+
90+
// HTMLElement
91+
const element = document.createElement('div')
92+
fireEvent.click(getByText(element, 'foo'))
93+
94+
// ChildNode
95+
const child = document.createElement('div')
96+
element.appendChild(child)
97+
if (!element.firstChild) {
98+
// Narrow Type
99+
throw new Error(`Can't find firstChild`)
100+
}
101+
fireEvent.click(element.firstChild)
102+
}
103+
104+
async function testWaitFors() {
105+
const element = document.createElement('div')
106+
107+
await waitFor(() => getByText(element, 'apple'))
108+
await waitFor(() => getAllByText(element, 'apple'))
109+
const result: HTMLSpanElement = await waitFor(() =>
110+
getByText(element, 'apple'),
111+
)
112+
if (!result) {
113+
// Use value
114+
throw new Error(`Can't find result`)
115+
}
116+
117+
element.innerHTML = '<span>apple</span>'
118+
119+
await waitForElementToBeRemoved(() => getByText(element, 'apple'), {interval: 3000, container: element, timeout: 5000})
120+
await waitForElementToBeRemoved(getByText(element, 'apple'))
121+
await waitForElementToBeRemoved(getAllByText(element, 'apple'))
122+
}

types/config.d.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export interface Config {
2+
testIdAttribute: string;
3+
asyncWrapper(cb: (...args: any[]) => any): Promise<any>;
4+
asyncUtilTimeout: number;
5+
defaultHidden: boolean;
6+
}
7+
8+
export interface ConfigFn {
9+
(existingConfig: Config): Partial<Config>;
10+
}
11+
12+
export function configure(configDelta: Partial<Config> | ConfigFn): void;

types/events.d.ts

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
export type EventType =
2+
| 'copy'
3+
| 'cut'
4+
| 'paste'
5+
| 'compositionEnd'
6+
| 'compositionStart'
7+
| 'compositionUpdate'
8+
| 'keyDown'
9+
| 'keyPress'
10+
| 'keyUp'
11+
| 'focus'
12+
| 'blur'
13+
| 'focusIn'
14+
| 'focusOut'
15+
| 'change'
16+
| 'input'
17+
| 'invalid'
18+
| 'submit'
19+
| 'reset'
20+
| 'click'
21+
| 'contextMenu'
22+
| 'dblClick'
23+
| 'drag'
24+
| 'dragEnd'
25+
| 'dragEnter'
26+
| 'dragExit'
27+
| 'dragLeave'
28+
| 'dragOver'
29+
| 'dragStart'
30+
| 'drop'
31+
| 'mouseDown'
32+
| 'mouseEnter'
33+
| 'mouseLeave'
34+
| 'mouseMove'
35+
| 'mouseOut'
36+
| 'mouseOver'
37+
| 'mouseUp'
38+
| 'popState'
39+
| 'select'
40+
| 'touchCancel'
41+
| 'touchEnd'
42+
| 'touchMove'
43+
| 'touchStart'
44+
| 'scroll'
45+
| 'wheel'
46+
| 'abort'
47+
| 'canPlay'
48+
| 'canPlayThrough'
49+
| 'durationChange'
50+
| 'emptied'
51+
| 'encrypted'
52+
| 'ended'
53+
| 'loadedData'
54+
| 'loadedMetadata'
55+
| 'loadStart'
56+
| 'pause'
57+
| 'play'
58+
| 'playing'
59+
| 'progress'
60+
| 'rateChange'
61+
| 'seeked'
62+
| 'seeking'
63+
| 'stalled'
64+
| 'suspend'
65+
| 'timeUpdate'
66+
| 'volumeChange'
67+
| 'waiting'
68+
| 'load'
69+
| 'error'
70+
| 'animationStart'
71+
| 'animationEnd'
72+
| 'animationIteration'
73+
| 'transitionEnd'
74+
| 'doubleClick'
75+
| 'pointerOver'
76+
| 'pointerEnter'
77+
| 'pointerDown'
78+
| 'pointerMove'
79+
| 'pointerUp'
80+
| 'pointerCancel'
81+
| 'pointerOut'
82+
| 'pointerLeave'
83+
| 'gotPointerCapture'
84+
| 'lostPointerCapture';
85+
86+
export type FireFunction = (element: Document | Element | Window | Node, event: Event) => boolean;
87+
export type FireObject = {
88+
[K in EventType]: (element: Document | Element | Window | Node, options?: {}) => boolean;
89+
};
90+
export type CreateObject = {
91+
[K in EventType]: (element: Document | Element | Window | Node, options?: {}) => Event;
92+
};
93+
94+
export const createEvent: CreateObject;
95+
export const fireEvent: FireFunction & FireObject;

types/get-node-text.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export function getNodeText(node: HTMLElement): string;

types/get-queries-for-element.d.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Matcher } from './matches';
2+
import * as queries from './queries';
3+
4+
export type BoundFunction<T> = T extends (
5+
attribute: string,
6+
element: HTMLElement,
7+
text: infer P,
8+
options: infer Q,
9+
) => infer R
10+
? (text: P, options?: Q) => R
11+
: T extends (a1: any, text: infer P, options: infer Q, waitForElementOptions: infer W) => infer R
12+
? (text: P, options?: Q, waitForElementOptions?: W) => R
13+
: T extends (a1: any, text: infer P, options: infer Q) => infer R
14+
? (text: P, options?: Q) => R
15+
: never;
16+
export type BoundFunctions<T> = { [P in keyof T]: BoundFunction<T[P]> };
17+
18+
export type Query = (
19+
container: HTMLElement,
20+
...args: any[]
21+
) => Error | Promise<HTMLElement[]> | Promise<HTMLElement> | HTMLElement[] | HTMLElement | null;
22+
23+
export interface Queries {
24+
[T: string]: Query;
25+
}
26+
27+
export function getQueriesForElement<T extends Queries = typeof queries>(
28+
element: HTMLElement,
29+
queriesToBind?: T,
30+
): BoundFunctions<T>;

types/index.d.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// TypeScript Version: 3.8
2+
3+
import { getQueriesForElement } from './get-queries-for-element';
4+
import * as queries from './queries';
5+
import * as queryHelpers from './query-helpers';
6+
7+
declare const within: typeof getQueriesForElement;
8+
export { queries, queryHelpers, within };
9+
10+
export * from './queries';
11+
export * from './query-helpers';
12+
export * from './screen';
13+
export * from './wait';
14+
export * from './wait-for';
15+
export * from './wait-for-dom-change';
16+
export * from './wait-for-element';
17+
export * from './wait-for-element-to-be-removed';
18+
export * from './matches';
19+
export * from './get-node-text';
20+
export * from './events';
21+
export * from './get-queries-for-element';
22+
export * from './pretty-dom';
23+
export * from './role-helpers';
24+
export * from './config';

types/matches.d.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export type MatcherFunction = (content: string, element: HTMLElement) => boolean;
2+
export type Matcher = string | RegExp | MatcherFunction;
3+
4+
export type NormalizerFn = (text: string) => string;
5+
6+
export interface MatcherOptions {
7+
exact?: boolean;
8+
/** Use normalizer with getDefaultNormalizer instead */
9+
trim?: boolean;
10+
/** Use normalizer with getDefaultNormalizer instead */
11+
collapseWhitespace?: boolean;
12+
normalizer?: NormalizerFn;
13+
}
14+
15+
export type Match = (
16+
textToMatch: string,
17+
node: HTMLElement | null,
18+
matcher: Matcher,
19+
options?: MatcherOptions,
20+
) => boolean;
21+
22+
export interface DefaultNormalizerOptions {
23+
trim?: boolean;
24+
collapseWhitespace?: boolean;
25+
}
26+
27+
export function getDefaultNormalizer(options?: DefaultNormalizerOptions): NormalizerFn;
28+
29+
// N.B. Don't expose fuzzyMatches + matches here: they're not public API

types/pretty-dom.d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { OptionsReceived } from 'pretty-format';
2+
3+
export function prettyDOM(dom?: Element | HTMLDocument, maxLength?: number, options?: OptionsReceived): string | false;
4+
export function logDOM(dom?: Element | HTMLDocument, maxLength?: number, options?: OptionsReceived): void;

0 commit comments

Comments
 (0)