main
js 288 lines 7.89 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 PropTypes;
13 let React;
14 let ReactDOMClient;
15 let act;
16 let assertConsoleErrorDev;
17
18 function FunctionComponent(props) {
19 return <div>{props.name}</div>;
20 }
21
22 describe('ReactFunctionComponent', () => {
23 beforeEach(() => {
24 jest.resetModules();
25 PropTypes = require('prop-types');
26 React = require('react');
27 ReactDOMClient = require('react-dom/client');
28 ({act, assertConsoleErrorDev} = require('internal-test-utils'));
29 });
30
31 it('should render stateless component', async () => {
32 const el = document.createElement('div');
33
34 const root = ReactDOMClient.createRoot(el);
35 await act(() => {
36 root.render(<FunctionComponent name="A" />);
37 });
38
39 expect(el.textContent).toBe('A');
40 });
41
42 it('should update stateless component', async () => {
43 class Parent extends React.Component {
44 render() {
45 return <FunctionComponent {...this.props} />;
46 }
47 }
48
49 const el = document.createElement('div');
50
51 const root = ReactDOMClient.createRoot(el);
52 await act(() => {
53 root.render(<Parent name="A" />);
54 });
55 expect(el.textContent).toBe('A');
56
57 await act(() => {
58 root.render(<Parent name="B" />);
59 });
60 expect(el.textContent).toBe('B');
61 });
62
63 it('should unmount stateless component', async () => {
64 const container = document.createElement('div');
65
66 const root = ReactDOMClient.createRoot(container);
67 await act(() => {
68 root.render(<FunctionComponent name="A" />);
69 });
70 expect(container.textContent).toBe('A');
71
72 root.unmount();
73 expect(container.textContent).toBe('');
74 });
75
76 // @gate !disableLegacyContext
77 it('should pass context thru stateless component', async () => {
78 class Child extends React.Component {
79 static contextTypes = {
80 test: PropTypes.string.isRequired,
81 };
82
83 render() {
84 return <div>{this.context.test}</div>;
85 }
86 }
87
88 function Parent() {
89 return <Child />;
90 }
91
92 class GrandParent extends React.Component {
93 static childContextTypes = {
94 test: PropTypes.string.isRequired,
95 };
96
97 getChildContext() {
98 return {test: this.props.test};
99 }
100
101 render() {
102 return <Parent />;
103 }
104 }
105
106 const el = document.createElement('div');
107
108 const root = ReactDOMClient.createRoot(el);
109 await act(() => {
110 root.render(<GrandParent test="test" />);
111 });
112
113 assertConsoleErrorDev([
114 'GrandParent uses the legacy childContextTypes API which will soon be removed. ' +
115 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
116 ' in GrandParent (at **)',
117 'Child uses the legacy contextTypes API which will soon be removed. ' +
118 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
119 ' in Parent (at **)\n' +
120 ' in GrandParent (at **)',
121 ]);
122
123 expect(el.textContent).toBe('test');
124
125 await act(() => {
126 root.render(<GrandParent test="mest" />);
127 });
128
129 expect(el.textContent).toBe('mest');
130 });
131
132 it('should warn for getDerivedStateFromProps on a function component', async () => {
133 function FunctionComponentWithChildContext() {
134 return null;
135 }
136 FunctionComponentWithChildContext.getDerivedStateFromProps = function () {};
137
138 const container = document.createElement('div');
139
140 const root = ReactDOMClient.createRoot(container);
141 await act(() => {
142 root.render(<FunctionComponentWithChildContext />);
143 });
144 assertConsoleErrorDev([
145 'FunctionComponentWithChildContext: Function ' +
146 'components do not support getDerivedStateFromProps.\n' +
147 ' in FunctionComponentWithChildContext (at **)',
148 ]);
149 });
150
151 it('should warn for childContextTypes on a function component', async () => {
152 function FunctionComponentWithChildContext(props) {
153 return <div>{props.name}</div>;
154 }
155
156 FunctionComponentWithChildContext.childContextTypes = {
157 foo: PropTypes.string,
158 };
159
160 const container = document.createElement('div');
161
162 const root = ReactDOMClient.createRoot(container);
163 await act(() => {
164 root.render(<FunctionComponentWithChildContext name="A" />);
165 });
166 assertConsoleErrorDev([
167 'childContextTypes cannot ' +
168 'be defined on a function component.\n' +
169 ' FunctionComponentWithChildContext.childContextTypes = ...\n' +
170 ' in FunctionComponentWithChildContext (at **)',
171 ]);
172 });
173
174 it('should not throw when stateless component returns undefined', async () => {
175 function NotAComponent() {}
176 const container = document.createElement('div');
177 const root = ReactDOMClient.createRoot(container);
178 await expect(
179 act(() => {
180 root.render(
181 <div>
182 <NotAComponent />
183 </div>,
184 );
185 }),
186 ).resolves.not.toThrow();
187 });
188
189 it('should use correct name in key warning', async () => {
190 function Child() {
191 return <div>{[<span />]}</div>;
192 }
193
194 const container = document.createElement('div');
195 const root = ReactDOMClient.createRoot(container);
196 await act(() => {
197 root.render(<Child />);
198 });
199 assertConsoleErrorDev([
200 'Each child in a list should have a unique "key" prop.\n' +
201 '\n' +
202 'Check the render method of `Child`. See https://react.dev/link/warning-keys for more information.\n' +
203 ' in span (at **)\n' +
204 ' in Child (at **)',
205 ]);
206 });
207
208 // @gate !disableLegacyContext && !disableLegacyContextForFunctionComponents
209 it('should receive context', async () => {
210 class Parent extends React.Component {
211 static childContextTypes = {
212 lang: PropTypes.string,
213 };
214
215 getChildContext() {
216 return {lang: 'en'};
217 }
218
219 render() {
220 return <Child />;
221 }
222 }
223
224 function Child(props, context) {
225 return <div>{context.lang}</div>;
226 }
227 Child.contextTypes = {lang: PropTypes.string};
228
229 const el = document.createElement('div');
230
231 const root = ReactDOMClient.createRoot(el);
232 await act(() => {
233 root.render(<Parent />);
234 });
235 assertConsoleErrorDev([
236 'Parent uses the legacy childContextTypes API which will soon be removed. ' +
237 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
238 ' in Parent (at **)',
239 'Child uses the legacy contextTypes API which will be removed soon. ' +
240 'Use React.createContext() with React.useContext() instead. (https://react.dev/link/legacy-context)\n' +
241 ' in Parent (at **)',
242 ]);
243 expect(el.textContent).toBe('en');
244 });
245
246 it('should work with arrow functions', async () => {
247 let Child = function () {
248 return <div />;
249 };
250 // Will create a new bound function without a prototype, much like a native
251 // arrow function.
252 Child = Child.bind(this);
253
254 await expect(async () => {
255 const container = document.createElement('div');
256 const root = ReactDOMClient.createRoot(container);
257 await act(() => {
258 root.render(<Child />);
259 });
260 }).not.toThrow();
261 });
262
263 it('should allow simple functions to return null', async () => {
264 const Child = function () {
265 return null;
266 };
267 await expect(async () => {
268 const container = document.createElement('div');
269 const root = ReactDOMClient.createRoot(container);
270 await act(() => {
271 root.render(<Child />);
272 });
273 }).not.toThrow();
274 });
275
276 it('should allow simple functions to return false', async () => {
277 function Child() {
278 return false;
279 }
280 const container = document.createElement('div');
281 const root = ReactDOMClient.createRoot(container);
282 await expect(
283 act(() => {
284 root.render(<Child />);
285 }),
286 ).resolves.not.toThrow();
287 });
288 });