Skip to content

Commit a56501b

Browse files
thymikeeEsemesek
authored andcommitted
feat: implement ByDisplayValue queries (#190)
1 parent 3352603 commit a56501b

File tree

7 files changed

+152
-9
lines changed

7 files changed

+152
-9
lines changed

docs/Queries.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,19 @@ const { getByPlaceholder } = render(<MyComponent />);
7070
const element = getByPlaceholder('username');
7171
```
7272

73+
### `ByDisplayValue`
74+
75+
> getByDisplayValue, getAllByDisplayValue, queryByDisplayValue, queryAllByDisplayValue
76+
77+
Returns a `ReactTestInstance` for a `TextInput` with a matching display value – may be a string or regular expression.
78+
79+
```jsx
80+
import { render } from 'react-native-testing-library';
81+
82+
const { getByDisplayValue } = render(<MyComponent />);
83+
const element = getByDisplayValue('username');
84+
```
85+
7386
### `ByTestId`
7487

7588
> getByTestId, getAllByTestId, queryByTestId, queryAllByTestId

src/__tests__/__snapshots__/render.test.js.snap

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ exports[`debug 1`] = `
1616
rejectResponderTermination={true}
1717
testID=\\"bananaCustomFreshness\\"
1818
underlineColorAndroid=\\"transparent\\"
19+
value=\\"Custom Freshie\\"
1920
/>
2021
<TextInput
2122
allowFontScaling={true}
2223
placeholder=\\"Who inspected freshness?\\"
2324
rejectResponderTermination={true}
2425
testID=\\"bananaChef\\"
2526
underlineColorAndroid=\\"transparent\\"
27+
value=\\"I inspected freshie\\"
2628
/>
2729
<View
2830
accessible={true}
@@ -62,13 +64,15 @@ exports[`debug changing component: bananaFresh button message should now be "fre
6264
rejectResponderTermination={true}
6365
testID=\\"bananaCustomFreshness\\"
6466
underlineColorAndroid=\\"transparent\\"
67+
value=\\"Custom Freshie\\"
6568
/>
6669
<TextInput
6770
allowFontScaling={true}
6871
placeholder=\\"Who inspected freshness?\\"
6972
rejectResponderTermination={true}
7073
testID=\\"bananaChef\\"
7174
underlineColorAndroid=\\"transparent\\"
75+
value=\\"I inspected freshie\\"
7276
/>
7377
<View
7478
accessible={true}
@@ -108,13 +112,15 @@ exports[`debug: shallow 1`] = `
108112
rejectResponderTermination={true}
109113
testID=\\"bananaCustomFreshness\\"
110114
underlineColorAndroid=\\"transparent\\"
115+
value=\\"Custom Freshie\\"
111116
/>
112117
<TextInput
113118
allowFontScaling={true}
114119
placeholder=\\"Who inspected freshness?\\"
115120
rejectResponderTermination={true}
116121
testID=\\"bananaChef\\"
117122
underlineColorAndroid=\\"transparent\\"
123+
value=\\"I inspected freshie\\"
118124
/>
119125
<Button
120126
onPress={[Function anonymous]}
@@ -143,13 +149,15 @@ exports[`debug: shallow with message 1`] = `
143149
rejectResponderTermination={true}
144150
testID=\\"bananaCustomFreshness\\"
145151
underlineColorAndroid=\\"transparent\\"
152+
value=\\"Custom Freshie\\"
146153
/>
147154
<TextInput
148155
allowFontScaling={true}
149156
placeholder=\\"Who inspected freshness?\\"
150157
rejectResponderTermination={true}
151158
testID=\\"bananaChef\\"
152159
underlineColorAndroid=\\"transparent\\"
160+
value=\\"I inspected freshie\\"
153161
/>
154162
<Button
155163
onPress={[Function anonymous]}
@@ -178,13 +186,15 @@ exports[`debug: with message 1`] = `
178186
rejectResponderTermination={true}
179187
testID=\\"bananaCustomFreshness\\"
180188
underlineColorAndroid=\\"transparent\\"
189+
value=\\"Custom Freshie\\"
181190
/>
182191
<TextInput
183192
allowFontScaling={true}
184193
placeholder=\\"Who inspected freshness?\\"
185194
rejectResponderTermination={true}
186195
testID=\\"bananaChef\\"
187196
underlineColorAndroid=\\"transparent\\"
197+
value=\\"I inspected freshie\\"
188198
/>
189199
<View
190200
accessible={true}

src/__tests__/render.test.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ type ConsoleLogMock = JestMockFn<Array<string>, void>;
1515

1616
const PLACEHOLDER_FRESHNESS = 'Add custom freshness';
1717
const PLACEHOLDER_CHEF = 'Who inspected freshness?';
18+
const INPUT_FRESHNESS = 'Custom Freshie';
19+
const INPUT_CHEF = 'I inspected freshie';
1820

1921
class Button extends React.Component<*> {
2022
render() {
@@ -59,8 +61,13 @@ class Banana extends React.Component<*, *> {
5961
<TextInput
6062
testID="bananaCustomFreshness"
6163
placeholder={PLACEHOLDER_FRESHNESS}
64+
value={INPUT_FRESHNESS}
65+
/>
66+
<TextInput
67+
testID="bananaChef"
68+
placeholder={PLACEHOLDER_CHEF}
69+
value={INPUT_CHEF}
6270
/>
63-
<TextInput testID="bananaChef" placeholder={PLACEHOLDER_CHEF} />
6471
<Button onPress={this.changeFresh} type="primary">
6572
Change freshness!
6673
</Button>
@@ -208,6 +215,33 @@ test('getAllByPlaceholder, queryAllByPlaceholder', () => {
208215
expect(queryAllByPlaceholder('no placeholder')).toHaveLength(0);
209216
});
210217

218+
test('getByDisplayValue, queryByDisplayValue', () => {
219+
const { getByDisplayValue, queryByDisplayValue } = render(<Banana />);
220+
const input = getByDisplayValue(/custom/i);
221+
222+
expect(input.props.value).toBe(INPUT_FRESHNESS);
223+
224+
const sameInput = getByDisplayValue(INPUT_FRESHNESS);
225+
226+
expect(sameInput.props.value).toBe(INPUT_FRESHNESS);
227+
expect(() => getByDisplayValue('no value')).toThrow('No instances found');
228+
229+
expect(queryByDisplayValue(/custom/i)).toBe(input);
230+
expect(queryByDisplayValue('no value')).toBeNull();
231+
expect(() => queryByDisplayValue(/fresh/i)).toThrow('Expected 1 but found 2');
232+
});
233+
234+
test('getAllByDisplayValue, queryAllByDisplayValue', () => {
235+
const { getAllByDisplayValue, queryAllByDisplayValue } = render(<Banana />);
236+
const inputs = getAllByDisplayValue(/fresh/i);
237+
238+
expect(inputs).toHaveLength(2);
239+
expect(() => getAllByDisplayValue('no value')).toThrow('No instances found');
240+
241+
expect(queryAllByDisplayValue(/fresh/i)).toEqual(inputs);
242+
expect(queryAllByDisplayValue('no value')).toHaveLength(0);
243+
});
244+
211245
test('getByProps, queryByProps', () => {
212246
const { getByProps, queryByProps } = render(<Banana />);
213247
const primaryType = getByProps({ type: 'primary' });

src/helpers/getByAPI.js

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ const getNodeByText = (node, text) => {
1818
try {
1919
// eslint-disable-next-line
2020
const { Text, TextInput } = require('react-native');
21-
const isTextComponent =
22-
filterNodeByType(node, Text) || filterNodeByType(node, TextInput);
21+
const isTextComponent = filterNodeByType(node, Text);
2322
if (isTextComponent) {
2423
const textChildren = React.Children.map(
2524
node.props.children,
@@ -54,6 +53,21 @@ const getTextInputNodeByPlaceholder = (node, placeholder) => {
5453
}
5554
};
5655

56+
const getTextInputNodeByDisplayValue = (node, value) => {
57+
try {
58+
// eslint-disable-next-line
59+
const { TextInput } = require('react-native');
60+
return (
61+
filterNodeByType(node, TextInput) &&
62+
(typeof value === 'string'
63+
? value === node.props.value
64+
: value.test(node.props.value))
65+
);
66+
} catch (error) {
67+
throw createLibraryNotSupportedError(error);
68+
}
69+
};
70+
5771
export const getByName = (instance: ReactTestInstance) =>
5872
function getByNameFn(name: string | React.ComponentType<*>) {
5973
logDeprecationWarning('getByName', 'getByType');
@@ -95,6 +109,17 @@ export const getByPlaceholder = (instance: ReactTestInstance) =>
95109
}
96110
};
97111

112+
export const getByDisplayValue = (instance: ReactTestInstance) =>
113+
function getByDisplayValueFn(placeholder: string | RegExp) {
114+
try {
115+
return instance.find(node =>
116+
getTextInputNodeByDisplayValue(node, placeholder)
117+
);
118+
} catch (error) {
119+
throw new ErrorWithStack(prepareErrorMessage(error), getByDisplayValueFn);
120+
}
121+
};
122+
98123
export const getByProps = (instance: ReactTestInstance) =>
99124
function getByPropsFn(props: { [propName: string]: any }) {
100125
try {
@@ -161,6 +186,20 @@ export const getAllByPlaceholder = (instance: ReactTestInstance) =>
161186
return results;
162187
};
163188

189+
export const getAllByDisplayValue = (instance: ReactTestInstance) =>
190+
function getAllByDisplayValueFn(value: string | RegExp) {
191+
const results = instance.findAll(node =>
192+
getTextInputNodeByDisplayValue(node, value)
193+
);
194+
if (results.length === 0) {
195+
throw new ErrorWithStack(
196+
`No instances found with display value: ${String(value)}`,
197+
getAllByDisplayValueFn
198+
);
199+
}
200+
return results;
201+
};
202+
164203
export const getAllByProps = (instance: ReactTestInstance) =>
165204
function getAllByPropsFn(props: { [propName: string]: any }) {
166205
const results = instance.findAllByProps(props);
@@ -179,10 +218,12 @@ export const getByAPI = (instance: ReactTestInstance) => ({
179218
getByType: getByType(instance),
180219
getByText: getByText(instance),
181220
getByPlaceholder: getByPlaceholder(instance),
221+
getByDisplayValue: getByDisplayValue(instance),
182222
getByProps: getByProps(instance),
183223
getAllByName: getAllByName(instance),
184224
getAllByType: getAllByType(instance),
185225
getAllByText: getAllByText(instance),
186226
getAllByPlaceholder: getAllByPlaceholder(instance),
227+
getAllByDisplayValue: getAllByDisplayValue(instance),
187228
getAllByProps: getAllByProps(instance),
188229
});

src/helpers/queryByAPI.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import {
66
getByType,
77
getByText,
88
getByPlaceholder,
9+
getByDisplayValue,
910
getByProps,
1011
getAllByName,
1112
getAllByType,
1213
getAllByText,
1314
getAllByPlaceholder,
15+
getAllByDisplayValue,
1416
getAllByProps,
1517
} from './getByAPI';
1618
import { logDeprecationWarning, createQueryByError } from './errors';
@@ -48,7 +50,16 @@ export const queryByPlaceholder = (instance: ReactTestInstance) =>
4850
try {
4951
return getByPlaceholder(instance)(placeholder);
5052
} catch (error) {
51-
return createQueryByError(error, queryByPlaceholder);
53+
return createQueryByError(error, queryByPlaceholderFn);
54+
}
55+
};
56+
57+
export const queryByDisplayValue = (instance: ReactTestInstance) =>
58+
function queryByDisplayValueFn(value: string | RegExp) {
59+
try {
60+
return getByDisplayValue(instance)(value);
61+
} catch (error) {
62+
return createQueryByError(error, queryByDisplayValueFn);
5263
}
5364
};
5465

@@ -111,6 +122,16 @@ export const queryAllByPlaceholder = (instance: ReactTestInstance) => (
111122
}
112123
};
113124

125+
export const queryAllByDisplayValue = (instance: ReactTestInstance) => (
126+
value: string | RegExp
127+
) => {
128+
try {
129+
return getAllByDisplayValue(instance)(value);
130+
} catch (error) {
131+
return [];
132+
}
133+
};
134+
114135
export const queryAllByProps = (instance: ReactTestInstance) => (props: {
115136
[propName: string]: any,
116137
}) => {
@@ -127,10 +148,12 @@ export const queryByAPI = (instance: ReactTestInstance) => ({
127148
queryByType: queryByType(instance),
128149
queryByText: queryByText(instance),
129150
queryByPlaceholder: queryByPlaceholder(instance),
151+
queryByDisplayValue: queryByDisplayValue(instance),
130152
queryByProps: queryByProps(instance),
131153
queryAllByName: queryAllByName(instance),
132154
queryAllByType: queryAllByType(instance),
133155
queryAllByText: queryAllByText(instance),
134156
queryAllByPlaceholder: queryAllByPlaceholder(instance),
157+
queryAllByDisplayValue: queryAllByDisplayValue(instance),
135158
queryAllByProps: queryAllByProps(instance),
136159
});

typings/__tests__/index.test.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ const getByPlaceholderString: ReactTestInstance = tree.getByPlaceholder(
4646
const getByPlaceholderRegExp: ReactTestInstance = tree.getByPlaceholder(
4747
/placeholder/g
4848
);
49+
const getByDisplayValueString: ReactTestInstance = tree.getByDisplayValue(
50+
'my value'
51+
);
52+
const getByDisplayValueRegExp: ReactTestInstance = tree.getByDisplayValue(
53+
/value/g
54+
);
4955
const getByProps: ReactTestInstance = tree.getByProps({ value: 2 });
5056
const getByTestId: ReactTestInstance = tree.getByTestId('test-id');
5157
const getAllByNameString: Array<ReactTestInstance> = tree.getAllByName('View');
@@ -71,16 +77,20 @@ const queryByType: ReactTestInstance | null = tree.queryByType(View);
7177
const queryByTypeWithRequiredProps: ReactTestInstance | null = tree.queryByType(
7278
ElementWithRequiredProps
7379
);
74-
const queryByTextString: ReactTestInstance | null = tree.queryByText(
75-
'<View />'
76-
);
80+
const queryByTextString: ReactTestInstance | null = tree.queryByText('View');
7781
const queryByTextRegExp: ReactTestInstance | null = tree.queryByText(/View/g);
78-
const queryByPlaceholderString: ReactTestInstance | null = tree.queryByText(
82+
const queryByPlaceholderString: ReactTestInstance | null = tree.queryByPlaceholder(
7983
'my placeholder'
8084
);
81-
const queryByPlaceholderRegExp: ReactTestInstance | null = tree.queryByText(
85+
const queryByPlaceholderRegExp: ReactTestInstance | null = tree.queryByPlaceholder(
8286
/placeholder/g
8387
);
88+
const queryByDisplayValueString: ReactTestInstance | null = tree.queryByDisplayValue(
89+
'my value'
90+
);
91+
const queryByDisplayValueRegExp: ReactTestInstance | null = tree.queryByDisplayValue(
92+
/value/g
93+
);
8494
const queryByProps: ReactTestInstance | null = tree.queryByProps({ value: 2 });
8595
const queryByTestId: ReactTestInstance | null = tree.queryByTestId('test-id');
8696
const queryAllByNameString: Array<ReactTestInstance> = tree.queryAllByName(
@@ -99,6 +109,12 @@ const queryAllByTextString: Array<ReactTestInstance> = tree.queryAllByText(
99109
const queryAllByTextRegExp: Array<ReactTestInstance> = tree.queryAllByText(
100110
/View/g
101111
);
112+
const queryAllByDisplayValueString: Array<
113+
ReactTestInstance
114+
> = tree.queryAllByDisplayValue('View');
115+
const queryAllByDisplayValueRegExp: Array<
116+
ReactTestInstance
117+
> = tree.queryAllByDisplayValue(/View/g);
102118

103119
// Accessibility queries
104120
const getByA11yLabel: ReactTestInstance = tree.getByA11yLabel('label');

typings/index.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export interface GetByAPI {
66
getByType: <P>(type: React.ComponentType<P>) => ReactTestInstance;
77
getByText: (text: string | RegExp) => ReactTestInstance;
88
getByPlaceholder: (placeholder: string | RegExp) => ReactTestInstance;
9+
getByDisplayValue: (value: string | RegExp) => ReactTestInstance;
910
getByProps: (props: Record<string, any>) => ReactTestInstance;
1011
getByTestId: (testID: string) => ReactTestInstance;
1112
getAllByName: (name: React.ReactType | string) => Array<ReactTestInstance>;
@@ -14,6 +15,7 @@ export interface GetByAPI {
1415
getAllByPlaceholder: (
1516
placeholder: string | RegExp
1617
) => Array<ReactTestInstance>;
18+
getAllByDisplayValue: (value: string | RegExp) => Array<ReactTestInstance>;
1719
getAllByProps: (props: Record<string, any>) => Array<ReactTestInstance>;
1820
}
1921

@@ -24,6 +26,7 @@ export interface QueryByAPI {
2426
queryByPlaceholder: (
2527
placeholder: string | RegExp
2628
) => ReactTestInstance | null;
29+
queryByDisplayValue: (value: string | RegExp) => ReactTestInstance | null;
2730
queryByProps: (props: Record<string, any>) => ReactTestInstance | null;
2831
queryByTestId: (testID: string) => ReactTestInstance | null;
2932
queryAllByName: (
@@ -36,6 +39,9 @@ export interface QueryByAPI {
3639
queryAllByPlaceholder: (
3740
placeholder: string | RegExp
3841
) => Array<ReactTestInstance> | [];
42+
queryAllByDisplayValue: (
43+
value: string | RegExp
44+
) => Array<ReactTestInstance> | [];
3945
queryAllByProps: (
4046
props: Record<string, any>
4147
) => Array<ReactTestInstance> | [];

0 commit comments

Comments
 (0)