Skip to content

Commit a184166

Browse files
authored
Merge branch 'master' into feat/waitfor-perf
2 parents da1f856 + 2a366f8 commit a184166

13 files changed

+199
-52
lines changed

.all-contributorsrc

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,53 @@
917917
"contributions": [
918918
"code"
919919
]
920+
},
921+
{
922+
"login": "mzdunek93",
923+
"name": "Michał Zdunek",
924+
"avatar_url": "https://avatars0.githubusercontent.com/u/10826511?v=4",
925+
"profile": "https://github.com/mzdunek93",
926+
"contributions": [
927+
"code"
928+
]
929+
},
930+
{
931+
"login": "Lagily",
932+
"name": "Ali Nasserzadeh",
933+
"avatar_url": "https://avatars2.githubusercontent.com/u/42535205?v=4",
934+
"profile": "https://github.com/Lagily",
935+
"contributions": [
936+
"code"
937+
]
938+
},
939+
{
940+
"login": "darekkay",
941+
"name": "Darek Kay",
942+
"avatar_url": "https://avatars0.githubusercontent.com/u/3101914?v=4",
943+
"profile": "https://darekkay.com",
944+
"contributions": [
945+
"doc"
946+
]
947+
},
948+
{
949+
"login": "Lukas-Kullmann",
950+
"name": "Lukas",
951+
"avatar_url": "https://avatars0.githubusercontent.com/u/387547?v=4",
952+
"profile": "https://github.com/Lukas-Kullmann",
953+
"contributions": [
954+
"code",
955+
"test"
956+
]
957+
},
958+
{
959+
"login": "pelotom",
960+
"name": "Tom Crockett",
961+
"avatar_url": "https://avatars2.githubusercontent.com/u/128019?v=4",
962+
"profile": "https://twitter.com/pelotom",
963+
"contributions": [
964+
"code",
965+
"test"
966+
]
920967
}
921968
],
922969
"repoHost": "https://github.com"

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,11 @@ Thanks goes to these people ([emoji key][emojis]):
251251
<tr>
252252
<td align="center"><a href="https://github.com/nstepien"><img src="https://avatars0.githubusercontent.com/u/567105?v=4" width="100px;" alt=""/><br /><sub><b>Nicolas Stepien</b></sub></a><br /><a href="https://github.com/testing-library/dom-testing-library/commits?author=nstepien" title="Code">💻</a></td>
253253
<td align="center"><a href="https://knpw.rs"><img src="https://avatars0.githubusercontent.com/u/174864?v=4" width="100px;" alt=""/><br /><sub><b>Ken Powers</b></sub></a><br /><a href="https://github.com/testing-library/dom-testing-library/commits?author=knpwrs" title="Code">💻</a></td>
254+
<td align="center"><a href="https://github.com/mzdunek93"><img src="https://avatars0.githubusercontent.com/u/10826511?v=4" width="100px;" alt=""/><br /><sub><b>Michał Zdunek</b></sub></a><br /><a href="https://github.com/testing-library/dom-testing-library/commits?author=mzdunek93" title="Code">💻</a></td>
255+
<td align="center"><a href="https://github.com/Lagily"><img src="https://avatars2.githubusercontent.com/u/42535205?v=4" width="100px;" alt=""/><br /><sub><b>Ali Nasserzadeh</b></sub></a><br /><a href="https://github.com/testing-library/dom-testing-library/commits?author=Lagily" title="Code">💻</a></td>
256+
<td align="center"><a href="https://darekkay.com"><img src="https://avatars0.githubusercontent.com/u/3101914?v=4" width="100px;" alt=""/><br /><sub><b>Darek Kay</b></sub></a><br /><a href="https://github.com/testing-library/dom-testing-library/commits?author=darekkay" title="Documentation">📖</a></td>
257+
<td align="center"><a href="https://github.com/Lukas-Kullmann"><img src="https://avatars0.githubusercontent.com/u/387547?v=4" width="100px;" alt=""/><br /><sub><b>Lukas</b></sub></a><br /><a href="https://github.com/testing-library/dom-testing-library/commits?author=Lukas-Kullmann" title="Code">💻</a> <a href="https://github.com/testing-library/dom-testing-library/commits?author=Lukas-Kullmann" title="Tests">⚠️</a></td>
258+
<td align="center"><a href="https://twitter.com/pelotom"><img src="https://avatars2.githubusercontent.com/u/128019?v=4" width="100px;" alt=""/><br /><sub><b>Tom Crockett</b></sub></a><br /><a href="https://github.com/testing-library/dom-testing-library/commits?author=pelotom" title="Code">💻</a> <a href="https://github.com/testing-library/dom-testing-library/commits?author=pelotom" title="Tests">⚠️</a></td>
254259
</tr>
255260
</table>
256261

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
"dependencies": {
4141
"@babel/runtime": "^7.9.6",
4242
"aria-query": "^4.0.2",
43-
"dom-accessibility-api": "^0.4.3",
44-
"pretty-format": "^26.0.1"
43+
"dom-accessibility-api": "^0.4.4",
44+
"pretty-format": "^25.5.0"
4545
},
4646
"devDependencies": {
4747
"dtslint": "^3.4.2",
@@ -50,7 +50,7 @@
5050
"jest-serializer-ansi": "^1.0.3",
5151
"jest-watch-select-projects": "^2.0.0",
5252
"jsdom": "^16.2.2",
53-
"kcd-scripts": "^6.0.0"
53+
"kcd-scripts": "^6.2.0"
5454
},
5555
"eslintConfig": {
5656
"extends": "./node_modules/kcd-scripts/eslint.js",

src/__tests__/role.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,35 @@ test('has no useful error message in findBy', async () => {
349349
await expect(findByRole('option', {timeout: 1})).rejects.toThrow('Unable to find role="option"')
350350
})
351351

352+
test('explicit role is most specific', () => {
353+
const {getByRole} = renderIntoDocument(
354+
`<button role="tab" aria-label="my-tab" />`,
355+
)
356+
357+
expect(() => getByRole('button')).toThrowErrorMatchingInlineSnapshot(`
358+
"Unable to find an accessible element with the role "button"
359+
360+
Here are the accessible roles:
361+
362+
tab:
363+
364+
Name "my-tab":
365+
<button
366+
aria-label="my-tab"
367+
role="tab"
368+
/>
369+
370+
--------------------------------------------------
371+
372+
<body>
373+
<button
374+
aria-label="my-tab"
375+
role="tab"
376+
/>
377+
</body>"
378+
`)
379+
})
380+
352381
describe('configuration', () => {
353382
let originalConfig
354383
beforeEach(() => {

src/__tests__/wait-for-element.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ test('waits for element to appear in the document', async () => {
2222
expect(console.warn.mock.calls).toMatchInlineSnapshot(`
2323
Array [
2424
Array [
25-
"\`waitForElement\` has been deprecated. Use a \`find*\` query (preferred: https://testing-library.com/docs/dom-testing-library/api-queries#findby) or use \`waitFor\` instead (it's the same API, so you can find/replace): https://testing-library.com/docs/dom-testing-library/api-async#waitfor",
25+
"\`waitForElement\` has been deprecated. Use a \`find*\` query (preferred: https://testing-library.com/docs/dom-testing-library/api-queries#findby) or use \`waitFor\` instead: https://testing-library.com/docs/dom-testing-library/api-async#waitfor",
2626
],
2727
]
2828
`)

src/role-helpers.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,13 @@ function getRoles(container, {hidden = false} = {}) {
134134
return hidden === false ? isInaccessible(element) === false : true
135135
})
136136
.reduce((acc, node) => {
137-
const roles = getImplicitAriaRoles(node)
137+
let roles = []
138+
// TODO: This violates html-aria which does not allow any role on every element
139+
if (node.hasAttribute('role')) {
140+
roles = node.getAttribute('role').split(' ').slice(0, 1)
141+
} else {
142+
roles = getImplicitAriaRoles(node)
143+
}
138144

139145
return roles.reduce(
140146
(rolesAcc, role) =>

src/wait-for-element.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ async function waitForElement(callback, options) {
99
if (!hasWarned) {
1010
hasWarned = true
1111
console.warn(
12-
`\`waitForElement\` has been deprecated. Use a \`find*\` query (preferred: https://testing-library.com/docs/dom-testing-library/api-queries#findby) or use \`waitFor\` instead (it's the same API, so you can find/replace): https://testing-library.com/docs/dom-testing-library/api-async#waitfor`,
12+
`\`waitForElement\` has been deprecated. Use a \`find*\` query (preferred: https://testing-library.com/docs/dom-testing-library/api-queries#findby) or use \`waitFor\` instead: https://testing-library.com/docs/dom-testing-library/api-async#waitfor`,
1313
)
1414
}
1515
if (!callback) {

types/__tests__/type-tests.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ import {
22
fireEvent,
33
isInaccessible,
44
queries,
5+
buildQueries,
6+
queryAllByAttribute,
57
screen,
68
waitFor,
79
waitForElementToBeRemoved,
8-
} from '../index'
10+
MatcherOptions,
11+
} from '@testing-library/dom'
912

1013
const {
1114
getByText,
@@ -42,6 +45,42 @@ async function testQueries() {
4245
await screen.findAllByText('bar', undefined, {timeout: 10})
4346
}
4447

48+
async function testQueryHelpers() {
49+
const element = document.createElement('div')
50+
const includesAutomationId = (content: string, automationId: string) =>
51+
content.split(/\s+/).some(id => id === automationId)
52+
const queryAllByAutomationId = (
53+
container: HTMLElement,
54+
automationId: string | string[],
55+
options?: MatcherOptions,
56+
) =>
57+
queryAllByAttribute(
58+
'testId',
59+
container,
60+
content =>
61+
Array.isArray(automationId)
62+
? automationId.every(id => includesAutomationId(content, id))
63+
: includesAutomationId(content, automationId),
64+
options,
65+
)
66+
const [
67+
queryByAutomationId,
68+
getAllByAutomationId,
69+
getByAutomationId,
70+
findAllByAutomationId,
71+
findByAutomationId,
72+
] = buildQueries(
73+
queryAllByAutomationId,
74+
() => 'Multiple Error',
75+
() => 'Missing Error',
76+
)
77+
queryByAutomationId(element, 'id')
78+
getAllByAutomationId(element, 'id')
79+
getByAutomationId(element, ['id', 'automationId'])
80+
findAllByAutomationId(element, 'id', {}, {timeout: 1000})
81+
findByAutomationId(element, 'id', {}, {timeout: 1000})
82+
}
83+
4584
async function testByRole() {
4685
const element = document.createElement('button')
4786
element.setAttribute('aria-hidden', 'true')
@@ -116,7 +155,14 @@ async function testWaitFors() {
116155

117156
element.innerHTML = '<span>apple</span>'
118157

119-
await waitForElementToBeRemoved(() => getByText(element, 'apple'), {interval: 3000, container: element, timeout: 5000})
158+
await waitForElementToBeRemoved(() => getByText(element, 'apple'), {
159+
interval: 3000,
160+
container: element,
161+
timeout: 5000,
162+
})
120163
await waitForElementToBeRemoved(getByText(element, 'apple'))
121164
await waitForElementToBeRemoved(getAllByText(element, 'apple'))
165+
166+
// $ExpectError
167+
await waitFor(async () => {})
122168
}

types/queries.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export interface ByRoleOptions extends MatcherOptions {
6060
selected?: boolean;
6161
/**
6262
* Includes every role used in the `role` attribute
63-
* For example *ByRole('progressbar', {queryFallbacks: true})` will find <div role="meter progresbar">`.
63+
* For example *ByRole('progressbar', {queryFallbacks: true})` will find <div role="meter progressbar">`.
6464
*/
6565
queryFallbacks?: boolean;
6666
/**

types/query-helpers.d.ts

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,62 @@
1-
import { Matcher, MatcherOptions } from './matches';
1+
import { Matcher, MatcherOptions } from './matches'
2+
import { waitForOptions } from './wait-for'
23

34
export interface SelectorMatcherOptions extends MatcherOptions {
4-
selector?: string;
5+
selector?: string
56
}
67

78
export type QueryByAttribute = (
8-
attribute: string,
9-
container: HTMLElement,
10-
id: Matcher,
11-
options?: MatcherOptions,
12-
) => HTMLElement | null;
9+
attribute: string,
10+
container: HTMLElement,
11+
id: Matcher,
12+
options?: MatcherOptions,
13+
) => HTMLElement | null
1314

1415
export type AllByAttribute = (
15-
attribute: string,
16-
container: HTMLElement,
17-
id: Matcher,
18-
options?: MatcherOptions,
19-
) => HTMLElement[];
16+
attribute: string,
17+
container: HTMLElement,
18+
id: Matcher,
19+
options?: MatcherOptions,
20+
) => HTMLElement[]
2021

21-
export const queryByAttribute: QueryByAttribute;
22-
export const queryAllByAttribute: AllByAttribute;
23-
export function getElementError(message: string, container: HTMLElement): Error;
22+
export const queryByAttribute: QueryByAttribute
23+
export const queryAllByAttribute: AllByAttribute
24+
export function getElementError(message: string, container: HTMLElement): Error
2425

2526
/**
2627
* query methods have a common call signature. Only the return type differs.
2728
*/
28-
export type QueryMethod<Arguments extends any[], Return> = (container: HTMLElement, ...args: Arguments) => Return;
29-
export type QueryBy<Arguments extends any[]> = QueryMethod<Arguments, HTMLElement | null>;
30-
export type GetAllBy<Arguments extends any[]> = QueryMethod<Arguments, HTMLElement[]>;
31-
export type FindAllBy<Arguments extends any[]> = QueryMethod<Arguments, Promise<HTMLElement[]>>;
32-
export type GetBy<Arguments extends any[]> = QueryMethod<Arguments, HTMLElement>;
33-
export type FindBy<Arguments extends any[]> = QueryMethod<Arguments, Promise<HTMLElement>>;
29+
export type QueryMethod<Arguments extends any[], Return> = (
30+
container: HTMLElement,
31+
...args: Arguments
32+
) => Return
33+
export type QueryBy<Arguments extends any[]> = QueryMethod<
34+
Arguments,
35+
HTMLElement | null
36+
>
37+
export type GetAllBy<Arguments extends any[]> = QueryMethod<
38+
Arguments,
39+
HTMLElement[]
40+
>
41+
export type FindAllBy<Arguments extends any[]> = QueryMethod<
42+
[Arguments[0], Arguments[1], waitForOptions],
43+
Promise<HTMLElement[]>
44+
>
45+
export type GetBy<Arguments extends any[]> = QueryMethod<Arguments, HTMLElement>
46+
export type FindBy<Arguments extends any[]> = QueryMethod<
47+
[Arguments[0], Arguments[1], waitForOptions],
48+
Promise<HTMLElement>
49+
>
3450

3551
export type BuiltQueryMethods<Arguments extends any[]> = [
36-
QueryBy<Arguments>,
37-
GetAllBy<Arguments>,
38-
GetBy<Arguments>,
39-
FindAllBy<Arguments>,
40-
FindBy<Arguments>
41-
];
52+
QueryBy<Arguments>,
53+
GetAllBy<Arguments>,
54+
GetBy<Arguments>,
55+
FindAllBy<Arguments>,
56+
FindBy<Arguments>,
57+
]
4258
export function buildQueries<Arguments extends any[]>(
43-
queryByAll: GetAllBy<Arguments>,
44-
getMultipleError: (container: HTMLElement, ...args: Arguments) => string,
45-
getMissingError: (container: HTMLElement, ...args: Arguments) => string,
46-
): BuiltQueryMethods<Arguments>;
59+
queryByAll: GetAllBy<Arguments>,
60+
getMultipleError: (container: HTMLElement, ...args: Arguments) => string,
61+
getMissingError: (container: HTMLElement, ...args: Arguments) => string,
62+
): BuiltQueryMethods<Arguments>

types/screen.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export type Screen<Q extends Queries = typeof queries> = BoundFunctions<Q> & {
88
* of elements
99
*/
1010
debug: (
11-
element: Element | HTMLDocument | Array<Element | HTMLDocument>,
11+
element?: Element | HTMLDocument | Array<Element | HTMLDocument>,
1212
maxLength?: number,
1313
options?: OptionsReceived,
1414
) => void;

types/tsconfig.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@
99
"strictNullChecks": true,
1010
"strictFunctionTypes": true,
1111
"noEmit": true,
12-
13-
// If the library is an external module (uses `export`), this allows your test file to import "mylib" instead of "./index".
14-
// If the library is global (cannot be imported via `import` or `require`), leave this out.
15-
"baseUrl": "."
12+
"baseUrl": ".",
13+
"paths": {"@testing-library/dom": ["."]}
1614
}
1715
}

types/wait-for.d.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
export interface waitForOptions {
2-
container?: HTMLElement;
3-
timeout?: number;
4-
interval?: number;
5-
mutationObserverOptions?: MutationObserverInit;
2+
container?: HTMLElement
3+
timeout?: number
4+
interval?: number
5+
mutationObserverOptions?: MutationObserverInit
66
}
77

88
export function waitFor<T>(
9-
callback: () => T,
10-
options?: waitForOptions,
11-
): Promise<T>;
9+
callback: () => T extends Promise<any> ? never : T,
10+
options?: waitForOptions,
11+
): Promise<T>

0 commit comments

Comments
 (0)