Skip to content
This repository was archived by the owner on Jul 30, 2020. It is now read-only.

Commit 61895c9

Browse files
author
Brandon Carroll
committed
feat(queries): throw when multiple elements are returned from getBy
BREAKING CHANGE: `queryBy` and `getBy` now throw when multiple elements are returned and prompt users to use other queries if multiple results were intentional. This was done to remain in feature parity with dom-testing-library.
1 parent 77155c2 commit 61895c9

File tree

3 files changed

+270
-20
lines changed

3 files changed

+270
-20
lines changed

src/__tests__/get-by-errors.js

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import React from 'react';
2+
import { Text, TextInput, View } from 'react-native';
3+
import cases from 'jest-in-case';
4+
5+
import { render } from '../';
6+
7+
cases(
8+
'getBy* queries throw an error when there are multiple elements returned',
9+
({ name, query, tree }) => {
10+
const utils = render(tree);
11+
expect(() => utils[name](query)).toThrow(/multiple elements/i);
12+
},
13+
{
14+
getByA11yHint: {
15+
query: /his/,
16+
tree: (
17+
<View>
18+
<View accessibilityHint="his" />
19+
<View accessibilityHint="history" />
20+
</View>
21+
),
22+
},
23+
getByA11yLabel: {
24+
query: /his/,
25+
tree: (
26+
<View>
27+
<View accessibilityLabel="his" />
28+
<View accessibilityLabel="history" />
29+
</View>
30+
),
31+
},
32+
getByA11yRole: {
33+
query: 'button',
34+
tree: (
35+
<View>
36+
<View accessibilityRole="button" />
37+
<View accessibilityRole="button" />
38+
</View>
39+
),
40+
},
41+
getByA11yStates: {
42+
query: ['selected'],
43+
tree: (
44+
<View>
45+
<View accessibilityStates={['selected']} />
46+
<View accessibilityStates={['selected']} />
47+
</View>
48+
),
49+
},
50+
getByA11yTraits: {
51+
query: ['button'],
52+
tree: (
53+
<View>
54+
<View accessibilityTraits={['button']} />
55+
<View accessibilityTraits={['button']} />
56+
</View>
57+
),
58+
},
59+
getByPlaceholder: {
60+
query: /his/,
61+
tree: (
62+
<View>
63+
<TextInput placeholder="his" />
64+
<TextInput placeholder="history" />
65+
</View>
66+
),
67+
},
68+
getByTestId: {
69+
query: /his/,
70+
tree: (
71+
<View>
72+
<Text testID="his">text</Text>
73+
<Text testID="history">other</Text>
74+
</View>
75+
),
76+
},
77+
getByText: {
78+
query: /his/,
79+
tree: (
80+
<View>
81+
<Text>his</Text>
82+
<Text>history</Text>
83+
</View>
84+
),
85+
},
86+
getByValue: {
87+
query: /his/,
88+
tree: (
89+
<View>
90+
<TextInput value="his" />
91+
<TextInput value="history" />
92+
</View>
93+
),
94+
},
95+
},
96+
);
97+
98+
cases(
99+
'queryBy* queries throw an error when there are multiple elements returned',
100+
({ name, query, tree }) => {
101+
const utils = render(tree);
102+
expect(() => utils[name](query)).toThrow(/multiple elements/i);
103+
},
104+
{
105+
queryByA11yHint: {
106+
query: /his/,
107+
tree: (
108+
<View>
109+
<View accessibilityHint="his" />
110+
<View accessibilityHint="history" />
111+
</View>
112+
),
113+
},
114+
queryByA11yLabel: {
115+
query: /his/,
116+
tree: (
117+
<View>
118+
<View accessibilityLabel="his" />
119+
<View accessibilityLabel="history" />
120+
</View>
121+
),
122+
},
123+
queryByA11yRole: {
124+
query: 'button',
125+
tree: (
126+
<View>
127+
<View accessibilityRole="button" />
128+
<View accessibilityRole="button" />
129+
</View>
130+
),
131+
},
132+
queryByA11yStates: {
133+
query: ['selected'],
134+
tree: (
135+
<View>
136+
<View accessibilityStates={['selected']} />
137+
<View accessibilityStates={['selected']} />
138+
</View>
139+
),
140+
},
141+
queryByA11yTraits: {
142+
query: ['button'],
143+
tree: (
144+
<View>
145+
<View accessibilityTraits={['button']} />
146+
<View accessibilityTraits={['button']} />
147+
</View>
148+
),
149+
},
150+
queryByPlaceholder: {
151+
query: /his/,
152+
tree: (
153+
<View>
154+
<TextInput placeholder="his" />
155+
<TextInput placeholder="history" />
156+
</View>
157+
),
158+
},
159+
queryByTestId: {
160+
query: /his/,
161+
tree: (
162+
<View>
163+
<Text testID="his">text</Text>
164+
<Text testID="history">other</Text>
165+
</View>
166+
),
167+
},
168+
queryByText: {
169+
query: /his/,
170+
tree: (
171+
<View>
172+
<Text>his</Text>
173+
<Text>history</Text>
174+
</View>
175+
),
176+
},
177+
queryByValue: {
178+
query: /his/,
179+
tree: (
180+
<View>
181+
<TextInput value="his" />
182+
<TextInput value="history" />
183+
</View>
184+
),
185+
},
186+
}
187+
);

src/queries.js

Lines changed: 73 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { waitForElement } from './wait-for-element';
66
import { fuzzyMatches, makeNormalizer, matches } from './matches';
77
import {
88
getBaseElement,
9+
getGetByElementError,
910
getElementError,
1011
firstResultOrNull,
1112
queryAllByProp,
@@ -107,40 +108,94 @@ function getAllByValue(container, value, ...rest) {
107108
| Get by...
108109
|--------------------------------------------------------------------------
109110
*/
110-
function getByA11yHint(...args) {
111-
return firstResultOrNull(getAllByA11yHint, ...args);
111+
function getByA11yHint(container, hint, ...args) {
112+
const els = getAllByA11yHint(container, hint, ...args);
113+
if (els.length > 1) {
114+
throw getGetByElementError(
115+
`Found multiple elements with the accessibilityHint: ${hint}`,
116+
container,
117+
);
118+
}
119+
return els[0];
112120
}
113121

114-
function getByA11yLabel(...args) {
115-
return firstResultOrNull(getAllByA11yLabel, ...args);
122+
function getByA11yLabel(container, label, ...args) {
123+
const els = getAllByA11yLabel(container, label, ...args);
124+
if (els.length > 1) {
125+
throw getGetByElementError(
126+
`Found multiple elements with the accessibilityLabel: ${label}`,
127+
container,
128+
);
129+
}
130+
return els[0];
116131
}
117132

118-
function getByPlaceholder(...args) {
119-
return firstResultOrNull(getAllByPlaceholder, ...args);
133+
function getByA11yRole(container, role, ...args) {
134+
const els = getAllByA11yRole(container, role, ...args);
135+
if (els.length > 1) {
136+
throw getGetByElementError(
137+
`Found multiple elements with the accessibilityRole: "${role}"`,
138+
container,
139+
);
140+
}
141+
return els[0];
120142
}
121143

122-
function getByA11yRole(...args) {
123-
return firstResultOrNull(getAllByA11yRole, ...args);
144+
function getByA11yStates(container, states, ...args) {
145+
const els = getAllByA11yStates(container, states, ...args);
146+
if (els.length > 1) {
147+
throw getGetByElementError(
148+
`Found multiple elements with the accessibilityStates: ${JSON.stringify(states)}`,
149+
container,
150+
);
151+
}
152+
return els[0];
124153
}
125154

126-
function getByA11yStates(...args) {
127-
return firstResultOrNull(getAllByA11yStates, ...args);
155+
function getByA11yTraits(container, traits, ...args) {
156+
const els = getAllByA11yTraits(container, traits, ...args);
157+
if (els.length > 1) {
158+
throw getGetByElementError(
159+
`Found multiple elements with the accessibilityTraits: ${JSON.stringify(traits)}`,
160+
container,
161+
);
162+
}
163+
return els[0];
128164
}
129165

130-
function getByA11yTraits(...args) {
131-
return firstResultOrNull(getAllByA11yTraits, ...args);
166+
function getByPlaceholder(container, placeholder, ...args) {
167+
const els = getAllByPlaceholder(container, placeholder, ...args);
168+
if (els.length > 1) {
169+
throw getGetByElementError(
170+
`Found multiple elements with the placeholder: "${placeholder}"`,
171+
container,
172+
);
173+
}
174+
return els[0];
132175
}
133176

134-
function getByTestId(...args) {
135-
return firstResultOrNull(getAllByTestId, ...args);
177+
function getByTestId(container, id, ...args) {
178+
const els = getAllByTestId(container, id, ...args);
179+
if (els.length > 1) {
180+
throw getGetByElementError(`Found multiple elements with the testID: "${id}"`, container);
181+
}
182+
return els[0];
136183
}
137184

138-
function getByText(...args) {
139-
return firstResultOrNull(getAllByText, ...args);
185+
function getByText(container, text, ...args) {
186+
const els = getAllByText(container, text, ...args);
187+
if (els.length > 1) {
188+
throw getGetByElementError(`Found multiple elements with the text: ${text}`, container);
189+
}
190+
return els[0];
140191
}
141192

142-
function getByValue(...args) {
143-
return firstResultOrNull(getAllByValue, ...args);
193+
function getByValue(container, value, ...args) {
194+
const els = getAllByValue(container, value, ...args);
195+
if (els.length > 1) {
196+
throw getGetByElementError(`Found multiple elements with the value: ${value}`, container);
197+
}
198+
return els[0];
144199
}
145200

146201
/*

src/query-helpers.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ function filterNodeByType(node, type) {
1515
return node.type === type;
1616
}
1717

18+
function getGetByElementError(message, container) {
19+
return getElementError(
20+
`${message}\n\n(If this is intentional, then use the \`*AllBy*\` variant of the query (like \`getAllByText\` or \`findAllByText\`)).`,
21+
container,
22+
)
23+
}
24+
1825
function firstResultOrNull(queryFunction, ...args) {
1926
const result = queryFunction(...args);
2027
if (result.length === 0) return null;
@@ -90,10 +97,11 @@ function queryByProp(...args) {
9097

9198
export {
9299
defaultFilter,
100+
filterNodeByType,
101+
firstResultOrNull,
93102
getBaseElement,
94103
getElementError,
95-
firstResultOrNull,
96-
filterNodeByType,
104+
getGetByElementError,
97105
queryAllByProp,
98106
queryByProp,
99107
removeBadProperties,

0 commit comments

Comments
 (0)