main
js 279 lines 9.83 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @emails react-core
8 * @jest-environment node
9 */
10
11 'use strict';
12
13 const React = require('react');
14 let ReactTestRenderer;
15 let Context;
16 let act;
17
18 const RCTView = 'RCTView';
19 const View = props => <RCTView {...props} />;
20
21 describe('ReactTestRendererTraversal', () => {
22 beforeEach(() => {
23 jest.resetModules();
24 ReactTestRenderer = require('react-test-renderer');
25 act = require('internal-test-utils').act;
26 Context = React.createContext(null);
27 });
28
29 class Example extends React.Component {
30 render() {
31 return (
32 <View>
33 <View foo="foo">
34 <View bar="bar" />
35 <View bar="bar" baz="baz" itself="itself" />
36 <View />
37 <ExampleSpread bar="bar" />
38 <ExampleFn bar="bar" bing="bing" />
39 <ExampleNull bar="bar" />
40 <ExampleNull null="null">
41 <View void="void" />
42 <View void="void" />
43 </ExampleNull>
44 <React.Profiler id="test" onRender={() => {}}>
45 <ExampleForwardRef qux="qux" />
46 </React.Profiler>
47 <>
48 <>
49 <Context.Provider value={null}>
50 <Context.Consumer>
51 {() => <View nested={true} />}
52 </Context.Consumer>
53 </Context.Provider>
54 </>
55 <View nested={true} />
56 <View nested={true} />
57 </>
58 </View>
59 </View>
60 );
61 }
62 }
63 class ExampleSpread extends React.Component {
64 render = () => <View {...this.props} />;
65 }
66 const ExampleFn = props => <View baz="baz" />;
67 const ExampleNull = props => null;
68
69 const ExampleForwardRef = React.forwardRef((props, ref) => (
70 <View {...props} ref={ref} />
71 ));
72
73 it('initializes', async () => {
74 let render;
75 await act(() => {
76 render = ReactTestRenderer.create(<Example />);
77 });
78 const hasFooProp = node => node.props.hasOwnProperty('foo');
79
80 // assert .props, .type and .parent attributes
81 const foo = render.root.find(hasFooProp);
82 expect(foo.props.children).toHaveLength(9);
83 expect(foo.type).toBe(View);
84 expect(render.root.parent).toBe(null);
85 expect(foo.children[0].parent).toBe(foo);
86 });
87
88 it('searches via .find() / .findAll()', async () => {
89 let render;
90 await act(() => {
91 render = ReactTestRenderer.create(<Example />);
92 });
93
94 const hasFooProp = node => node.props.hasOwnProperty('foo');
95 const hasBarProp = node => node.props.hasOwnProperty('bar');
96 const hasBazProp = node => node.props.hasOwnProperty('baz');
97 const hasBingProp = node => node.props.hasOwnProperty('bing');
98 const hasNullProp = node => node.props.hasOwnProperty('null');
99 const hasVoidProp = node => node.props.hasOwnProperty('void');
100 const hasItselfProp = node => node.props.hasOwnProperty('itself');
101 const hasNestedProp = node => node.props.hasOwnProperty('nested');
102
103 expect(() => render.root.find(hasFooProp)).not.toThrow(); // 1 match
104 expect(() => render.root.find(hasBarProp)).toThrow(); // >1 matches
105 expect(() => render.root.find(hasBazProp)).toThrow(); // >1 matches
106 expect(() => render.root.find(hasBingProp)).not.toThrow(); // 1 match
107 expect(() => render.root.find(hasNullProp)).not.toThrow(); // 1 match
108 expect(() => render.root.find(hasVoidProp)).toThrow(); // 0 matches
109 expect(() => render.root.find(hasNestedProp)).toThrow(); // >1 matches
110
111 // same assertion as .find(), but confirm length
112 expect(render.root.findAll(hasFooProp, {deep: false})).toHaveLength(1);
113 expect(render.root.findAll(hasBarProp, {deep: false})).toHaveLength(5);
114 expect(render.root.findAll(hasBazProp, {deep: false})).toHaveLength(2);
115 expect(render.root.findAll(hasBingProp, {deep: false})).toHaveLength(1);
116 expect(render.root.findAll(hasNullProp, {deep: false})).toHaveLength(1);
117 expect(render.root.findAll(hasVoidProp, {deep: false})).toHaveLength(0);
118 expect(render.root.findAll(hasNestedProp, {deep: false})).toHaveLength(3);
119
120 // note: with {deep: true}, .findAll() will continue to
121 // search children, even after finding a match
122 expect(render.root.findAll(hasFooProp)).toHaveLength(2);
123 expect(render.root.findAll(hasBarProp)).toHaveLength(9);
124 expect(render.root.findAll(hasBazProp)).toHaveLength(4);
125 expect(render.root.findAll(hasBingProp)).toHaveLength(1); // no spread
126 expect(render.root.findAll(hasNullProp)).toHaveLength(1); // no spread
127 expect(render.root.findAll(hasVoidProp)).toHaveLength(0);
128 expect(render.root.findAll(hasNestedProp, {deep: false})).toHaveLength(3);
129
130 const bing = render.root.find(hasBingProp);
131 expect(bing.find(hasBarProp)).toBe(bing);
132 expect(bing.find(hasBingProp)).toBe(bing);
133 expect(bing.findAll(hasBazProp, {deep: false})).toHaveLength(1);
134 expect(bing.findAll(hasBazProp)).toHaveLength(2);
135
136 const foo = render.root.find(hasFooProp);
137 expect(foo.findAll(hasFooProp, {deep: false})).toHaveLength(1);
138 expect(foo.findAll(hasFooProp)).toHaveLength(2);
139
140 const itself = foo.find(hasItselfProp);
141 expect(itself.find(hasBarProp)).toBe(itself);
142 expect(itself.find(hasBazProp)).toBe(itself);
143 expect(itself.findAll(hasBazProp, {deep: false})).toHaveLength(1);
144 expect(itself.findAll(hasBazProp)).toHaveLength(2);
145 });
146
147 it('searches via .findByType() / .findAllByType()', async () => {
148 let render;
149 await act(() => {
150 render = ReactTestRenderer.create(<Example />);
151 });
152
153 expect(() => render.root.findByType(ExampleFn)).not.toThrow(); // 1 match
154 expect(() => render.root.findByType(View)).not.toThrow(); // 1 match
155 expect(() => render.root.findByType(ExampleForwardRef)).not.toThrow(); // 1 match
156 // note: there are clearly multiple <View /> in general, but there
157 // is only one being rendered at root node level
158 expect(() => render.root.findByType(ExampleNull)).toThrow(); // 2 matches
159
160 expect(render.root.findAllByType(ExampleFn)).toHaveLength(1);
161 expect(render.root.findAllByType(View, {deep: false})).toHaveLength(1);
162 expect(render.root.findAllByType(View)).toHaveLength(11);
163 expect(render.root.findAllByType(ExampleNull)).toHaveLength(2);
164 expect(render.root.findAllByType(ExampleForwardRef)).toHaveLength(1);
165
166 const nulls = render.root.findAllByType(ExampleNull);
167 expect(nulls[0].findAllByType(View)).toHaveLength(0);
168 expect(nulls[1].findAllByType(View)).toHaveLength(0);
169
170 const fn = render.root.findAllByType(ExampleFn);
171 expect(fn[0].findAllByType(View)).toHaveLength(1);
172 });
173
174 it('searches via .findByProps() / .findAllByProps()', async () => {
175 let render;
176 await act(() => {
177 render = ReactTestRenderer.create(<Example />);
178 });
179 const foo = 'foo';
180 const bar = 'bar';
181 const baz = 'baz';
182 const qux = 'qux';
183
184 expect(() => render.root.findByProps({foo})).not.toThrow(); // 1 match
185 expect(() => render.root.findByProps({bar})).toThrow(); // >1 matches
186 expect(() => render.root.findByProps({baz})).toThrow(); // >1 matches
187 expect(() => render.root.findByProps({qux})).not.toThrow(); // 1 match
188
189 expect(render.root.findAllByProps({foo}, {deep: false})).toHaveLength(1);
190 expect(render.root.findAllByProps({bar}, {deep: false})).toHaveLength(5);
191 expect(render.root.findAllByProps({baz}, {deep: false})).toHaveLength(2);
192 expect(render.root.findAllByProps({qux}, {deep: false})).toHaveLength(1);
193
194 expect(render.root.findAllByProps({foo})).toHaveLength(2);
195 expect(render.root.findAllByProps({bar})).toHaveLength(9);
196 expect(render.root.findAllByProps({baz})).toHaveLength(4);
197 expect(render.root.findAllByProps({qux})).toHaveLength(3);
198 });
199
200 it('skips special nodes', async () => {
201 let render;
202 await act(() => {
203 render = ReactTestRenderer.create(<Example />);
204 });
205 expect(render.root.findAllByType(React.Fragment)).toHaveLength(0);
206 expect(render.root.findAllByType(Context.Consumer)).toHaveLength(0);
207 expect(render.root.findAllByType(Context.Provider)).toHaveLength(0);
208
209 const expectedParent = render.root.findByProps({foo: 'foo'}, {deep: false})
210 .children[0];
211 const nestedViews = render.root.findAllByProps(
212 {nested: true},
213 {deep: false},
214 );
215 expect(nestedViews.length).toBe(3);
216 expect(nestedViews[0].parent).toBe(expectedParent);
217 expect(nestedViews[1].parent).toBe(expectedParent);
218 expect(nestedViews[2].parent).toBe(expectedParent);
219 });
220
221 it('can have special nodes as roots', async () => {
222 const FR = React.forwardRef((props, ref) => <section {...props} />);
223
224 let render1;
225 await act(() => {
226 render1 = ReactTestRenderer.create(
227 <FR>
228 <div />
229 <div />
230 </FR>,
231 );
232 });
233 expect(render1.root.findAllByType('div').length).toBe(2);
234
235 let render2;
236 await act(() => {
237 render2 = ReactTestRenderer.create(
238 <>
239 <div />
240 <div />
241 </>,
242 );
243 });
244 expect(render2.root.findAllByType('div').length).toBe(2);
245
246 let render3;
247 await act(() => {
248 render3 = ReactTestRenderer.create(
249 <React.Fragment key="foo">
250 <div />
251 <div />
252 </React.Fragment>,
253 );
254 });
255 expect(render3.root.findAllByType('div').length).toBe(2);
256
257 let render4;
258 await act(() => {
259 render4 = ReactTestRenderer.create(
260 <React.StrictMode>
261 <div />
262 <div />
263 </React.StrictMode>,
264 );
265 });
266 expect(render4.root.findAllByType('div').length).toBe(2);
267
268 let render5;
269 await act(() => {
270 render5 = ReactTestRenderer.create(
271 <Context.Provider value={null}>
272 <div />
273 <div />
274 </Context.Provider>,
275 );
276 });
277 expect(render5.root.findAllByType('div').length).toBe(2);
278 });
279 });