Skip to content

Commit a9b0b44

Browse files
committed
refactor: renderer API to be more similar to React DOM
1 parent cf4b8b7 commit a9b0b44

File tree

7 files changed

+124
-146
lines changed

7 files changed

+124
-146
lines changed

src/__tests__/host-component-names.test.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
configureHostComponentNamesIfNeeded,
77
} from '../helpers/host-component-names';
88
import { act, render } from '..';
9-
import * as internalRenderer from '../renderer/renderer';
9+
import * as rendererModule from '../renderer/renderer';
1010

1111
describe('getHostComponentNames', () => {
1212
test('returns host component names from internal config', () => {
@@ -102,13 +102,14 @@ describe('configureHostComponentNamesIfNeeded', () => {
102102
});
103103

104104
test('throw an error when auto-detection fails', () => {
105-
let mockRender: jest.SpyInstance;
106-
const result = internalRenderer.render(<View />);
105+
const renderer = rendererModule.createRenderer();
106+
renderer.render(<View />);
107107

108-
mockRender = jest.spyOn(internalRenderer, 'render') as jest.Mock;
109-
mockRender.mockReturnValue({
110-
root: result.root,
111-
});
108+
const mockCreateRenderer = jest
109+
.spyOn(rendererModule, 'createRenderer')
110+
.mockReturnValue(renderer);
111+
// @ts-expect-error
112+
jest.spyOn(renderer, 'render').mockReturnValue(renderer.root);
112113

113114
expect(() => configureHostComponentNamesIfNeeded()).toThrowErrorMatchingInlineSnapshot(`
114115
"Trying to detect host component names triggered the following error:
@@ -119,6 +120,6 @@ describe('configureHostComponentNamesIfNeeded', () => {
119120
Please check if you are using compatible versions of React Native and React Native Testing Library."
120121
`);
121122

122-
mockRender.mockReset();
123+
mockCreateRenderer.mockReset();
123124
});
124125
});

src/__tests__/render-hook.test.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable jest/no-conditional-expect */
22
import React, { ReactNode } from 'react';
3-
import * as internalRenderer from '../renderer/renderer';
3+
import * as rendererModule from '../renderer/renderer';
44
import { renderHook } from '../pure';
55

66
test('gives committed result', () => {
@@ -94,12 +94,14 @@ test('props type is inferred correctly when initial props is explicitly undefine
9494
* we check the count of renders using React Test Renderers.
9595
*/
9696
test('does render only once', () => {
97-
jest.spyOn(internalRenderer, 'render');
97+
const renderer = rendererModule.createRenderer();
98+
const renderSpy = jest.spyOn(renderer, 'render');
99+
jest.spyOn(rendererModule, 'createRenderer').mockReturnValue(renderer);
98100

99101
renderHook(() => {
100102
const [state, setState] = React.useState(1);
101103
return [state, setState];
102104
});
103105

104-
expect(internalRenderer.render).toHaveBeenCalledTimes(1);
106+
expect(renderSpy).toHaveBeenCalledTimes(1);
105107
});

src/helpers/host-component-names.tsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { Image, Modal, ScrollView, Switch, Text, TextInput, View } from 'react-n
33
import { configureInternal, getConfig, HostComponentNames } from '../config';
44
import { HostElement } from '../renderer/host-element';
55
import { renderWithAct } from '../render-act';
6+
import { createRenderer } from '../renderer/renderer';
7+
import act from '../act';
68

79
const userConfigErrorMessage = `There seems to be an issue with your configuration that prevents React Native Testing Library from working correctly.
810
Please check if you are using compatible versions of React Native and React Native Testing Library.`;
@@ -29,17 +31,19 @@ export function configureHostComponentNamesIfNeeded() {
2931

3032
function detectHostComponentNames(): HostComponentNames {
3133
try {
32-
const renderer = renderWithAct(
33-
<View>
34-
<Text testID="text">Hello</Text>
35-
<TextInput testID="textInput" />
36-
<Image testID="image" />
37-
<Switch testID="switch" />
38-
<ScrollView testID="scrollView" />
39-
<Modal testID="modal" />
40-
</View>,
41-
);
42-
34+
const renderer = createRenderer();
35+
act(() => {
36+
renderer.render(
37+
<View>
38+
<Text testID="text">Hello</Text>
39+
<TextInput testID="textInput" />
40+
<Image testID="image" />
41+
<Switch testID="switch" />
42+
<ScrollView testID="scrollView" />
43+
<Modal testID="modal" />
44+
</View>,
45+
);
46+
});
4347
return {
4448
text: getByTestId(renderer.root, 'text').type as string,
4549
textInput: getByTestId(renderer.root, 'textInput').type as string,

src/render-act.ts

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

src/render.tsx

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import { getConfig } from './config';
55
import debugDeep, { DebugOptions } from './helpers/debug-deep';
66
import { configureHostComponentNamesIfNeeded } from './helpers/host-component-names';
77
import { HostElement } from './renderer/host-element';
8-
import { RenderResult as RendererResult } from './renderer/renderer';
9-
import { renderWithAct } from './render-act';
8+
import { createRenderer, Renderer } from './renderer/renderer';
109
import { setRenderResult } from './screen';
1110
import { getQueriesForElement } from './within';
1211

1312
export interface RenderOptions {
1413
wrapper?: React.ComponentType<any>;
1514
createNodeMock?: (element: React.ReactElement) => unknown;
15+
isConcurrent?: boolean;
1616
}
1717

1818
export type RenderResult = ReturnType<typeof render>;
@@ -40,17 +40,24 @@ export function renderInternal<T>(
4040
}
4141

4242
const wrap = (element: React.ReactElement) => (Wrapper ? <Wrapper>{element}</Wrapper> : element);
43-
const renderer = renderWithAct(wrap(component), restOptions);
43+
44+
const renderer = createRenderer(restOptions);
45+
void act(() => {
46+
renderer.render(wrap(component));
47+
});
48+
4449
return buildRenderResult(renderer, wrap);
4550
}
4651

47-
function buildRenderResult(
48-
renderer: RendererResult,
49-
wrap: (element: React.ReactElement) => JSX.Element,
50-
) {
51-
const update = updateWithAct(renderer, wrap);
52+
function buildRenderResult(renderer: Renderer, wrap: (element: React.ReactElement) => JSX.Element) {
5253
const instance = renderer.container ?? renderer.root;
5354

55+
const update = (element: React.ReactElement) => {
56+
void act(() => {
57+
renderer.render(wrap(element));
58+
});
59+
};
60+
5461
const unmount = () => {
5562
void act(() => {
5663
renderer.unmount();
@@ -76,20 +83,9 @@ function buildRenderResult(
7683
return result;
7784
}
7885

79-
function updateWithAct(
80-
renderer: RendererResult,
81-
wrap: (innerElement: React.ReactElement) => React.ReactElement,
82-
) {
83-
return function (component: React.ReactElement) {
84-
void act(() => {
85-
renderer.update(wrap(component));
86-
});
87-
};
88-
}
89-
9086
export type DebugFunction = (options?: DebugOptions | string) => void;
9187

92-
function debug(renderer: RendererResult): DebugFunction {
88+
function debug(renderer: Renderer): DebugFunction {
9389
function debugImpl(options?: DebugOptions | string) {
9490
const { defaultDebugOptions } = getConfig();
9591
const debugOptions =

src/renderer/renderer.test.tsx

Lines changed: 56 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,42 @@
11
import * as React from 'react';
22
import { View, Text } from 'react-native';
3-
import { render } from './renderer';
3+
import { createRenderer } from './renderer';
44

55
function Passthrough({ children }: { children: React.ReactNode }) {
66
return children;
77
}
88

99
test('renders View', () => {
10-
render(<View />);
11-
expect(true).toBe(true);
10+
const renderer = createRenderer();
11+
renderer.render(<View />);
12+
expect(renderer.toJSON()).toMatchInlineSnapshot(`<View />`);
1213
});
1314

1415
test('renders Text', () => {
15-
render(<Text>Hello world</Text>);
16-
expect(true).toBe(true);
17-
});
18-
19-
test('throws when rendering string outside of Text', () => {
20-
jest.spyOn(console, 'error').mockImplementation(() => {});
21-
22-
expect(() => render(<View>Hello</View>)).toThrowErrorMatchingInlineSnapshot(
23-
`"Invariant Violation: Text strings must be rendered within a <Text> component. Detected attempt to render "Hello" string within a <View> component."`,
24-
);
25-
26-
expect(() => render(<Passthrough>Hello</Passthrough>)).toThrowErrorMatchingInlineSnapshot(
27-
`"Invariant Violation: Text strings must be rendered within a <Text> component. Detected attempt to render "Hello" string within a <ROOT> component."`,
28-
);
29-
30-
expect(() => render(<>Hello</>)).toThrowErrorMatchingInlineSnapshot(
31-
`"Invariant Violation: Text strings must be rendered within a <Text> component. Detected attempt to render "Hello" string within a <ROOT> component."`,
32-
);
33-
34-
jest.restoreAllMocks();
16+
const renderer = createRenderer();
17+
renderer.render(<Text>Hello RNTL!</Text>);
18+
expect(renderer.toJSON()).toMatchInlineSnapshot(`
19+
<Text>
20+
Hello RNTL!
21+
</Text>
22+
`);
3523
});
3624

37-
test('implements update()', () => {
38-
const result = render(<View testID="view" />);
39-
expect(result.toJSON()).toMatchInlineSnapshot(`
25+
test('can update rendered element', () => {
26+
const renderer = createRenderer();
27+
renderer.render(<View testID="view" />);
28+
expect(renderer.toJSON()).toMatchInlineSnapshot(`
4029
<View
4130
testID="view"
4231
/>
4332
`);
4433

45-
result.update(
34+
renderer.render(
4635
<View testID="view">
4736
<Text>Hello</Text>
4837
</View>,
4938
);
50-
expect(result.toJSON()).toMatchInlineSnapshot(`
39+
expect(renderer.toJSON()).toMatchInlineSnapshot(`
5140
<View
5241
testID="view"
5342
>
@@ -58,46 +47,57 @@ test('implements update()', () => {
5847
`);
5948
});
6049

61-
test('implements unmount()', () => {
62-
const result = render(<View testID="view" />);
63-
expect(result.toJSON()).toMatchInlineSnapshot(`
50+
test('can unmount renderer element', () => {
51+
const renderer = createRenderer();
52+
renderer.render(<View testID="view" />);
53+
expect(renderer.toJSON()).toMatchInlineSnapshot(`
6454
<View
6555
testID="view"
6656
/>
6757
`);
6858

69-
result.unmount();
70-
expect(result.toJSON()).toBeNull();
59+
renderer.unmount();
60+
expect(renderer.toJSON()).toBeNull();
7161
});
7262

73-
test('implements get root()', () => {
74-
const result = render(<View testID="view" />);
75-
expect(result.root).toMatchInlineSnapshot(`
63+
test('returns root view', () => {
64+
const renderer = createRenderer();
65+
renderer.render(<View testID="view" />);
66+
expect(renderer.root).toMatchInlineSnapshot(`
7667
<View
7768
testID="view"
7869
/>
7970
`);
8071
});
8172

82-
test('implements toJSON()', () => {
83-
const result = render(
84-
<View testID="view">
85-
<Text style={{ color: 'blue' }}>Hello</Text>
86-
</View>,
87-
);
88-
expect(result.toJSON()).toMatchInlineSnapshot(`
89-
<View
90-
testID="view"
91-
>
92-
<Text
93-
style={
94-
{
95-
"color": "blue",
96-
}
97-
}
98-
>
99-
Hello
100-
</Text>
101-
</View>
73+
test('returns container view', () => {
74+
const renderer = createRenderer();
75+
renderer.render(<View testID="view" />);
76+
expect(renderer.container).toMatchInlineSnapshot(`
77+
<CONTAINER>
78+
<View
79+
testID="view"
80+
/>
81+
</CONTAINER>
10282
`);
10383
});
84+
85+
test('throws when rendering string outside of Text', () => {
86+
jest.spyOn(console, 'error').mockImplementation(() => {});
87+
88+
expect(() => createRenderer().render(<View>Hello</View>)).toThrowErrorMatchingInlineSnapshot(
89+
`"Invariant Violation: Text strings must be rendered within a <Text> component. Detected attempt to render "Hello" string within a <View> component."`,
90+
);
91+
92+
expect(() =>
93+
createRenderer().render(<Passthrough>Hello</Passthrough>),
94+
).toThrowErrorMatchingInlineSnapshot(
95+
`"Invariant Violation: Text strings must be rendered within a <Text> component. Detected attempt to render "Hello" string within a <ROOT> component."`,
96+
);
97+
98+
expect(() => createRenderer().render(<>Hello</>)).toThrowErrorMatchingInlineSnapshot(
99+
`"Invariant Violation: Text strings must be rendered within a <Text> component. Detected attempt to render "Hello" string within a <ROOT> component."`,
100+
);
101+
102+
jest.restoreAllMocks();
103+
});

0 commit comments

Comments
 (0)