30
act = require('internal-test-utils').act;
31
ComponentClass = class extends React.Component {
32
render() {
33
- return React.createElement('div');
33
+ return React.createElement('div', null, this.props.children);
34
}
35
};
36
});
37
38
- it('warns for keys for arrays of elements in rest args', () => {
39
- expect(() => {
40
- React.createElement(ComponentClass, null, [
41
- React.createElement(ComponentClass),
42
- React.createElement(ComponentClass),
43
- ]);
38
+ it('warns for keys for arrays of elements in rest args', async () => {
39
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
40
+ await expect(async () => {
41
+ await act(() =>
42
+ root.render(
43
+ React.createElement(ComponentClass, null, [
44
+ React.createElement(ComponentClass),
45
+ React.createElement(ComponentClass),
46
+ ]),
47
+ ),
48
+ );
49
}).toErrorDev('Each child in a list should have a unique "key" prop.');
50
});
51
72
await act(() => root.render(React.createElement(ComponentWrapper)));
73
}).toErrorDev(
74
'Each child in a list should have a unique "key" prop.' +
70
- '\n\nCheck the render method of `InnerClass`. ' +
75
+ '\n\nCheck the render method of `' +
76
+ (gate(flags => flags.enableOwnerStacks)
77
+ ? 'ComponentClass'
78
+ : 'InnerClass') +
79
+ '`. ' +
80
'It was passed a child from ComponentWrapper. ',
81
);
82
});
83
84
it('warns for keys for arrays with no owner or parent info', async () => {
76
- function Anonymous() {
77
- return <div />;
85
+ function Anonymous({children}) {
86
+ return <div>{children}</div>;
87
}
88
Object.defineProperty(Anonymous, 'name', {value: undefined});
89
93
const root = ReactDOMClient.createRoot(document.createElement('div'));
94
await act(() => root.render(<Anonymous>{divs}</Anonymous>));
95
}).toErrorDev(
87
- 'Warning: Each child in a list should have a unique ' +
88
- '"key" prop. See https://react.dev/link/warning-keys for more information.\n' +
89
- ' in div (at **)',
96
+ gate(flags => flags.enableOwnerStacks)
97
+ ? // For owner stacks the parent being validated is the div.
98
+ 'Warning: Each child in a list should have a unique ' +
99
+ '"key" prop.' +
100
+ '\n\nCheck the top-level render call using <div>. ' +
101
+ 'See https://react.dev/link/warning-keys for more information.\n' +
102
+ ' in div (at **)'
103
+ : 'Warning: Each child in a list should have a unique ' +
104
+ '"key" prop. See https://react.dev/link/warning-keys for more information.\n' +
105
+ ' in div (at **)',
106
);
107
});
108
142
'"key" prop.\n\nCheck the render method of `Component`. See ' +
143
'https://react.dev/link/warning-keys for more information.\n' +
144
' in div (at **)\n' +
145
+ // TODO: Because this validates after the div has been mounted, it is part of
146
+ // the parent stack but since owner stacks will switch to owners this goes away again.
147
+ (gate(flags => flags.enableOwnerStacks) ? ' in div (at **)\n' : '') +
148
' in Component (at **)\n' +
149
' in Parent (at **)\n' +
150
' in GrandParent (at **)',
172
);
173
});
174
156
- it('warns for keys for iterables of elements in rest args', () => {
175
+ it('warns for keys for iterables of elements in rest args', async () => {
176
const iterable = {
177
'@@iterator': function () {
178
let i = 0;
188
},
189
};
190
172
- expect(() =>
173
- React.createElement(ComponentClass, null, iterable),
174
- ).toErrorDev('Each child in a list should have a unique "key" prop.');
191
+ await expect(async () => {
192
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
193
+ await act(() =>
194
+ root.render(React.createElement(ComponentClass, null, iterable)),
195
+ );
196
+ }).toErrorDev(
197
+ gate(flag => flag.enableOwnerStacks)
198
+ ? 'Each child in a list should have a unique "key" prop.'
199
+ : // Since each pass generates a new element, it doesn't get marked as
200
+ // validated and it gets rechecked each time.
201
+
202
+ [
203
+ 'Each child in a list should have a unique "key" prop.',
204
+ 'Each child in a list should have a unique "key" prop.',
205
+ 'Each child in a list should have a unique "key" prop.',
206
+ ],
207
+ );
208
});
209
210
it('does not warns for arrays of elements with keys', () => {
259
const root = ReactDOMClient.createRoot(document.createElement('div'));
260
await act(() => root.render(React.createElement(ParentComp)));
261
}).toErrorDev(
229
- 'Each child in a list should have a unique "key" prop. ' +
262
+ 'Each child in a list should have a unique "key" prop.' +
263
+ '\n\nCheck the render method of `ParentComp`. It was passed a child from MyComp. ' +
264
'See https://react.dev/link/warning-keys for more information.\n' +
265
+ // TODO: Because this validates after the div has been mounted, it is part of
266
+ // the parent stack but since owner stacks will switch to owners this goes away again.
267
+ ' in div (at **)\n' +
268
' in MyComp (at **)\n' +
269
' in ParentComp (at **)',
270
);
271
});
272
236
- it('gives a helpful error when passing invalid types', () => {
273
+ it('gives a helpful error when passing invalid types', async () => {
274
function Foo() {}
238
- expect(() => {
239
- React.createElement(undefined);
240
- React.createElement(null);
241
- React.createElement(true);
242
- React.createElement({x: 17});
243
- React.createElement({});
244
- React.createElement(React.createElement('div'));
245
- React.createElement(React.createElement(Foo));
246
- React.createElement(React.createElement(React.createContext().Consumer));
247
- React.createElement({$$typeof: 'non-react-thing'});
275
+ const errors = [];
276
+ await expect(async () => {
277
+ const root = ReactDOMClient.createRoot(document.createElement('div'), {
278
+ onUncaughtError(error) {
279
+ errors.push(error.message);
280
+ },
281
+ });
282
+ const cases = [
283
+ React.createElement(undefined),
284
+ React.createElement(null),
285
+ React.createElement(true),
286
+ React.createElement({x: 17}),
287
+ React.createElement({}),
288
+ React.createElement(React.createElement('div')),
289
+ React.createElement(React.createElement(Foo)),
290
+ React.createElement(
291
+ React.createElement(React.createContext().Consumer),
292
+ ),
293
+ React.createElement({$$typeof: 'non-react-thing'}),
294
+ ];
295
+ for (let i = 0; i < cases.length; i++) {
296
+ await act(() => root.render(cases[i]));
297
+ }
298
}).toErrorDev(
249
- [
250
- 'Warning: React.createElement: type is invalid -- expected a string ' +
251
- '(for built-in components) or a class/function (for composite ' +
252
- 'components) but got: undefined. You likely forgot to export your ' +
253
- "component from the file it's defined in, or you might have mixed up " +
254
- 'default and named imports.',
255
- 'Warning: React.createElement: type is invalid -- expected a string ' +
256
- '(for built-in components) or a class/function (for composite ' +
257
- 'components) but got: null.',
258
- 'Warning: React.createElement: type is invalid -- expected a string ' +
259
- '(for built-in components) or a class/function (for composite ' +
260
- 'components) but got: boolean.',
261
- 'Warning: React.createElement: type is invalid -- expected a string ' +
262
- '(for built-in components) or a class/function (for composite ' +
263
- 'components) but got: object.',
264
- 'Warning: React.createElement: type is invalid -- expected a string ' +
265
- '(for built-in components) or a class/function (for composite ' +
266
- 'components) but got: object. You likely forgot to export your ' +
267
- "component from the file it's defined in, or you might have mixed up " +
268
- 'default and named imports.',
269
- 'Warning: React.createElement: type is invalid -- expected a string ' +
270
- '(for built-in components) or a class/function (for composite ' +
271
- 'components) but got: <div />. Did you accidentally export a JSX literal ' +
272
- 'instead of a component?',
273
- 'Warning: React.createElement: type is invalid -- expected a string ' +
274
- '(for built-in components) or a class/function (for composite ' +
275
- 'components) but got: <Foo />. Did you accidentally export a JSX literal ' +
276
- 'instead of a component?',
277
- 'Warning: React.createElement: type is invalid -- expected a string ' +
278
- '(for built-in components) or a class/function (for composite ' +
279
- 'components) but got: <Context.Consumer />. Did you accidentally ' +
280
- 'export a JSX literal instead of a component?',
281
- 'Warning: React.createElement: type is invalid -- expected a string ' +
282
- '(for built-in components) or a class/function (for composite ' +
283
- 'components) but got: object.',
284
- ],
299
+ gate(flag => flag.enableOwnerStacks)
300
+ ? // We don't need these extra warnings because we already have the errors.
301
+ []
302
+ : [
303
+ 'Warning: React.createElement: type is invalid -- expected a string ' +
304
+ '(for built-in components) or a class/function (for composite ' +
305
+ 'components) but got: undefined. You likely forgot to export your ' +
306
+ "component from the file it's defined in, or you might have mixed up " +
307
+ 'default and named imports.',
308
+ 'Warning: React.createElement: type is invalid -- expected a string ' +
309
+ '(for built-in components) or a class/function (for composite ' +
310
+ 'components) but got: null.',
311
+ 'Warning: React.createElement: type is invalid -- expected a string ' +
312
+ '(for built-in components) or a class/function (for composite ' +
313
+ 'components) but got: boolean.',
314
+ 'Warning: React.createElement: type is invalid -- expected a string ' +
315
+ '(for built-in components) or a class/function (for composite ' +
316
+ 'components) but got: object.',
317
+ 'Warning: React.createElement: type is invalid -- expected a string ' +
318
+ '(for built-in components) or a class/function (for composite ' +
319
+ 'components) but got: object. You likely forgot to export your ' +
320
+ "component from the file it's defined in, or you might have mixed up " +
321
+ 'default and named imports.',
322
+ 'Warning: React.createElement: type is invalid -- expected a string ' +
323
+ '(for built-in components) or a class/function (for composite ' +
324
+ 'components) but got: <div />. Did you accidentally export a JSX literal ' +
325
+ 'instead of a component?',
326
+ 'Warning: React.createElement: type is invalid -- expected a string ' +
327
+ '(for built-in components) or a class/function (for composite ' +
328
+ 'components) but got: <Foo />. Did you accidentally export a JSX literal ' +
329
+ 'instead of a component?',
330
+ 'Warning: React.createElement: type is invalid -- expected a string ' +
331
+ '(for built-in components) or a class/function (for composite ' +
332
+ 'components) but got: <Context.Consumer />. Did you accidentally ' +
333
+ 'export a JSX literal instead of a component?',
334
+ 'Warning: React.createElement: type is invalid -- expected a string ' +
335
+ '(for built-in components) or a class/function (for composite ' +
336
+ 'components) but got: object.',
337
+ ],
338
{withoutStack: true},
339
);
340
341
+ expect(errors).toEqual(
342
+ __DEV__
343
+ ? [
344
+ 'Element type is invalid: expected a string ' +
345
+ '(for built-in components) or a class/function (for composite ' +
346
+ 'components) but got: undefined. You likely forgot to export your ' +
347
+ "component from the file it's defined in, or you might have mixed up " +
348
+ 'default and named imports.',
349
+ 'Element type is invalid: expected a string ' +
350
+ '(for built-in components) or a class/function (for composite ' +
351
+ 'components) but got: null.',
352
+ 'Element type is invalid: expected a string ' +
353
+ '(for built-in components) or a class/function (for composite ' +
354
+ 'components) but got: boolean.',
355
+ 'Element type is invalid: expected a string ' +
356
+ '(for built-in components) or a class/function (for composite ' +
357
+ 'components) but got: object.',
358
+ 'Element type is invalid: expected a string ' +
359
+ '(for built-in components) or a class/function (for composite ' +
360
+ 'components) but got: object. You likely forgot to export your ' +
361
+ "component from the file it's defined in, or you might have mixed up " +
362
+ 'default and named imports.',
363
+ 'Element type is invalid: expected a string ' +
364
+ '(for built-in components) or a class/function (for composite ' +
365
+ 'components) but got: <div />. Did you accidentally export a JSX literal ' +
366
+ 'instead of a component?',
367
+ 'Element type is invalid: expected a string ' +
368
+ '(for built-in components) or a class/function (for composite ' +
369
+ 'components) but got: <Foo />. Did you accidentally export a JSX literal ' +
370
+ 'instead of a component?',
371
+ 'Element type is invalid: expected a string ' +
372
+ '(for built-in components) or a class/function (for composite ' +
373
+ 'components) but got: <Context.Consumer />. Did you accidentally ' +
374
+ 'export a JSX literal instead of a component?',
375
+ 'Element type is invalid: expected a string ' +
376
+ '(for built-in components) or a class/function (for composite ' +
377
+ 'components) but got: object.',
378
+ ]
379
+ : [
380
+ 'Element type is invalid: expected a string ' +
381
+ '(for built-in components) or a class/function (for composite ' +
382
+ 'components) but got: undefined.',
383
+ 'Element type is invalid: expected a string ' +
384
+ '(for built-in components) or a class/function (for composite ' +
385
+ 'components) but got: null.',
386
+ 'Element type is invalid: expected a string ' +
387
+ '(for built-in components) or a class/function (for composite ' +
388
+ 'components) but got: boolean.',
389
+ 'Element type is invalid: expected a string ' +
390
+ '(for built-in components) or a class/function (for composite ' +
391
+ 'components) but got: object.',
392
+ 'Element type is invalid: expected a string ' +
393
+ '(for built-in components) or a class/function (for composite ' +
394
+ 'components) but got: object.',
395
+ 'Element type is invalid: expected a string ' +
396
+ '(for built-in components) or a class/function (for composite ' +
397
+ 'components) but got: object.',
398
+ 'Element type is invalid: expected a string ' +
399
+ '(for built-in components) or a class/function (for composite ' +
400
+ 'components) but got: object.',
401
+ 'Element type is invalid: expected a string ' +
402
+ '(for built-in components) or a class/function (for composite ' +
403
+ 'components) but got: object.',
404
+ 'Element type is invalid: expected a string ' +
405
+ '(for built-in components) or a class/function (for composite ' +
406
+ 'components) but got: object.',
407
+ ],
408
+ );
409
+
410
// Should not log any additional warnings
411
React.createElement('div');
412
});
425
'or a class/function (for composite components) but got: null.' +
426
(__DEV__ ? '\n\nCheck the render method of `ParentComp`.' : ''),
427
);
306
- }).toErrorDev([
307
- 'Warning: React.createElement: type is invalid -- expected a string ' +
308
- '(for built-in components) or a class/function (for composite ' +
309
- 'components) but got: null.\n' +
310
- ' in ParentComp (at **)',
311
- 'Warning: React.createElement: type is invalid -- expected a string ' +
312
- '(for built-in components) or a class/function (for composite ' +
313
- 'components) but got: null.\n' +
314
- ' in ParentComp (at **)',
315
- ]);
428
+ }).toErrorDev(
429
+ gate(flag => flag.enableOwnerStacks)
430
+ ? // We don't need these extra warnings because we already have the errors.
431
+ []
432
+ : [
433
+ 'Warning: React.createElement: type is invalid -- expected a string ' +
434
+ '(for built-in components) or a class/function (for composite ' +
435
+ 'components) but got: null.\n' +
436
+ ' in ParentComp (at **)',
437
+ 'Warning: React.createElement: type is invalid -- expected a string ' +
438
+ '(for built-in components) or a class/function (for composite ' +
439
+ 'components) but got: null.\n' +
440
+ ' in ParentComp (at **)',
441
+ ],
442
+ );
443
});
444
445
it('warns for fragments with illegal attributes', async () => {