Skip to content

Commit 495811b

Browse files
thymikeeEsemesek
authored andcommitted
feat: add query API (#13)
* feat: add query API * use plural for API * return empty array from queryAll * fix wording in readme * move mass exports to get/query files
1 parent d66255d commit 495811b

File tree

5 files changed

+166
-33
lines changed

5 files changed

+166
-33
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,30 @@ test('fetch data', async () => {
282282
});
283283
```
284284

285+
## `query` APIs
286+
287+
Each of the get APIs listed in the render section above have a complimentary query API. The get APIs will throw errors if a proper node cannot be found. This is normally the desired effect. However, if you want to make an assertion that an element is not present in the hierarchy, then you can use the query API instead:
288+
289+
```jsx
290+
import { render } from 'react-native-testing-library';
291+
292+
const { queryByText } = render(<Form />);
293+
const submitButton = queryByText('submit');
294+
expect(submitButton).toBeNull(); // it doesn't exist
295+
```
296+
297+
## `queryAll` APIs
298+
299+
Each of the query APIs have a corresponding queryAll version that always returns an Array of matching nodes. getAll is the same but throws when the array has a length of 0.
300+
301+
```jsx
302+
import { render } from 'react-native-testing-library';
303+
304+
const { queryAllByText } = render(<Forms />);
305+
const submitButtons = queryAllByText('submit');
306+
expect(submitButtons).toHaveLength(3); // expect 3 elements
307+
```
308+
285309
<!-- badges -->
286310

287311
[build-badge]: https://img.shields.io/circleci/project/github/callstack/react-native-testing-library/master.svg?style=flat-square

src/__tests__/render.test.js

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,19 @@ class Banana extends React.Component {
5151
}
5252
}
5353

54-
test('getByTestId', () => {
55-
const { getByTestId } = render(<Banana />);
54+
test('getByTestId, queryByTestId', () => {
55+
const { getByTestId, queryByTestId } = render(<Banana />);
5656
const component = getByTestId('bananaFresh');
5757

5858
expect(component.props.children).toBe('not fresh');
5959
expect(() => getByTestId('InExistent')).toThrow();
60+
61+
expect(getByTestId('bananaFresh')).toBe(component);
62+
expect(queryByTestId('InExistent')).toBeNull();
6063
});
6164

62-
test('getByName', () => {
63-
const { getByTestId, getByName } = render(<Banana />);
65+
test('getByName, queryByName', () => {
66+
const { getByTestId, getByName, queryByName } = render(<Banana />);
6467
const bananaFresh = getByTestId('bananaFresh');
6568
const button = getByName('Button');
6669

@@ -72,22 +75,27 @@ test('getByName', () => {
7275
sameButton.props.onPress();
7376

7477
expect(bananaFresh.props.children).toBe('not fresh');
75-
7678
expect(() => getByName('InExistent')).toThrow();
79+
80+
expect(queryByName('Button')).toBe(button);
81+
expect(queryByName('InExistent')).toBeNull();
7782
});
7883

79-
test('getAllByName', () => {
80-
const { getAllByName } = render(<Banana />);
84+
test('getAllByName, queryAllByName', () => {
85+
const { getAllByName, queryAllByName } = render(<Banana />);
8186
const [text, status, button] = getAllByName('Text');
8287

8388
expect(text.props.children).toBe('Is the banana fresh?');
8489
expect(status.props.children).toBe('not fresh');
8590
expect(button.props.children).toBe('Change freshness!');
8691
expect(() => getAllByName('InExistent')).toThrow();
92+
93+
expect(queryAllByName('Text')[1]).toBe(status);
94+
expect(queryAllByName('InExistent')).toHaveLength(0);
8795
});
8896

89-
test('getByText', () => {
90-
const { getByText } = render(<Banana />);
97+
test('getByText, queryByText', () => {
98+
const { getByText, queryByText } = render(<Banana />);
9199
const button = getByText(/change/i);
92100

93101
expect(button.props.children).toBe('Change freshness!');
@@ -96,30 +104,42 @@ test('getByText', () => {
96104

97105
expect(sameButton.props.children).toBe('not fresh');
98106
expect(() => getByText('InExistent')).toThrow();
107+
108+
expect(queryByText(/change/i)).toBe(button);
109+
expect(queryByText('InExistent')).toBeNull();
99110
});
100111

101-
test('getAllByText', () => {
102-
const { getAllByText } = render(<Banana />);
103-
const button = getAllByText(/fresh/i);
112+
test('getAllByText, queryAllByText', () => {
113+
const { getAllByText, queryAllByText } = render(<Banana />);
114+
const buttons = getAllByText(/fresh/i);
104115

105-
expect(button).toHaveLength(3);
116+
expect(buttons).toHaveLength(3);
106117
expect(() => getAllByText('InExistent')).toThrow();
118+
119+
expect(queryAllByText(/fresh/i)).toEqual(buttons);
120+
expect(queryAllByText('InExistent')).toHaveLength(0);
107121
});
108122

109-
test('getByProps', () => {
110-
const { getByProps } = render(<Banana />);
123+
test('getByProps, queryByProps', () => {
124+
const { getByProps, queryByProps } = render(<Banana />);
111125
const primaryType = getByProps({ type: 'primary' });
112126

113127
expect(primaryType.props.children).toBe('Change freshness!');
114128
expect(() => getByProps({ type: 'inexistent' })).toThrow();
129+
130+
expect(queryByProps({ type: 'primary' })).toBe(primaryType);
131+
expect(queryByProps({ type: 'inexistent' })).toBeNull();
115132
});
116133

117-
test('getAllByProps', () => {
118-
const { getAllByProps } = render(<Banana />);
134+
test('getAllByProp, queryAllByProps', () => {
135+
const { getAllByProps, queryAllByProps } = render(<Banana />);
119136
const primaryTypes = getAllByProps({ type: 'primary' });
120137

121138
expect(primaryTypes).toHaveLength(1);
122139
expect(() => getAllByProps({ type: 'inexistent' })).toThrow();
140+
141+
expect(queryAllByProps({ type: 'primary' })).toEqual(primaryTypes);
142+
expect(queryAllByProps({ type: 'inexistent' })).toHaveLength(0);
123143
});
124144

125145
test('update', () => {

src/helpers/getBy.js renamed to src/helpers/getByAPI.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,13 @@ export const getAllByProps = (instance: ReactTestInstance) => (props: {
7575
}
7676
return results;
7777
};
78+
79+
export const getByAPI = (instance: ReactTestInstance) => ({
80+
getByTestId: getByTestId(instance),
81+
getByName: getByName(instance),
82+
getByText: getByText(instance),
83+
getByProps: getByProps(instance),
84+
getAllByName: getAllByName(instance),
85+
getAllByText: getAllByText(instance),
86+
getAllByProps: getAllByProps(instance),
87+
});

src/helpers/queryByAPI.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// @flow
2+
import * as React from 'react';
3+
import {
4+
getByTestId,
5+
getByName,
6+
getByText,
7+
getByProps,
8+
getAllByName,
9+
getAllByText,
10+
getAllByProps,
11+
} from './getByAPI';
12+
13+
export const queryByName = (instance: ReactTestInstance) => (
14+
name: string | React.Element<*>
15+
) => {
16+
try {
17+
return getByName(instance)(name);
18+
} catch (error) {
19+
return null;
20+
}
21+
};
22+
23+
export const queryByText = (instance: ReactTestInstance) => (
24+
text: string | RegExp
25+
) => {
26+
try {
27+
return getByText(instance)(text);
28+
} catch (error) {
29+
return null;
30+
}
31+
};
32+
33+
export const queryByProps = (instance: ReactTestInstance) => (props: {
34+
[propName: string]: any,
35+
}) => {
36+
try {
37+
return getByProps(instance)(props);
38+
} catch (error) {
39+
return null;
40+
}
41+
};
42+
43+
export const queryByTestId = (instance: ReactTestInstance) => (
44+
testID: string
45+
) => {
46+
try {
47+
return getByTestId(instance)(testID);
48+
} catch (error) {
49+
return null;
50+
}
51+
};
52+
53+
export const queryAllByName = (instance: ReactTestInstance) => (
54+
name: string | React.Element<*>
55+
) => {
56+
try {
57+
return getAllByName(instance)(name);
58+
} catch (error) {
59+
return [];
60+
}
61+
};
62+
63+
export const queryAllByText = (instance: ReactTestInstance) => (
64+
text: string | RegExp
65+
) => {
66+
try {
67+
return getAllByText(instance)(text);
68+
} catch (error) {
69+
return [];
70+
}
71+
};
72+
73+
export const queryAllByProps = (instance: ReactTestInstance) => (props: {
74+
[propName: string]: any,
75+
}) => {
76+
try {
77+
return getAllByProps(instance)(props);
78+
} catch (error) {
79+
return [];
80+
}
81+
};
82+
83+
export const queryByAPI = (instance: ReactTestInstance) => ({
84+
queryByTestId: queryByTestId(instance),
85+
queryByName: queryByName(instance),
86+
queryByText: queryByText(instance),
87+
queryByProps: queryByProps(instance),
88+
queryAllByName: queryAllByName(instance),
89+
queryAllByText: queryAllByText(instance),
90+
queryAllByProps: queryAllByProps(instance),
91+
});

src/render.js

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,8 @@
11
// @flow
22
import * as React from 'react';
33
import TestRenderer from 'react-test-renderer'; // eslint-disable-line import/no-extraneous-dependencies
4-
import {
5-
getByTestId,
6-
getByName,
7-
getByText,
8-
getByProps,
9-
getAllByName,
10-
getAllByText,
11-
getAllByProps,
12-
} from './helpers/getBy';
4+
import { getByAPI } from './helpers/getByAPI';
5+
import { queryByAPI } from './helpers/queryByAPI';
136

147
/**
158
* Renders test component deeply using react-test-renderer and exposes helpers
@@ -23,13 +16,8 @@ export default function render(
2316
const instance = renderer.root;
2417

2518
return {
26-
getByTestId: getByTestId(instance),
27-
getByName: getByName(instance),
28-
getByText: getByText(instance),
29-
getByProps: getByProps(instance),
30-
getAllByName: getAllByName(instance),
31-
getAllByText: getAllByText(instance),
32-
getAllByProps: getAllByProps(instance),
19+
...getByAPI(instance),
20+
...queryByAPI(instance),
3321
update: renderer.update,
3422
unmount: renderer.unmount,
3523
};

0 commit comments

Comments
 (0)