main
js 245 lines 7.15 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 */
9
10 'use strict';
11
12 let React;
13 let ReactDOMClient;
14 let ReactDOMServer;
15 let ReactFeatureFlags;
16 let act;
17 let assertConsoleErrorDev;
18
19 describe('ReactLegacyContextDisabled', () => {
20 beforeEach(() => {
21 jest.resetModules();
22
23 React = require('react');
24 ReactDOMClient = require('react-dom/client');
25 ReactDOMServer = require('react-dom/server');
26 ReactFeatureFlags = require('shared/ReactFeatureFlags');
27 ReactFeatureFlags.disableLegacyContext = true;
28 act = require('internal-test-utils').act;
29 assertConsoleErrorDev =
30 require('internal-test-utils').assertConsoleErrorDev;
31 });
32
33 function formatValue(val) {
34 if (val === null) {
35 return 'null';
36 }
37 if (val === undefined) {
38 return 'undefined';
39 }
40 if (typeof val === 'string') {
41 return val;
42 }
43 return JSON.stringify(val);
44 }
45
46 it('warns for legacy context', async () => {
47 class LegacyProvider extends React.Component {
48 static childContextTypes = {
49 foo() {},
50 };
51 getChildContext() {
52 return {foo: 10};
53 }
54 render() {
55 return this.props.children;
56 }
57 }
58
59 const lifecycleContextLog = [];
60 class LegacyClsConsumer extends React.Component {
61 static contextTypes = {
62 foo() {},
63 };
64 shouldComponentUpdate(nextProps, nextState, nextContext) {
65 lifecycleContextLog.push(nextContext);
66 return true;
67 }
68 UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
69 lifecycleContextLog.push(nextContext);
70 }
71 UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
72 lifecycleContextLog.push(nextContext);
73 }
74 render() {
75 return formatValue(this.context);
76 }
77 }
78
79 function LegacyFnConsumer(props, context) {
80 return formatValue(context);
81 }
82 LegacyFnConsumer.contextTypes = {foo() {}};
83
84 function RegularFn(props, context) {
85 return formatValue(context);
86 }
87
88 const container = document.createElement('div');
89 const root = ReactDOMClient.createRoot(container);
90 await act(() => {
91 root.render(
92 <LegacyProvider>
93 <span>
94 <LegacyClsConsumer />
95 <LegacyFnConsumer />
96 <RegularFn />
97 </span>
98 </LegacyProvider>,
99 );
100 });
101 assertConsoleErrorDev([
102 'LegacyProvider uses the legacy childContextTypes API which was removed in React 19. ' +
103 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
104 ' in LegacyProvider (at **)',
105 'LegacyClsConsumer uses the legacy contextTypes API which was removed in React 19. ' +
106 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
107 ' in LegacyClsConsumer (at **)',
108 'LegacyFnConsumer uses the legacy contextTypes API which was removed in React 19. ' +
109 'Use React.createContext() with React.useContext() instead. (https://react.dev/link/legacy-context)\n' +
110 ' in LegacyFnConsumer (at **)',
111 ]);
112 expect(container.textContent).toBe('{}undefinedundefined');
113 expect(lifecycleContextLog).toEqual([]);
114
115 // Test update path.
116 await act(() => {
117 root.render(
118 <LegacyProvider>
119 <span>
120 <LegacyClsConsumer />
121 <LegacyFnConsumer />
122 <RegularFn />
123 </span>
124 </LegacyProvider>,
125 );
126 });
127 expect(container.textContent).toBe('{}undefinedundefined');
128 expect(lifecycleContextLog).toEqual([{}, {}, {}]);
129 root.unmount();
130
131 // test server path.
132 const text = ReactDOMServer.renderToString(
133 <LegacyProvider>
134 <span>
135 <LegacyClsConsumer />
136 <LegacyFnConsumer />
137 <RegularFn />
138 </span>
139 </LegacyProvider>,
140 container,
141 );
142 assertConsoleErrorDev([
143 'LegacyProvider uses the legacy childContextTypes API which was removed in React 19. ' +
144 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
145 ' in LegacyProvider (at **)',
146 'LegacyClsConsumer uses the legacy contextTypes API which was removed in React 19. ' +
147 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
148 ' in LegacyClsConsumer (at **)',
149 'LegacyFnConsumer uses the legacy contextTypes API which was removed in React 19. ' +
150 'Use React.createContext() with React.useContext() instead. (https://react.dev/link/legacy-context)\n' +
151 ' in LegacyFnConsumer (at **)',
152 ]);
153 expect(text).toBe('<span>{}<!-- -->undefined<!-- -->undefined</span>');
154 expect(lifecycleContextLog).toEqual([{}, {}, {}]);
155 });
156
157 it('renders a tree with modern context', async () => {
158 const Ctx = React.createContext();
159
160 class Provider extends React.Component {
161 render() {
162 return (
163 <Ctx.Provider value={this.props.value}>
164 {this.props.children}
165 </Ctx.Provider>
166 );
167 }
168 }
169
170 class RenderPropConsumer extends React.Component {
171 render() {
172 return <Ctx.Consumer>{value => formatValue(value)}</Ctx.Consumer>;
173 }
174 }
175
176 const lifecycleContextLog = [];
177 class ContextTypeConsumer extends React.Component {
178 static contextType = Ctx;
179 shouldComponentUpdate(nextProps, nextState, nextContext) {
180 lifecycleContextLog.push(nextContext);
181 return true;
182 }
183 UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
184 lifecycleContextLog.push(nextContext);
185 }
186 UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
187 lifecycleContextLog.push(nextContext);
188 }
189 render() {
190 return formatValue(this.context);
191 }
192 }
193
194 function FnConsumer() {
195 return formatValue(React.useContext(Ctx));
196 }
197
198 const container = document.createElement('div');
199 const root = ReactDOMClient.createRoot(container);
200 await act(() => {
201 root.render(
202 <Provider value="a">
203 <span>
204 <RenderPropConsumer />
205 <ContextTypeConsumer />
206 <FnConsumer />
207 </span>
208 </Provider>,
209 );
210 });
211 expect(container.textContent).toBe('aaa');
212 expect(lifecycleContextLog).toEqual([]);
213
214 // Test update path
215 await act(() => {
216 root.render(
217 <Provider value="a">
218 <span>
219 <RenderPropConsumer />
220 <ContextTypeConsumer />
221 <FnConsumer />
222 </span>
223 </Provider>,
224 );
225 });
226 expect(container.textContent).toBe('aaa');
227 expect(lifecycleContextLog).toEqual(['a', 'a', 'a']);
228 lifecycleContextLog.length = 0;
229
230 await act(() => {
231 root.render(
232 <Provider value="b">
233 <span>
234 <RenderPropConsumer />
235 <ContextTypeConsumer />
236 <FnConsumer />
237 </span>
238 </Provider>,
239 );
240 });
241 expect(container.textContent).toBe('bbb');
242 expect(lifecycleContextLog).toEqual(['b', 'b', 'b']);
243 root.unmount();
244 });
245 });