Skip to content

Commit 34fc03a

Browse files
MattAgnthymikee
andauthored
Finish refactoring queries using new query builder makeQueries (#654)
* refactor: extract method filterNodeByType * feat: use makeQueries for byText * feat: use makeQueries for byPlaceholderText * feat: use makeQueries for byDisplayValue * refactor: renamed method in queryAllByTestId * refactor: rename method makeQuery to makeA11yQuery the renaming is done to avoid any confusion with makeQueries which should be the new way to add new queries * test: add test for errors for byTestId * sort exports Co-authored-by: Michał Pierzchała <[email protected]>
1 parent 6e38890 commit 34fc03a

12 files changed

+255
-347
lines changed

src/__tests__/byTestId.test.js

+7
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,13 @@ test('getByTestId, queryByTestId', () => {
129129

130130
expect(getByTestId('bananaFresh')).toBe(component);
131131
expect(queryByTestId('InExistent')).toBeNull();
132+
133+
expect(() => getByTestId('duplicateText')).toThrow(
134+
'Found multiple elements with testID: duplicateText'
135+
);
136+
expect(() => queryByTestId('duplicateText')).toThrow(
137+
'Found multiple elements with testID: duplicateText'
138+
);
132139
});
133140

134141
test('getAllByTestId, queryAllByTestId', () => {

src/__tests__/render.test.js

+17-9
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,16 @@ test('getByText, queryByText', () => {
108108

109109
expect(sameButton.props.children).toBe('not fresh');
110110
expect(() => getByText('InExistent')).toThrow(
111-
'No instances found with text "InExistent"'
111+
'Unable to find an element with text: InExistent'
112112
);
113113

114114
const zeroText = getByText('0');
115115

116116
expect(queryByText(/change/i)).toBe(button);
117117
expect(queryByText('InExistent')).toBeNull();
118-
expect(() => queryByText(/fresh/)).toThrow('Expected 1 but found 3');
118+
expect(() => queryByText(/fresh/)).toThrow(
119+
'Found multiple elements with text: /fresh/'
120+
);
119121
expect(queryByText('0')).toBe(zeroText);
120122
});
121123

@@ -147,7 +149,9 @@ test('getAllByText, queryAllByText', () => {
147149
const buttons = getAllByText(/fresh/i);
148150

149151
expect(buttons).toHaveLength(3);
150-
expect(() => getAllByText('InExistent')).toThrow('No instances found');
152+
expect(() => getAllByText('InExistent')).toThrow(
153+
'Unable to find an element with text: InExistent'
154+
);
151155

152156
expect(queryAllByText(/fresh/i)).toEqual(buttons);
153157
expect(queryAllByText('InExistent')).toHaveLength(0);
@@ -163,13 +167,13 @@ test('getByPlaceholderText, queryByPlaceholderText', () => {
163167

164168
expect(sameInput.props.placeholder).toBe(PLACEHOLDER_FRESHNESS);
165169
expect(() => getByPlaceholderText('no placeholder')).toThrow(
166-
'No instances found with placeholder "no placeholder"'
170+
'Unable to find an element with placeholder: no placeholder'
167171
);
168172

169173
expect(queryByPlaceholderText(/add/i)).toBe(input);
170174
expect(queryByPlaceholderText('no placeholder')).toBeNull();
171175
expect(() => queryByPlaceholderText(/fresh/)).toThrow(
172-
'Expected 1 but found 2'
176+
'Found multiple elements with placeholder: /fresh/ '
173177
);
174178
});
175179

@@ -181,7 +185,7 @@ test('getAllByPlaceholderText, queryAllByPlaceholderText', () => {
181185

182186
expect(inputs).toHaveLength(2);
183187
expect(() => getAllByPlaceholderText('no placeholder')).toThrow(
184-
'No instances found'
188+
'Unable to find an element with placeholder: no placeholder'
185189
);
186190

187191
expect(queryAllByPlaceholderText(/fresh/i)).toEqual(inputs);
@@ -198,12 +202,14 @@ test('getByDisplayValue, queryByDisplayValue', () => {
198202

199203
expect(sameInput.props.value).toBe(INPUT_FRESHNESS);
200204
expect(() => getByDisplayValue('no value')).toThrow(
201-
'No instances found with display value "no value"'
205+
'Unable to find an element with displayValue: no value'
202206
);
203207

204208
expect(queryByDisplayValue(/custom/i)).toBe(input);
205209
expect(queryByDisplayValue('no value')).toBeNull();
206-
expect(() => queryByDisplayValue(/fresh/i)).toThrow('Expected 1 but found 2');
210+
expect(() => queryByDisplayValue(/fresh/i)).toThrow(
211+
'Found multiple elements with display value: /fresh/i'
212+
);
207213
});
208214

209215
test('getByDisplayValue, queryByDisplayValue get element by default value only when value is undefined', () => {
@@ -223,7 +229,9 @@ test('getAllByDisplayValue, queryAllByDisplayValue', () => {
223229
const inputs = getAllByDisplayValue(/fresh/i);
224230

225231
expect(inputs).toHaveLength(2);
226-
expect(() => getAllByDisplayValue('no value')).toThrow('No instances found');
232+
expect(() => getAllByDisplayValue('no value')).toThrow(
233+
'Unable to find an element with displayValue: no value'
234+
);
227235

228236
expect(queryAllByDisplayValue(/fresh/i)).toEqual(inputs);
229237
expect(queryAllByDisplayValue('no value')).toHaveLength(0);

src/helpers/a11yAPI.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// @flow
22
import type { A11yRole, A11yStates, A11yState, A11yValue } from '../types.flow';
33
import type { WaitForOptions } from '../waitFor';
4-
import makeQuery from './makeQuery';
4+
import makeA11yQuery from './makeA11yQuery';
55

66
type GetReturn = ReactTestInstance;
77
type GetAllReturn = Array<ReactTestInstance>;
@@ -118,7 +118,7 @@ export function matchObject<T: {}>(prop?: T, matcher: T): boolean {
118118

119119
export const a11yAPI = (instance: ReactTestInstance): A11yAPI =>
120120
({
121-
...makeQuery(
121+
...makeA11yQuery(
122122
'accessibilityLabel',
123123
{
124124
getBy: ['getByA11yLabel', 'getByAccessibilityLabel', 'getByLabelText'],
@@ -150,7 +150,7 @@ export const a11yAPI = (instance: ReactTestInstance): A11yAPI =>
150150
},
151151
matchStringValue
152152
)(instance),
153-
...makeQuery(
153+
...makeA11yQuery(
154154
'accessibilityHint',
155155
{
156156
getBy: ['getByA11yHint', 'getByAccessibilityHint', 'getByHintText'],
@@ -178,7 +178,7 @@ export const a11yAPI = (instance: ReactTestInstance): A11yAPI =>
178178
},
179179
matchStringValue
180180
)(instance),
181-
...makeQuery(
181+
...makeA11yQuery(
182182
'accessibilityRole',
183183
{
184184
getBy: ['getByA11yRole', 'getByAccessibilityRole', 'getByRole'],
@@ -202,7 +202,7 @@ export const a11yAPI = (instance: ReactTestInstance): A11yAPI =>
202202
},
203203
matchStringValue
204204
)(instance),
205-
...makeQuery(
205+
...makeA11yQuery(
206206
'accessibilityStates',
207207
{
208208
getBy: ['getByA11yStates', 'getByAccessibilityStates'],
@@ -214,7 +214,7 @@ export const a11yAPI = (instance: ReactTestInstance): A11yAPI =>
214214
},
215215
matchArrayValue
216216
)(instance),
217-
...makeQuery(
217+
...makeA11yQuery(
218218
'accessibilityState',
219219
{
220220
getBy: ['getByA11yState', 'getByAccessibilityState'],
@@ -226,7 +226,7 @@ export const a11yAPI = (instance: ReactTestInstance): A11yAPI =>
226226
},
227227
matchObject
228228
)(instance),
229-
...makeQuery(
229+
...makeA11yQuery(
230230
'accessibilityValue',
231231
{
232232
getBy: ['getByA11yValue', 'getByAccessibilityValue'],

src/helpers/byDisplayValue.js

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// @flow
2+
import { makeQueries } from './makeQueries';
3+
import type { Queries } from './makeQueries';
4+
import { filterNodeByType } from './filterNodeByType';
5+
import { createLibraryNotSupportedError } from './errors';
6+
7+
const getTextInputNodeByDisplayValue = (node, value) => {
8+
try {
9+
const { TextInput } = require('react-native');
10+
const nodeValue =
11+
node.props.value !== undefined
12+
? node.props.value
13+
: node.props.defaultValue;
14+
return (
15+
filterNodeByType(node, TextInput) &&
16+
(typeof value === 'string' ? value === nodeValue : value.test(nodeValue))
17+
);
18+
} catch (error) {
19+
throw createLibraryNotSupportedError(error);
20+
}
21+
};
22+
23+
const queryAllByDisplayValue = (
24+
instance: ReactTestInstance
25+
): ((displayValue: string | RegExp) => Array<ReactTestInstance>) =>
26+
function queryAllByDisplayValueFn(displayValue) {
27+
return instance.findAll((node) =>
28+
getTextInputNodeByDisplayValue(node, displayValue)
29+
);
30+
};
31+
32+
const getMultipleError = (displayValue: string | RegExp) =>
33+
`Found multiple elements with display value: ${String(displayValue)} `;
34+
const getMissingError = (displayValue: string | RegExp) =>
35+
`Unable to find an element with displayValue: ${String(displayValue)}`;
36+
37+
const {
38+
getBy: getByDisplayValue,
39+
getAllBy: getAllByDisplayValue,
40+
queryBy: queryByDisplayValue,
41+
findBy: findByDisplayValue,
42+
findAllBy: findAllByDisplayValue,
43+
}: Queries<string | RegExp> = makeQueries(
44+
queryAllByDisplayValue,
45+
getMissingError,
46+
getMultipleError
47+
);
48+
49+
export {
50+
findAllByDisplayValue,
51+
findByDisplayValue,
52+
getAllByDisplayValue,
53+
getByDisplayValue,
54+
queryAllByDisplayValue,
55+
queryByDisplayValue,
56+
};

src/helpers/byPlaceholderText.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// @flow
2+
import { makeQueries } from './makeQueries';
3+
import type { Queries } from './makeQueries';
4+
import { filterNodeByType } from './filterNodeByType';
5+
import { createLibraryNotSupportedError } from './errors';
6+
7+
const getTextInputNodeByPlaceholderText = (node, placeholder) => {
8+
try {
9+
const { TextInput } = require('react-native');
10+
return (
11+
filterNodeByType(node, TextInput) &&
12+
(typeof placeholder === 'string'
13+
? placeholder === node.props.placeholder
14+
: placeholder.test(node.props.placeholder))
15+
);
16+
} catch (error) {
17+
throw createLibraryNotSupportedError(error);
18+
}
19+
};
20+
21+
const queryAllByPlaceholderText = (
22+
instance: ReactTestInstance
23+
): ((placeholder: string | RegExp) => Array<ReactTestInstance>) =>
24+
function queryAllByPlaceholderFn(placeholder) {
25+
return instance.findAll((node) =>
26+
getTextInputNodeByPlaceholderText(node, placeholder)
27+
);
28+
};
29+
30+
const getMultipleError = (placeholder) =>
31+
`Found multiple elements with placeholder: ${String(placeholder)} `;
32+
const getMissingError = (placeholder) =>
33+
`Unable to find an element with placeholder: ${String(placeholder)}`;
34+
35+
const {
36+
getBy: getByPlaceholderText,
37+
getAllBy: getAllByPlaceholderText,
38+
queryBy: queryByPlaceholderText,
39+
findBy: findByPlaceholderText,
40+
findAllBy: findAllByPlaceholderText,
41+
}: Queries<string | RegExp> = makeQueries(
42+
queryAllByPlaceholderText,
43+
getMissingError,
44+
getMultipleError
45+
);
46+
47+
export {
48+
findAllByPlaceholderText,
49+
findByPlaceholderText,
50+
getAllByPlaceholderText,
51+
getByPlaceholderText,
52+
queryAllByPlaceholderText,
53+
queryByPlaceholderText,
54+
};

src/helpers/byTestId.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const getNodeByTestId = (node, testID) => {
1111
const queryAllByTestId = (
1212
instance: ReactTestInstance
1313
): ((testId: string | RegExp) => Array<ReactTestInstance>) =>
14-
function getAllByTestIdFn(testId) {
14+
function queryAllByTestIdFn(testId) {
1515
const results = instance
1616
.findAll((node) => getNodeByTestId(node, testId))
1717
.filter((element) => typeof element.type === 'string');
@@ -37,10 +37,10 @@ const {
3737
);
3838

3939
export {
40-
getByTestId,
41-
getAllByTestId,
42-
queryByTestId,
43-
findByTestId,
4440
findAllByTestId,
41+
findByTestId,
42+
getAllByTestId,
43+
getByTestId,
4544
queryAllByTestId,
45+
queryByTestId,
4646
};

src/helpers/byText.js

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// @flow
2+
import * as React from 'react';
3+
import { makeQueries } from './makeQueries';
4+
import type { Queries } from './makeQueries';
5+
import { filterNodeByType } from './filterNodeByType';
6+
import { createLibraryNotSupportedError } from './errors';
7+
8+
const getChildrenAsText = (children, TextComponent, textContent = []) => {
9+
React.Children.forEach(children, (child) => {
10+
if (typeof child === 'string') {
11+
textContent.push(child);
12+
return;
13+
}
14+
15+
if (typeof child === 'number') {
16+
textContent.push(child.toString());
17+
return;
18+
}
19+
20+
if (child?.props?.children) {
21+
// Bail on traversing text children down the tree if current node (child)
22+
// has no text. In such situations, react-test-renderer will traverse down
23+
// this tree in a separate call and run this query again. As a result, the
24+
// query will match the deepest text node that matches requested text.
25+
if (filterNodeByType(child, TextComponent) && textContent.length === 0) {
26+
return;
27+
}
28+
29+
getChildrenAsText(child.props.children, TextComponent, textContent);
30+
}
31+
});
32+
33+
return textContent;
34+
};
35+
36+
const getNodeByText = (node, text: string | RegExp) => {
37+
try {
38+
const { Text } = require('react-native');
39+
const isTextComponent = filterNodeByType(node, Text);
40+
if (isTextComponent) {
41+
const textChildren = getChildrenAsText(node.props.children, Text);
42+
if (textChildren) {
43+
const textToTest = textChildren.join('');
44+
return typeof text === 'string'
45+
? text === textToTest
46+
: text.test(textToTest);
47+
}
48+
}
49+
return false;
50+
} catch (error) {
51+
throw createLibraryNotSupportedError(error);
52+
}
53+
};
54+
55+
const queryAllByText = (
56+
instance: ReactTestInstance
57+
): ((text: string | RegExp) => Array<ReactTestInstance>) =>
58+
function queryAllByTextFn(text) {
59+
const results = instance.findAll((node) => getNodeByText(node, text));
60+
61+
return results;
62+
};
63+
64+
const getMultipleError = (text: string | RegExp) =>
65+
`Found multiple elements with text: ${String(text)}`;
66+
const getMissingError = (text: string | RegExp) =>
67+
`Unable to find an element with text: ${String(text)}`;
68+
69+
const {
70+
getBy: getByText,
71+
getAllBy: getAllByText,
72+
queryBy: queryByText,
73+
findBy: findByText,
74+
findAllBy: findAllByText,
75+
}: Queries<string | RegExp> = makeQueries(
76+
queryAllByText,
77+
getMissingError,
78+
getMultipleError
79+
);
80+
81+
export {
82+
findAllByText,
83+
findByText,
84+
getAllByText,
85+
getByText,
86+
queryAllByText,
87+
queryByText,
88+
};

src/helpers/filterNodeByType.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const filterNodeByType = (node, type) => node.type === type;

0 commit comments

Comments
 (0)