main
js 363 lines 10.3 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 ./scripts/jest/ReactDOMServerIntegrationEnvironment
9 */
10
11 'use strict';
12
13 const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
14
15 let PropTypes;
16 let React;
17 let ReactDOMClient;
18 let ReactDOMServer;
19 let assertConsoleErrorDev;
20
21 function initModules() {
22 // Reset warning cache.
23 jest.resetModules();
24 PropTypes = require('prop-types');
25 React = require('react');
26 ReactDOMClient = require('react-dom/client');
27 ReactDOMServer = require('react-dom/server');
28 assertConsoleErrorDev = require('internal-test-utils').assertConsoleErrorDev;
29
30 // Make them available to the helpers.
31 return {
32 ReactDOMClient,
33 ReactDOMServer,
34 };
35 }
36
37 const {
38 resetModules,
39 itRenders,
40 itThrowsWhenRendering,
41 clientRenderOnBadMarkup,
42 } = ReactDOMServerIntegrationUtils(initModules);
43
44 describe('ReactDOMServerIntegration', () => {
45 beforeEach(() => {
46 resetModules();
47 });
48 afterEach(() => {
49 // TODO: This is a hack because expectErrors does not restore mock,
50 // however fixing it requires a major refactor to all these tests.
51 if (console.error.mockClear) {
52 console.error.mockRestore();
53 }
54 });
55
56 describe('legacy context', function () {
57 // The `itRenders` test abstraction doesn't work with @gate so we have
58 // to do this instead.
59 if (gate(flags => flags.disableLegacyContext)) {
60 it('empty test to stop Jest from being a complainy complainer', () => {});
61 return;
62 }
63
64 let PurpleContext, RedContext;
65 beforeEach(() => {
66 class Parent extends React.Component {
67 getChildContext() {
68 return {text: this.props.text};
69 }
70 render() {
71 return this.props.children;
72 }
73 }
74 Parent.childContextTypes = {text: PropTypes.string};
75
76 PurpleContext = props => <Parent text="purple">{props.children}</Parent>;
77 RedContext = props => <Parent text="red">{props.children}</Parent>;
78 });
79
80 itRenders('class child with context', async render => {
81 class ClassChildWithContext extends React.Component {
82 render() {
83 return <div>{this.context.text}</div>;
84 }
85 }
86 ClassChildWithContext.contextTypes = {text: PropTypes.string};
87
88 const e = await render(
89 <PurpleContext>
90 <ClassChildWithContext />
91 </PurpleContext>,
92 2,
93 );
94 expect(e.textContent).toBe('purple');
95 });
96
97 itRenders('stateless child with context', async render => {
98 if (gate(flags => flags.disableLegacyContextForFunctionComponents)) {
99 return;
100 }
101 function FunctionChildWithContext(props, context) {
102 return <div>{context.text}</div>;
103 }
104 FunctionChildWithContext.contextTypes = {text: PropTypes.string};
105
106 const e = await render(
107 <PurpleContext>
108 <FunctionChildWithContext />
109 </PurpleContext>,
110 2,
111 );
112 expect(e.textContent).toBe('purple');
113 });
114
115 itRenders('class child without context', async render => {
116 class ClassChildWithoutContext extends React.Component {
117 render() {
118 // this should render blank; context isn't passed to this component.
119 return <div>{this.context.text}</div>;
120 }
121 }
122
123 const e = await render(
124 <PurpleContext>
125 <ClassChildWithoutContext />
126 </PurpleContext>,
127 1,
128 );
129 expect(e.textContent).toBe('');
130 });
131
132 itRenders('stateless child without context', async render => {
133 if (gate(flags => flags.disableLegacyContextForFunctionComponents)) {
134 return;
135 }
136 function FunctionChildWithoutContext(props, context) {
137 // this should render blank; context isn't passed to this component.
138 return <div>{context.text}</div>;
139 }
140
141 const e = await render(
142 <PurpleContext>
143 <FunctionChildWithoutContext />
144 </PurpleContext>,
145 1,
146 );
147 expect(e.textContent).toBe('');
148 });
149
150 itRenders('class child with wrong context', async render => {
151 class ClassChildWithWrongContext extends React.Component {
152 render() {
153 // this should render blank; context.text isn't passed to this component.
154 return <div id="classWrongChild">{this.context.text}</div>;
155 }
156 }
157 ClassChildWithWrongContext.contextTypes = {foo: PropTypes.string};
158
159 const e = await render(
160 <PurpleContext>
161 <ClassChildWithWrongContext />
162 </PurpleContext>,
163 2,
164 );
165 expect(e.textContent).toBe('');
166 });
167
168 itRenders('stateless child with wrong context', async render => {
169 if (gate(flags => flags.disableLegacyContextForFunctionComponents)) {
170 return;
171 }
172 function FunctionChildWithWrongContext(props, context) {
173 // this should render blank; context.text isn't passed to this component.
174 return <div id="statelessWrongChild">{context.text}</div>;
175 }
176 FunctionChildWithWrongContext.contextTypes = {
177 foo: PropTypes.string,
178 };
179
180 const e = await render(
181 <PurpleContext>
182 <FunctionChildWithWrongContext />
183 </PurpleContext>,
184 2,
185 );
186 expect(e.textContent).toBe('');
187 });
188
189 itRenders('with context passed through to a grandchild', async render => {
190 if (gate(flags => flags.disableLegacyContextForFunctionComponents)) {
191 return;
192 }
193 function Grandchild(props, context) {
194 return <div>{context.text}</div>;
195 }
196 Grandchild.contextTypes = {text: PropTypes.string};
197
198 const Child = props => <Grandchild />;
199
200 const e = await render(
201 <PurpleContext>
202 <Child />
203 </PurpleContext>,
204 2,
205 );
206 expect(e.textContent).toBe('purple');
207 });
208
209 itRenders('a child context overriding a parent context', async render => {
210 if (gate(flags => flags.disableLegacyContextForFunctionComponents)) {
211 return;
212 }
213 const Grandchild = (props, context) => {
214 return <div>{context.text}</div>;
215 };
216 Grandchild.contextTypes = {text: PropTypes.string};
217
218 const e = await render(
219 <PurpleContext>
220 <RedContext>
221 <Grandchild />
222 </RedContext>
223 </PurpleContext>,
224 2,
225 );
226 expect(e.textContent).toBe('red');
227 });
228
229 itRenders('a child context merged with a parent context', async render => {
230 if (gate(flags => flags.disableLegacyContextForFunctionComponents)) {
231 return;
232 }
233 class Parent extends React.Component {
234 getChildContext() {
235 return {text1: 'purple'};
236 }
237 render() {
238 return <Child />;
239 }
240 }
241 Parent.childContextTypes = {text1: PropTypes.string};
242
243 class Child extends React.Component {
244 getChildContext() {
245 return {text2: 'red'};
246 }
247 render() {
248 return <Grandchild />;
249 }
250 }
251 Child.childContextTypes = {text2: PropTypes.string};
252
253 const Grandchild = (props, context) => {
254 return (
255 <div>
256 <div id="first">{context.text1}</div>
257 <div id="second">{context.text2}</div>
258 </div>
259 );
260 };
261 Grandchild.contextTypes = {
262 text1: PropTypes.string,
263 text2: PropTypes.string,
264 };
265
266 const e = await render(<Parent />, 3);
267 expect(e.querySelector('#first').textContent).toBe('purple');
268 expect(e.querySelector('#second').textContent).toBe('red');
269 });
270
271 itRenders(
272 'with a call to componentWillMount before getChildContext',
273 async render => {
274 if (gate(flags => flags.disableLegacyContextForFunctionComponents)) {
275 return;
276 }
277 class WillMountContext extends React.Component {
278 getChildContext() {
279 return {text: this.state.text};
280 }
281 UNSAFE_componentWillMount() {
282 this.setState({text: 'foo'});
283 }
284 render() {
285 return <Child />;
286 }
287 }
288 WillMountContext.childContextTypes = {text: PropTypes.string};
289
290 const Child = (props, context) => {
291 return <div>{context.text}</div>;
292 };
293 Child.contextTypes = {text: PropTypes.string};
294
295 const e = await render(<WillMountContext />, 2);
296 expect(e.textContent).toBe('foo');
297 },
298 );
299
300 itRenders(
301 'if getChildContext exists but childContextTypes is missing with a warning',
302 async render => {
303 if (gate(flags => flags.disableLegacyContextForFunctionComponents)) {
304 return;
305 }
306 function HopefulChild(props, context) {
307 return context.foo || 'nope';
308 }
309 HopefulChild.contextTypes = {
310 foo: PropTypes.string,
311 };
312 class ForgetfulParent extends React.Component {
313 render() {
314 return <HopefulChild />;
315 }
316 getChildContext() {
317 return {foo: 'bar'};
318 }
319 }
320 const e = await render(
321 <ForgetfulParent />,
322 // Some warning is not de-duped and logged again on the client retry render.
323 render === clientRenderOnBadMarkup ? 3 : 2,
324 );
325 expect(e.textContent).toBe('nope');
326 },
327 );
328
329 itThrowsWhenRendering(
330 'if getChildContext returns a value not in childContextTypes',
331 render => {
332 class MyComponent extends React.Component {
333 render() {
334 return <div />;
335 }
336 getChildContext() {
337 return {value1: 'foo', value2: 'bar'};
338 }
339 }
340 MyComponent.childContextTypes = {value1: PropTypes.string};
341 return render(<MyComponent />);
342 },
343 'MyComponent.getChildContext(): key "value2" is not defined in childContextTypes.',
344 );
345
346 it('warns when childContextTypes is not defined', () => {
347 class MyComponent extends React.Component {
348 render() {
349 return <div />;
350 }
351 getChildContext() {
352 return {value1: 'foo', value2: 'bar'};
353 }
354 }
355
356 ReactDOMServer.renderToString(<MyComponent />);
357 assertConsoleErrorDev([
358 'MyComponent.getChildContext(): childContextTypes must be defined in order to use getChildContext().\n' +
359 ' in MyComponent (at **)',
360 ]);
361 });
362 });
363 });