Skip to content

Commit d71a7d8

Browse files
MattAgnthymikee
andauthored
chore: restructure ByTestId queries code by predicate type (#608)
* refactor: add makeQueries functions * refactor: use new query builders on byTestId * rebase and rearrange to a bit * add fixme * refactor: make single function makeQueries * doc: add TODO to do before end of pr * fix: fix flow issue * parametrize Queries * chore: delete duplicated test Co-authored-by: Michał Pierzchała <thymikee@gmail.com>
1 parent 199de62 commit d71a7d8

File tree

9 files changed

+336
-179
lines changed

9 files changed

+336
-179
lines changed

src/__tests__/byTestId.test.js

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// @flow
2+
import React from 'react';
3+
import { View, Text, TextInput, TouchableOpacity, Button } from 'react-native';
4+
import { render } from '..';
5+
6+
const PLACEHOLDER_FRESHNESS = 'Add custom freshness';
7+
const PLACEHOLDER_CHEF = 'Who inspected freshness?';
8+
const INPUT_FRESHNESS = 'Custom Freshie';
9+
const INPUT_CHEF = 'I inspected freshie';
10+
11+
class MyButton extends React.Component<any> {
12+
render() {
13+
return (
14+
<TouchableOpacity onPress={this.props.onPress}>
15+
<Text>{this.props.children}</Text>
16+
</TouchableOpacity>
17+
);
18+
}
19+
}
20+
21+
class Banana extends React.Component<any, any> {
22+
state = {
23+
fresh: false,
24+
};
25+
26+
componentDidUpdate() {
27+
if (this.props.onUpdate) {
28+
this.props.onUpdate();
29+
}
30+
}
31+
32+
componentWillUnmount() {
33+
if (this.props.onUnmount) {
34+
this.props.onUnmount();
35+
}
36+
}
37+
38+
changeFresh = () => {
39+
this.setState((state) => ({
40+
fresh: !state.fresh,
41+
}));
42+
};
43+
44+
render() {
45+
const test = 0;
46+
return (
47+
<View>
48+
<Text>Is the banana fresh?</Text>
49+
<Text testID="bananaFresh">
50+
{this.state.fresh ? 'fresh' : 'not fresh'}
51+
</Text>
52+
<TextInput
53+
testID="bananaCustomFreshness"
54+
placeholder={PLACEHOLDER_FRESHNESS}
55+
value={INPUT_FRESHNESS}
56+
/>
57+
<TextInput
58+
testID="bananaChef"
59+
placeholder={PLACEHOLDER_CHEF}
60+
value={INPUT_CHEF}
61+
/>
62+
<MyButton onPress={this.changeFresh} type="primary">
63+
Change freshness!
64+
</MyButton>
65+
<Text testID="duplicateText">First Text</Text>
66+
<Text testID="duplicateText">Second Text</Text>
67+
<Text>{test}</Text>
68+
</View>
69+
);
70+
}
71+
}
72+
73+
const MyComponent = () => {
74+
return <Text>My Component</Text>;
75+
};
76+
77+
test('getByTestId returns only native elements', () => {
78+
const { getByTestId, getAllByTestId } = render(
79+
<View>
80+
<Text testID="text">Text</Text>
81+
<TextInput testID="textInput" />
82+
<View testID="view" />
83+
<Button testID="button" title="Button" onPress={jest.fn()} />
84+
<MyComponent testID="myComponent" />
85+
</View>
86+
);
87+
88+
expect(getByTestId('text')).toBeTruthy();
89+
expect(getByTestId('textInput')).toBeTruthy();
90+
expect(getByTestId('view')).toBeTruthy();
91+
expect(getByTestId('button')).toBeTruthy();
92+
93+
expect(getAllByTestId('text')).toHaveLength(1);
94+
expect(getAllByTestId('textInput')).toHaveLength(1);
95+
expect(getAllByTestId('view')).toHaveLength(1);
96+
expect(getAllByTestId('button')).toHaveLength(1);
97+
98+
expect(() => getByTestId('myComponent')).toThrowError(
99+
'Unable to find an element with testID: myComponent'
100+
);
101+
expect(() => getAllByTestId('myComponent')).toThrowError(
102+
'Unable to find an element with testID: myComponent'
103+
);
104+
});
105+
106+
test('supports a regex matcher', () => {
107+
const { getByTestId, getAllByTestId } = render(
108+
<View>
109+
<Text testID="text">Text</Text>
110+
<TextInput testID="textInput" />
111+
<View testID="view" />
112+
<Button testID="button" title="Button" onPress={jest.fn()} />
113+
<MyComponent testID="myComponent" />
114+
</View>
115+
);
116+
117+
expect(getByTestId(/view/)).toBeTruthy();
118+
expect(getAllByTestId(/text/)).toHaveLength(2);
119+
});
120+
121+
test('getByTestId, queryByTestId', () => {
122+
const { getByTestId, queryByTestId } = render(<Banana />);
123+
const component = getByTestId('bananaFresh');
124+
125+
expect(component.props.children).toBe('not fresh');
126+
expect(() => getByTestId('InExistent')).toThrow(
127+
'Unable to find an element with testID: InExistent'
128+
);
129+
130+
expect(getByTestId('bananaFresh')).toBe(component);
131+
expect(queryByTestId('InExistent')).toBeNull();
132+
});
133+
134+
test('getAllByTestId, queryAllByTestId', () => {
135+
const { getAllByTestId, queryAllByTestId } = render(<Banana />);
136+
const textElements = getAllByTestId('duplicateText');
137+
138+
expect(textElements.length).toBe(2);
139+
expect(textElements[0].props.children).toBe('First Text');
140+
expect(textElements[1].props.children).toBe('Second Text');
141+
expect(() => getAllByTestId('nonExistentTestId')).toThrow(
142+
'Unable to find an element with testID: nonExistentTestId'
143+
);
144+
145+
const queriedTextElements = queryAllByTestId('duplicateText');
146+
147+
expect(queriedTextElements.length).toBe(2);
148+
expect(queriedTextElements[0]).toBe(textElements[0]);
149+
expect(queriedTextElements[1]).toBe(textElements[1]);
150+
expect(queryAllByTestId('nonExistentTestId')).toHaveLength(0);
151+
});
152+
153+
test('findByTestId and findAllByTestId work asynchronously', async () => {
154+
const options = { timeout: 10 }; // Short timeout so that this test runs quickly
155+
const { rerender, findByTestId, findAllByTestId } = render(<View />);
156+
await expect(findByTestId('aTestId', options)).rejects.toBeTruthy();
157+
await expect(findAllByTestId('aTestId', options)).rejects.toBeTruthy();
158+
159+
setTimeout(
160+
() =>
161+
rerender(
162+
<View testID="aTestId">
163+
<Text>Some Text</Text>
164+
<TextInput placeholder="Placeholder Text" />
165+
<TextInput value="Display Value" />
166+
</View>
167+
),
168+
20
169+
);
170+
171+
await expect(findByTestId('aTestId')).resolves.toBeTruthy();
172+
await expect(findAllByTestId('aTestId')).resolves.toHaveLength(1);
173+
}, 20000);

src/__tests__/findByApi.test.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,13 @@ test('findBy queries work asynchronously', async () => {
77
const options = { timeout: 10 }; // Short timeout so that this test runs quickly
88
const {
99
rerender,
10-
findByTestId,
11-
findAllByTestId,
1210
findByText,
1311
findAllByText,
1412
findByPlaceholderText,
1513
findAllByPlaceholderText,
1614
findByDisplayValue,
1715
findAllByDisplayValue,
1816
} = render(<View />);
19-
await expect(findByTestId('aTestId', options)).rejects.toBeTruthy();
20-
await expect(findAllByTestId('aTestId', options)).rejects.toBeTruthy();
2117
await expect(findByText('Some Text', options)).rejects.toBeTruthy();
2218
await expect(findAllByText('Some Text', options)).rejects.toBeTruthy();
2319
await expect(
@@ -45,8 +41,6 @@ test('findBy queries work asynchronously', async () => {
4541
20
4642
);
4743

48-
await expect(findByTestId('aTestId')).resolves.toBeTruthy();
49-
await expect(findAllByTestId('aTestId')).resolves.toHaveLength(1);
5044
await expect(findByText('Some Text')).resolves.toBeTruthy();
5145
await expect(findAllByText('Some Text')).resolves.toHaveLength(1);
5246
await expect(findByPlaceholderText('Placeholder Text')).resolves.toBeTruthy();

src/__tests__/getByApi.test.js

Lines changed: 0 additions & 52 deletions
This file was deleted.

src/__tests__/render.test.js

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -79,36 +79,6 @@ class Banana extends React.Component<any, any> {
7979
}
8080
}
8181

82-
test('getByTestId, queryByTestId', () => {
83-
const { getByTestId, queryByTestId } = render(<Banana />);
84-
const component = getByTestId('bananaFresh');
85-
86-
expect(component.props.children).toBe('not fresh');
87-
expect(() => getByTestId('InExistent')).toThrow('No instances found');
88-
89-
expect(getByTestId('bananaFresh')).toBe(component);
90-
expect(queryByTestId('InExistent')).toBeNull();
91-
});
92-
93-
test('getAllByTestId, queryAllByTestId', () => {
94-
const { getAllByTestId, queryAllByTestId } = render(<Banana />);
95-
const textElements = getAllByTestId('duplicateText');
96-
97-
expect(textElements.length).toBe(2);
98-
expect(textElements[0].props.children).toBe('First Text');
99-
expect(textElements[1].props.children).toBe('Second Text');
100-
expect(() => getAllByTestId('nonExistentTestId')).toThrow(
101-
'No instances found'
102-
);
103-
104-
const queriedTextElements = queryAllByTestId('duplicateText');
105-
106-
expect(queriedTextElements.length).toBe(2);
107-
expect(queriedTextElements[0]).toBe(textElements[0]);
108-
expect(queriedTextElements[1]).toBe(textElements[1]);
109-
expect(queryAllByTestId('nonExistentTestId')).toHaveLength(0);
110-
});
111-
11282
test('UNSAFE_getAllByType, UNSAFE_queryAllByType', () => {
11383
const { UNSAFE_getAllByType, UNSAFE_queryAllByType } = render(<Banana />);
11484
const [text, status, button] = UNSAFE_getAllByType(Text);

src/helpers/byTestId.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// @flow
2+
import { makeQueries } from './makeQueries';
3+
import type { Queries } from './makeQueries';
4+
5+
const getNodeByTestId = (node, testID) => {
6+
return typeof testID === 'string'
7+
? testID === node.props.testID
8+
: testID.test(node.props.testID);
9+
};
10+
11+
const queryAllByTestId = (
12+
instance: ReactTestInstance
13+
): ((testId: string | RegExp) => Array<ReactTestInstance>) =>
14+
function getAllByTestIdFn(testId) {
15+
const results = instance
16+
.findAll((node) => getNodeByTestId(node, testId))
17+
.filter((element) => typeof element.type === 'string');
18+
19+
return results;
20+
};
21+
22+
const getMultipleError = (testId) =>
23+
`Found multiple elements with testID: ${String(testId)}`;
24+
const getMissingError = (testId) =>
25+
`Unable to find an element with testID: ${String(testId)}`;
26+
27+
const {
28+
getBy: getByTestId,
29+
getAllBy: getAllByTestId,
30+
queryBy: queryByTestId,
31+
findBy: findByTestId,
32+
findAllBy: findAllByTestId,
33+
}: Queries<string | RegExp> = makeQueries(
34+
queryAllByTestId,
35+
getMissingError,
36+
getMultipleError
37+
);
38+
39+
export {
40+
getByTestId,
41+
getAllByTestId,
42+
queryByTestId,
43+
findByTestId,
44+
findAllByTestId,
45+
queryAllByTestId,
46+
};

src/helpers/findByAPI.js

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@
22
import waitFor from '../waitFor';
33
import type { WaitForOptions } from '../waitFor';
44
import {
5-
getByTestId,
6-
getAllByTestId,
75
getByText,
86
getAllByText,
97
getByPlaceholderText,
108
getAllByPlaceholderText,
119
getByDisplayValue,
1210
getAllByDisplayValue,
1311
} from './getByAPI';
12+
import { findAllByTestId, findByTestId } from './byTestId';
1413
import { throwRenamedFunctionError } from './errors';
1514

1615
export type FindByAPI = {|
@@ -57,26 +56,6 @@ const makeFindQuery = <Text, Result>(
5756
waitForOptions: WaitForOptions
5857
): Promise<Result> => waitFor(() => getQuery(instance)(text), waitForOptions);
5958

60-
export const findByTestId = (
61-
instance: ReactTestInstance
62-
): ((
63-
testId: string | RegExp,
64-
waitForOptions?: WaitForOptions
65-
) => Promise<ReactTestInstance>) => (
66-
testId: string | RegExp,
67-
waitForOptions: WaitForOptions = {}
68-
) => makeFindQuery(instance, getByTestId, testId, waitForOptions);
69-
70-
export const findAllByTestId = (
71-
instance: ReactTestInstance
72-
): ((
73-
testId: string | RegExp,
74-
waitForOptions?: WaitForOptions
75-
) => Promise<Array<ReactTestInstance>>) => (
76-
testId: string | RegExp,
77-
waitForOptions: WaitForOptions = {}
78-
) => makeFindQuery(instance, getAllByTestId, testId, waitForOptions);
79-
8059
export const findByText = (
8160
instance: ReactTestInstance
8261
): ((

0 commit comments

Comments
 (0)