main
js 345 lines 9.95 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 React;
16 let ReactDOMClient;
17 let ReactDOMServer;
18
19 function initModules() {
20 // Reset warning cache.
21 jest.resetModules();
22 React = require('react');
23 ReactDOMClient = require('react-dom/client');
24 ReactDOMServer = require('react-dom/server');
25
26 // Make them available to the helpers.
27 return {
28 ReactDOMClient,
29 ReactDOMServer,
30 };
31 }
32
33 const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
34
35 describe('ReactDOMServerIntegration', () => {
36 beforeEach(() => {
37 resetModules();
38 });
39
40 describe('context', function () {
41 let Context, PurpleContextProvider, RedContextProvider, Consumer;
42 beforeEach(() => {
43 Context = React.createContext('none');
44
45 class Parent extends React.Component {
46 render() {
47 return (
48 <Context.Provider value={this.props.text}>
49 {this.props.children}
50 </Context.Provider>
51 );
52 }
53 }
54 Consumer = Context.Consumer;
55 PurpleContextProvider = props => (
56 <Parent text="purple">{props.children}</Parent>
57 );
58 RedContextProvider = props => (
59 <Parent text="red">{props.children}</Parent>
60 );
61 });
62
63 itRenders('class child with context', async render => {
64 class ClassChildWithContext extends React.Component {
65 render() {
66 return (
67 <div>
68 <Consumer>{text => text}</Consumer>
69 </div>
70 );
71 }
72 }
73
74 const e = await render(
75 <PurpleContextProvider>
76 <ClassChildWithContext />
77 </PurpleContextProvider>,
78 );
79 expect(e.textContent).toBe('purple');
80 });
81
82 itRenders('stateless child with context', async render => {
83 function FunctionChildWithContext(props) {
84 return <Consumer>{text => text}</Consumer>;
85 }
86
87 const e = await render(
88 <PurpleContextProvider>
89 <FunctionChildWithContext />
90 </PurpleContextProvider>,
91 );
92 expect(e.textContent).toBe('purple');
93 });
94
95 itRenders('class child with default context', async render => {
96 class ClassChildWithWrongContext extends React.Component {
97 render() {
98 return (
99 <div id="classWrongChild">
100 <Consumer>{text => text}</Consumer>
101 </div>
102 );
103 }
104 }
105
106 const e = await render(<ClassChildWithWrongContext />);
107 expect(e.textContent).toBe('none');
108 });
109
110 itRenders('stateless child with wrong context', async render => {
111 function FunctionChildWithWrongContext(props) {
112 return (
113 <div id="statelessWrongChild">
114 <Consumer>{text => text}</Consumer>
115 </div>
116 );
117 }
118
119 const e = await render(<FunctionChildWithWrongContext />);
120 expect(e.textContent).toBe('none');
121 });
122
123 itRenders('with context passed through to a grandchild', async render => {
124 function Grandchild(props) {
125 return (
126 <div>
127 <Consumer>{text => text}</Consumer>
128 </div>
129 );
130 }
131
132 const Child = props => <Grandchild />;
133
134 const e = await render(
135 <PurpleContextProvider>
136 <Child />
137 </PurpleContextProvider>,
138 );
139 expect(e.textContent).toBe('purple');
140 });
141
142 itRenders('a child context overriding a parent context', async render => {
143 const Grandchild = props => {
144 return (
145 <div>
146 <Consumer>{text => text}</Consumer>
147 </div>
148 );
149 };
150
151 const e = await render(
152 <PurpleContextProvider>
153 <RedContextProvider>
154 <Grandchild />
155 </RedContextProvider>
156 </PurpleContextProvider>,
157 );
158 expect(e.textContent).toBe('red');
159 });
160
161 itRenders('readContext() in different components', async render => {
162 function readContext(Ctx) {
163 const dispatcher =
164 React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE
165 .H;
166 return dispatcher.readContext(Ctx);
167 }
168
169 class Cls extends React.Component {
170 render() {
171 return readContext(Context);
172 }
173 }
174 function Fn() {
175 return readContext(Context);
176 }
177 const Memo = React.memo(() => {
178 return readContext(Context);
179 });
180 const FwdRef = React.forwardRef((props, ref) => {
181 return readContext(Context);
182 });
183
184 const e = await render(
185 <PurpleContextProvider>
186 <RedContextProvider>
187 <span>
188 <Fn />
189 <Cls />
190 <Memo />
191 <FwdRef />
192 <Consumer>{() => readContext(Context)}</Consumer>
193 </span>
194 </RedContextProvider>
195 </PurpleContextProvider>,
196 );
197 expect(e.textContent).toBe('redredredredred');
198 });
199
200 itRenders('multiple contexts', async render => {
201 const Theme = React.createContext('dark');
202 const Language = React.createContext('french');
203 class Parent extends React.Component {
204 render() {
205 return (
206 <Theme.Provider value="light">
207 <Child />
208 </Theme.Provider>
209 );
210 }
211 }
212
213 function Child() {
214 return (
215 <Language.Provider value="english">
216 <Grandchild />
217 </Language.Provider>
218 );
219 }
220
221 const Grandchild = props => {
222 return (
223 <div>
224 <Theme.Consumer>
225 {theme => <div id="theme">{theme}</div>}
226 </Theme.Consumer>
227 <Language.Consumer>
228 {language => <div id="language">{language}</div>}
229 </Language.Consumer>
230 </div>
231 );
232 };
233
234 const e = await render(<Parent />);
235 expect(e.querySelector('#theme').textContent).toBe('light');
236 expect(e.querySelector('#language').textContent).toBe('english');
237 });
238
239 itRenders('nested context unwinding', async render => {
240 const Theme = React.createContext('dark');
241 const Language = React.createContext('french');
242
243 const App = () => (
244 <div>
245 <Theme.Provider value="light">
246 <Language.Provider value="english">
247 <Theme.Provider value="dark">
248 <Theme.Consumer>
249 {theme => <div id="theme1">{theme}</div>}
250 </Theme.Consumer>
251 </Theme.Provider>
252 <Theme.Consumer>
253 {theme => <div id="theme2">{theme}</div>}
254 </Theme.Consumer>
255 <Language.Provider value="sanskrit">
256 <Theme.Provider value="blue">
257 <Theme.Provider value="red">
258 <Language.Consumer>
259 {() => (
260 <Language.Provider value="chinese">
261 <Language.Provider value="hungarian" />
262 <Language.Consumer>
263 {language => <div id="language1">{language}</div>}
264 </Language.Consumer>
265 </Language.Provider>
266 )}
267 </Language.Consumer>
268 </Theme.Provider>
269 <Language.Consumer>
270 {language => (
271 <>
272 <Theme.Consumer>
273 {theme => <div id="theme3">{theme}</div>}
274 </Theme.Consumer>
275 <div id="language2">{language}</div>
276 </>
277 )}
278 </Language.Consumer>
279 </Theme.Provider>
280 </Language.Provider>
281 </Language.Provider>
282 </Theme.Provider>
283 <Language.Consumer>
284 {language => <div id="language3">{language}</div>}
285 </Language.Consumer>
286 </div>
287 );
288 const e = await render(<App />);
289 expect(e.querySelector('#theme1').textContent).toBe('dark');
290 expect(e.querySelector('#theme2').textContent).toBe('light');
291 expect(e.querySelector('#theme3').textContent).toBe('blue');
292 expect(e.querySelector('#language1').textContent).toBe('chinese');
293 expect(e.querySelector('#language2').textContent).toBe('sanskrit');
294 expect(e.querySelector('#language3').textContent).toBe('french');
295 });
296
297 itRenders('should treat Context as Context.Provider', async render => {
298 const Theme = React.createContext('dark');
299 const Language = React.createContext('french');
300
301 expect(Theme.Provider).toBe(Theme);
302
303 const App = () => (
304 <div>
305 <Theme value="light">
306 <Language value="english">
307 <Theme value="dark">
308 <Theme.Consumer>
309 {theme => <div id="theme1">{theme}</div>}
310 </Theme.Consumer>
311 </Theme>
312 </Language>
313 </Theme>
314 </div>
315 );
316
317 const e = await render(<App />, 0);
318 expect(e.textContent).toBe('dark');
319 });
320
321 it('does not pollute sync renders after an error', () => {
322 const LoggedInUser = React.createContext('default');
323 const Crash = () => {
324 throw new Error('Boo!');
325 };
326 const AppWithUser = user => (
327 <LoggedInUser.Provider value={user}>
328 <LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>
329 <Crash />
330 </LoggedInUser.Provider>
331 );
332
333 expect(() => {
334 ReactDOMServer.renderToString(AppWithUser('Casper'));
335 }).toThrow('Boo');
336
337 // Should not report a value from failed render
338 expect(
339 ReactDOMServer.renderToString(
340 <LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>,
341 ),
342 ).toBe('default');
343 });
344 });
345 });