Skip to content

Commit 5d68179

Browse files
feat: add screen API (#994)
* feature: screen * feature: update docs * refactor: add flow type * docs: more * docs: final tweaks * test rerender * code review changes * alias RenderResult to RenderAPI Co-authored-by: Michał Pierzchała <[email protected]>
1 parent 1e67026 commit 5d68179

14 files changed

+358
-139
lines changed

README.md

+4-6
Original file line numberDiff line numberDiff line change
@@ -102,22 +102,20 @@ flow-typed install react-test-renderer
102102
## Example
103103

104104
```jsx
105-
import { render, fireEvent } from '@testing-library/react-native';
105+
import { render, screen, fireEvent } from '@testing-library/react-native';
106106
import { QuestionsBoard } from '../QuestionsBoard';
107107

108108
test('form submits two answers', () => {
109109
const allQuestions = ['q1', 'q2'];
110110
const mockFn = jest.fn();
111111

112-
const { getAllByLabelText, getByText } = render(
113-
<QuestionsBoard questions={allQuestions} onSubmit={mockFn} />
114-
);
112+
render(<QuestionsBoard questions={allQuestions} onSubmit={mockFn} />);
115113

116-
const answerInputs = getAllByLabelText('answer input');
114+
const answerInputs = screen.getAllByLabelText('answer input');
117115

118116
fireEvent.changeText(answerInputs[0], 'a1');
119117
fireEvent.changeText(answerInputs[1], 'a2');
120-
fireEvent.press(getByText('Submit'));
118+
fireEvent.press(screen.getByText('Submit'));
121119

122120
expect(mockFn).toBeCalledWith({
123121
'1': { q: 'q1', a: 'a1' },

src/__tests__/screen.test.tsx

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import * as React from 'react';
2+
import { View, Text } from 'react-native';
3+
import { render, screen } from '..';
4+
5+
test('screen has the same queries as render result', () => {
6+
const result = render(<Text>Mt. Everest</Text>);
7+
expect(screen).toBe(result);
8+
9+
expect(screen.getByText('Mt. Everest')).toBeTruthy();
10+
expect(screen.queryByText('Mt. Everest')).toBeTruthy();
11+
expect(screen.getAllByText('Mt. Everest')).toHaveLength(1);
12+
expect(screen.queryAllByText('Mt. Everest')).toHaveLength(1);
13+
});
14+
15+
test('screen holds last render result', () => {
16+
render(<Text>Mt. Everest</Text>);
17+
render(<Text>Mt. Blanc</Text>);
18+
const finalResult = render(<Text>Śnieżka</Text>);
19+
expect(screen).toBe(finalResult);
20+
21+
expect(screen.getByText('Śnieżka')).toBeTruthy();
22+
expect(screen.queryByText('Mt. Everest')).toBeFalsy();
23+
expect(screen.queryByText('Mt. Blanc')).toBeFalsy();
24+
});
25+
26+
test('screen works with updating rerender', () => {
27+
const result = render(<Text>Mt. Everest</Text>);
28+
expect(screen).toBe(result);
29+
30+
screen.rerender(<Text>Śnieżka</Text>);
31+
expect(screen).toBe(result);
32+
expect(screen.getByText('Śnieżka')).toBeTruthy();
33+
});
34+
35+
test('screen works with nested re-mounting rerender', () => {
36+
const result = render(
37+
<View>
38+
<Text>Mt. Everest</Text>
39+
</View>
40+
);
41+
expect(screen).toBe(result);
42+
43+
screen.rerender(
44+
<View>
45+
<View>
46+
<Text>Śnieżka</Text>
47+
</View>
48+
</View>
49+
);
50+
expect(screen).toBe(result);
51+
expect(screen.getByText('Śnieżka')).toBeTruthy();
52+
});
53+
54+
test('screen throws without render', () => {
55+
expect(() => screen.container).toThrowError(
56+
'`render` method has not been called'
57+
);
58+
expect(() => screen.debug()).toThrowError(
59+
'`render` method has not been called'
60+
);
61+
expect(() => screen.debug.shallow()).toThrowError(
62+
'`render` method has not been called'
63+
);
64+
expect(() => screen.getByText('Mt. Everest')).toThrowError(
65+
'`render` method has not been called'
66+
);
67+
});

src/cleanup.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import * as React from 'react';
2+
import { clearRenderResult } from './screen';
23

34
type CleanUpFunction = (nextElement?: React.ReactElement<any>) => void;
45
let cleanupQueue = new Set<CleanUpFunction>();
56

67
export default function cleanup() {
8+
clearRenderResult();
79
cleanupQueue.forEach((fn) => fn());
810
cleanupQueue.clear();
911
}

src/pure.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import act from './act';
22
import cleanup from './cleanup';
33
import fireEvent from './fireEvent';
4-
import render from './render';
4+
import render, { RenderResult } from './render';
55
import waitFor from './waitFor';
66
import waitForElementToBeRemoved from './waitForElementToBeRemoved';
77
import { within, getQueriesForElement } from './within';
88
import { getDefaultNormalizer } from './matches';
99
import { renderHook } from './renderHook';
10+
import { screen } from './screen';
11+
12+
export type { RenderResult };
13+
export type RenderAPI = RenderResult;
1014

1115
export { act };
1216
export { cleanup };
@@ -17,4 +21,4 @@ export { waitForElementToBeRemoved };
1721
export { within, getQueriesForElement };
1822
export { getDefaultNormalizer };
1923
export { renderHook };
20-
export type RenderAPI = ReturnType<typeof render>;
24+
export { screen };

src/render.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { addToCleanupQueue } from './cleanup';
66
import debugShallow from './helpers/debugShallow';
77
import debugDeep from './helpers/debugDeep';
88
import { getQueriesForElement } from './within';
9+
import { setRenderResult } from './screen';
910

1011
type Options = {
1112
wrapper?: React.ComponentType<any>;
@@ -15,6 +16,8 @@ type TestRendererOptions = {
1516
createNodeMock: (element: React.ReactElement) => any;
1617
};
1718

19+
export type RenderResult = ReturnType<typeof render>;
20+
1821
/**
1922
* Renders test component deeply using react-test-renderer and exposes helpers
2023
* to assert on the output.
@@ -40,7 +43,7 @@ export default function render<T>(
4043

4144
addToCleanupQueue(unmount);
4245

43-
return {
46+
const result = {
4447
...getQueriesForElement(instance),
4548
update,
4649
unmount,
@@ -49,6 +52,9 @@ export default function render<T>(
4952
toJSON: renderer.toJSON,
5053
debug: debug(instance, renderer),
5154
};
55+
56+
setRenderResult(result);
57+
return result;
5258
}
5359

5460
function renderWithAct(

src/screen.ts

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { ReactTestInstance } from 'react-test-renderer';
2+
import { RenderResult } from './render';
3+
4+
const SCREEN_ERROR = '`render` method has not been called';
5+
6+
const notImplemented = () => {
7+
throw new Error(SCREEN_ERROR);
8+
};
9+
10+
const notImplementedDebug = () => {
11+
throw new Error(SCREEN_ERROR);
12+
};
13+
notImplementedDebug.shallow = notImplemented;
14+
15+
const defaultScreen: RenderResult = {
16+
get container(): ReactTestInstance {
17+
throw new Error(SCREEN_ERROR);
18+
},
19+
debug: notImplementedDebug,
20+
update: notImplemented,
21+
unmount: notImplemented,
22+
rerender: notImplemented,
23+
toJSON: notImplemented,
24+
getByLabelText: notImplemented,
25+
getAllByLabelText: notImplemented,
26+
queryByLabelText: notImplemented,
27+
queryAllByLabelText: notImplemented,
28+
findByLabelText: notImplemented,
29+
findAllByLabelText: notImplemented,
30+
getByA11yHint: notImplemented,
31+
getByHintText: notImplemented,
32+
getAllByA11yHint: notImplemented,
33+
getAllByHintText: notImplemented,
34+
queryByA11yHint: notImplemented,
35+
queryByHintText: notImplemented,
36+
queryAllByA11yHint: notImplemented,
37+
queryAllByHintText: notImplemented,
38+
findByA11yHint: notImplemented,
39+
findByHintText: notImplemented,
40+
findAllByA11yHint: notImplemented,
41+
findAllByHintText: notImplemented,
42+
getByRole: notImplemented,
43+
getAllByRole: notImplemented,
44+
queryByRole: notImplemented,
45+
queryAllByRole: notImplemented,
46+
findByRole: notImplemented,
47+
findAllByRole: notImplemented,
48+
getByA11yStates: notImplemented,
49+
getAllByA11yStates: notImplemented,
50+
queryByA11yStates: notImplemented,
51+
queryAllByA11yStates: notImplemented,
52+
findByA11yStates: notImplemented,
53+
findAllByA11yStates: notImplemented,
54+
getByA11yState: notImplemented,
55+
getAllByA11yState: notImplemented,
56+
queryByA11yState: notImplemented,
57+
queryAllByA11yState: notImplemented,
58+
findByA11yState: notImplemented,
59+
findAllByA11yState: notImplemented,
60+
getByA11yValue: notImplemented,
61+
getAllByA11yValue: notImplemented,
62+
queryByA11yValue: notImplemented,
63+
queryAllByA11yValue: notImplemented,
64+
findByA11yValue: notImplemented,
65+
findAllByA11yValue: notImplemented,
66+
UNSAFE_getByProps: notImplemented,
67+
UNSAFE_getAllByProps: notImplemented,
68+
UNSAFE_queryByProps: notImplemented,
69+
UNSAFE_queryAllByProps: notImplemented,
70+
UNSAFE_getByType: notImplemented,
71+
UNSAFE_getAllByType: notImplemented,
72+
UNSAFE_queryByType: notImplemented,
73+
UNSAFE_queryAllByType: notImplemented,
74+
getByPlaceholderText: notImplemented,
75+
getAllByPlaceholderText: notImplemented,
76+
queryByPlaceholderText: notImplemented,
77+
queryAllByPlaceholderText: notImplemented,
78+
findByPlaceholderText: notImplemented,
79+
findAllByPlaceholderText: notImplemented,
80+
getByDisplayValue: notImplemented,
81+
getAllByDisplayValue: notImplemented,
82+
queryByDisplayValue: notImplemented,
83+
queryAllByDisplayValue: notImplemented,
84+
findByDisplayValue: notImplemented,
85+
findAllByDisplayValue: notImplemented,
86+
getByTestId: notImplemented,
87+
getAllByTestId: notImplemented,
88+
queryByTestId: notImplemented,
89+
queryAllByTestId: notImplemented,
90+
findByTestId: notImplemented,
91+
findAllByTestId: notImplemented,
92+
getByText: notImplemented,
93+
getAllByText: notImplemented,
94+
queryByText: notImplemented,
95+
queryAllByText: notImplemented,
96+
findByText: notImplemented,
97+
findAllByText: notImplemented,
98+
};
99+
100+
export let screen: RenderResult = defaultScreen;
101+
102+
export function setRenderResult(output: RenderResult) {
103+
screen = output;
104+
}
105+
106+
export function clearRenderResult() {
107+
screen = defaultScreen;
108+
}

typings/index.flow.js

+2
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,8 @@ declare module '@testing-library/react-native' {
363363
options?: RenderOptions
364364
) => RenderAPI;
365365

366+
declare export var screen: RenderAPI;
367+
366368
declare export var cleanup: () => void;
367369
declare export var fireEvent: FireEventAPI;
368370

0 commit comments

Comments
 (0)