main
js 307 lines 8.79 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 // TODO: All these warnings should become static errors using Flow instead
13 // of dynamic errors when using JSX with Flow.
14 let act;
15 let React;
16 let ReactDOMClient;
17 let assertConsoleErrorDev;
18
19 describe('ReactJSXElementValidator', () => {
20 let Component;
21 let RequiredPropComponent;
22
23 beforeEach(() => {
24 jest.resetModules();
25
26 ({act, assertConsoleErrorDev} = require('internal-test-utils'));
27 React = require('react');
28 ReactDOMClient = require('react-dom/client');
29
30 Component = class extends React.Component {
31 render() {
32 return <div>{this.props.children}</div>;
33 }
34 };
35
36 RequiredPropComponent = class extends React.Component {
37 render() {
38 return <span>{this.props.prop}</span>;
39 }
40 };
41 RequiredPropComponent.displayName = 'RequiredPropComponent';
42 });
43
44 it('warns for keys for arrays of elements in children position', async () => {
45 const container = document.createElement('div');
46 const root = ReactDOMClient.createRoot(container);
47
48 await act(() => {
49 root.render(<Component>{[<Component />, <Component />]}</Component>);
50 });
51 assertConsoleErrorDev([
52 'Each child in a list should have a unique "key" prop.\n\n' +
53 'Check the render method of `Component`. See https://react.dev/link/warning-keys for more information.\n' +
54 ' in Component (at **)',
55 ]);
56 });
57
58 it('warns for keys for arrays of elements with owner info', async () => {
59 class InnerComponent extends React.Component {
60 render() {
61 return <Component>{this.props.childSet}</Component>;
62 }
63 }
64
65 class ComponentWrapper extends React.Component {
66 render() {
67 return <InnerComponent childSet={[<Component />, <Component />]} />;
68 }
69 }
70
71 const container = document.createElement('div');
72 const root = ReactDOMClient.createRoot(container);
73 await act(() => {
74 root.render(<ComponentWrapper />);
75 });
76 assertConsoleErrorDev([
77 'Each child in a list should have a unique "key" prop.' +
78 '\n\nCheck the render method of `Component`. ' +
79 'It was passed a child from ComponentWrapper. See https://react.dev/link/warning-keys for more information.\n' +
80 ' in ComponentWrapper (at **)',
81 ]);
82 });
83
84 it('warns for keys for iterables of elements in rest args', async () => {
85 const iterable = {
86 '@@iterator': function () {
87 let i = 0;
88 return {
89 next: function () {
90 const done = ++i > 2;
91 return {value: done ? undefined : <Component />, done: done};
92 },
93 };
94 },
95 };
96
97 const container = document.createElement('div');
98 const root = ReactDOMClient.createRoot(container);
99
100 await act(() => {
101 root.render(<Component>{iterable}</Component>);
102 });
103 assertConsoleErrorDev([
104 'Each child in a list should have a unique "key" prop.\n\n' +
105 'Check the render method of `Component`. It was passed a child from div. ' +
106 'See https://react.dev/link/warning-keys for more information.\n' +
107 ' in Component (at **)',
108 ]);
109 });
110
111 it('does not warn for arrays of elements with keys', async () => {
112 const container = document.createElement('div');
113 const root = ReactDOMClient.createRoot(container);
114
115 await act(() => {
116 root.render(
117 <Component>
118 {[<Component key="#1" />, <Component key="#2" />]}
119 </Component>,
120 );
121 });
122 });
123
124 it('does not warn for iterable elements with keys', async () => {
125 const iterable = {
126 '@@iterator': function () {
127 let i = 0;
128 return {
129 next: function () {
130 const done = ++i > 2;
131 return {
132 value: done ? undefined : <Component key={'#' + i} />,
133 done: done,
134 };
135 },
136 };
137 },
138 };
139
140 const container = document.createElement('div');
141 const root = ReactDOMClient.createRoot(container);
142
143 await act(() => {
144 root.render(<Component>{iterable}</Component>);
145 });
146 });
147
148 it('does not warn for numeric keys in entry iterable as a child', async () => {
149 const iterable = {
150 '@@iterator': function () {
151 let i = 0;
152 return {
153 next: function () {
154 const done = ++i > 2;
155 return {value: done ? undefined : [i, <Component />], done: done};
156 },
157 };
158 },
159 };
160 iterable.entries = iterable['@@iterator'];
161
162 // This only applies to the warning during construction.
163 // We do warn if it's actually rendered.
164 <Component>{iterable}</Component>;
165 });
166
167 it('does not warn when the element is directly as children', async () => {
168 const container = document.createElement('div');
169 const root = ReactDOMClient.createRoot(container);
170 await act(() => {
171 root.render(
172 <Component>
173 <Component />
174 <Component />
175 </Component>,
176 );
177 });
178 });
179
180 it('does not warn when the child array contains non-elements', () => {
181 void (<Component>{[{}, {}]}</Component>);
182 });
183
184 it('should give context for errors in nested components.', async () => {
185 class MyComp extends React.Component {
186 render() {
187 return [<div />];
188 }
189 }
190 class ParentComp extends React.Component {
191 render() {
192 return <MyComp />;
193 }
194 }
195 const container = document.createElement('div');
196 const root = ReactDOMClient.createRoot(container);
197
198 await act(() => {
199 root.render(<ParentComp />);
200 });
201 assertConsoleErrorDev([
202 'Each child in a list should have a unique "key" prop.' +
203 '\n\nCheck the render method of `ParentComp`. It was passed a child from MyComp. ' +
204 'See https://react.dev/link/warning-keys for more information.\n' +
205 ' in div (at **)\n' +
206 ' in MyComp (at **)\n' +
207 ' in ParentComp (at **)',
208 ]);
209 });
210
211 it('warns for fragments with illegal attributes', async () => {
212 class Foo extends React.Component {
213 render() {
214 return <React.Fragment a={1}>hello</React.Fragment>;
215 }
216 }
217
218 const container = document.createElement('div');
219 const root = ReactDOMClient.createRoot(container);
220 await act(() => {
221 root.render(<Foo />);
222 });
223 assertConsoleErrorDev([
224 gate('enableFragmentRefs')
225 ? 'Invalid prop `a` supplied to `React.Fragment`. React.Fragment ' +
226 'can only have `key`, `ref`, and `children` props.\n' +
227 ' in Foo (at **)'
228 : 'Invalid prop `a` supplied to `React.Fragment`. React.Fragment ' +
229 'can only have `key` and `children` props.\n' +
230 ' in Foo (at **)',
231 ]);
232 });
233
234 it('warns for fragments with refs', async () => {
235 class Foo extends React.Component {
236 render() {
237 return (
238 <React.Fragment
239 ref={bar => {
240 this.foo = bar;
241 }}>
242 hello
243 </React.Fragment>
244 );
245 }
246 }
247
248 const container = document.createElement('div');
249 const root = ReactDOMClient.createRoot(container);
250 await act(() => {
251 root.render(<Foo />);
252 });
253 assertConsoleErrorDev(
254 gate('enableFragmentRefs')
255 ? []
256 : [
257 'Invalid prop `ref` supplied to `React.Fragment`.' +
258 ' React.Fragment can only have `key` and `children` props.\n' +
259 ' in Foo (at **)',
260 ],
261 );
262 });
263
264 it('does not warn for fragments of multiple elements without keys', async () => {
265 const container = document.createElement('div');
266 const root = ReactDOMClient.createRoot(container);
267 await act(() => {
268 root.render(
269 <>
270 <span>1</span>
271 <span>2</span>
272 </>,
273 );
274 });
275 });
276
277 it('warns for fragments of multiple elements with same key', async () => {
278 const container = document.createElement('div');
279 const root = ReactDOMClient.createRoot(container);
280 await act(() => {
281 root.render(
282 <>
283 <span key="a">1</span>
284 <span key="a">2</span>
285 <span key="b">3</span>
286 </>,
287 );
288 });
289 assertConsoleErrorDev([
290 'Encountered two children with the same key, `a`. ' +
291 'Keys should be unique so that components maintain their identity across updates. ' +
292 'Non-unique keys may cause children to be duplicated and/or omitted — ' +
293 'the behavior is unsupported and could change in a future version.\n' +
294 ' in span (at **)',
295 ]);
296 });
297
298 it('does not call lazy initializers eagerly', () => {
299 let didCall = false;
300 const Lazy = React.lazy(() => {
301 didCall = true;
302 return {then() {}};
303 });
304 <Lazy />;
305 expect(didCall).toBe(false);
306 });
307 });