[asserts helpers] react package (#31853)
Based off https://github.com/facebook/react/pull/31844 Commit to review: https://github.com/facebook/react/commit/11aa104e3e70c0accc21f785060b812beb145089 Converts the rest of the `react` package.
Ricky committed
Dec 23, 2024 at 14:58 UTC
94867f33be327a52bfffda89a14c85897180e43e
18 files changed
+1431
-1013
packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+37
-15
@@ -233,8 +233,28 @@ describe('ReactLazy', () => {
233
expect(error.message).toMatch('Element type is invalid');
234
assertLog(['Loading...']);
235
assertConsoleErrorDev([
236
- 'Expected the result of a dynamic import() call',
237
- 'Expected the result of a dynamic import() call',
236
+ 'lazy: Expected the result of a dynamic import() call. ' +
237
+ 'Instead received: function Text(props) {\n' +
238
+ ' Scheduler.log(props.text);\n' +
239
+ ' return props.text;\n' +
240
+ ' }\n\n' +
241
+ 'Your code should look like: \n ' +
242
+ "const MyComponent = lazy(() => import('./MyComponent'))\n" +
243
+ (gate('enableOwnerStacks')
244
+ ? ''
245
+ : ' in Lazy (at **)\n' + ' in Suspense (at **)\n') +
246
+ ' in App (at **)',
247
+ 'lazy: Expected the result of a dynamic import() call. ' +
248
+ 'Instead received: function Text(props) {\n' +
249
+ ' Scheduler.log(props.text);\n' +
250
+ ' return props.text;\n' +
251
+ ' }\n\n' +
252
+ 'Your code should look like: \n ' +
253
+ "const MyComponent = lazy(() => import('./MyComponent'))\n" +
254
+ (gate('enableOwnerStacks')
255
+ ? ''
256
+ : ' in Lazy (at **)\n' + ' in Suspense (at **)\n') +
257
+ ' in App (at **)',
258
]);
259
expect(root).not.toMatchRenderedOutput('Hi');
260
});
@@ -852,19 +872,21 @@ describe('ReactLazy', () => {
872
expect(root).not.toMatchRenderedOutput('22');
873
874
// Mount
855
- await expect(async () => {
856
- await act(() => resolveFakeImport(Add));
857
- }).toErrorDev(
858
- shouldWarnAboutFunctionDefaultProps
859
- ? [
860
- 'Add: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
861
- ]
862
- : shouldWarnAboutMemoDefaultProps
863
- ? [
864
- 'Add: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
865
- ]
866
- : [],
867
- );
875
+ await act(() => resolveFakeImport(Add));
876
+
877
+ if (shouldWarnAboutFunctionDefaultProps) {
878
+ assertConsoleErrorDev([
879
+ 'Add: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.\n' +
880
+ ' in Add (at **)\n' +
881
+ ' in Suspense (at **)',
882
+ ]);
883
+ } else if (shouldWarnAboutMemoDefaultProps) {
884
+ assertConsoleErrorDev([
885
+ 'Add: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.\n' +
886
+ ' in Suspense (at **)',
887
+ ]);
888
+ }
889
+
890
expect(root).toMatchRenderedOutput('22');
891
892
// Update
packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee
+132
-85
@@ -9,6 +9,8 @@ PropTypes = null
9
React = null
10
ReactDOM = null
11
ReactDOMClient = null
12
+assertConsoleErrorDev = null
13
+assertConsoleWarnDev = null
14
15
featureFlags = require 'shared/ReactFeatureFlags'
16
@@ -28,6 +30,9 @@ describe 'ReactCoffeeScriptClass', ->
30
root = ReactDOMClient.createRoot container
31
attachedListener = null
32
renderedName = null
33
+ TestUtils = require 'internal-test-utils'
34
+ assertConsoleErrorDev = TestUtils.assertConsoleErrorDev
35
+ assertConsoleWarnDev = TestUtils.assertConsoleWarnDev
36
InnerComponent = class extends React.Component
37
getName: -> this.props.name
38
render: ->
@@ -53,14 +58,15 @@ describe 'ReactCoffeeScriptClass', ->
58
event.preventDefault()
59
caughtErrors.push(event.error)
60
window.addEventListener 'error', errorHandler;
56
- expect(->
57
- ReactDOM.flushSync ->
58
- root.render React.createElement(Foo)
59
- ).toErrorDev([
60
- # A failed component renders twice in DEV in concurrent mode
61
- 'No `render` method found on the Foo instance',
62
- 'No `render` method found on the Foo instance',
63
- ])
61
+ ReactDOM.flushSync ->
62
+ root.render React.createElement(Foo)
63
+ assertConsoleErrorDev [
64
+# A failed component renders twice in DEV in concurrent mode
65
+ 'No `render` method found on the Foo instance: you may have forgotten to define `render`.\n' +
66
+ ' in Foo (at **)',
67
+ 'No `render` method found on the Foo instance: you may have forgotten to define `render`.\n' +
68
+ ' in Foo (at **)',
69
+ ]
70
window.removeEventListener 'error', errorHandler;
71
expect(caughtErrors).toEqual([
72
expect.objectContaining(
@@ -136,11 +142,11 @@ describe 'ReactCoffeeScriptClass', ->
142
React.createElement('div')
143
getDerivedStateFromProps: ->
144
{}
139
- expect(->
140
- ReactDOM.flushSync ->
141
- root.render React.createElement(Foo, foo: 'foo')
142
- return
143
- ).toErrorDev 'Foo: getDerivedStateFromProps() is defined as an instance method and will be ignored. Instead, declare it as a static method.'
145
+ ReactDOM.flushSync ->
146
+ root.render React.createElement(Foo, foo: 'foo')
147
+ assertConsoleErrorDev [
148
+ 'Foo: getDerivedStateFromProps() is defined as an instance method and will be ignored. Instead, declare it as a static method.\n' +
149
+ ' in Foo (at **)']
150
151
it 'warns if getDerivedStateFromError is not static', ->
152
class Foo extends React.Component
@@ -148,11 +154,13 @@ describe 'ReactCoffeeScriptClass', ->
154
React.createElement('div')
155
getDerivedStateFromError: ->
156
{}
151
- expect(->
152
- ReactDOM.flushSync ->
153
- root.render React.createElement(Foo, foo: 'foo')
154
- return
155
- ).toErrorDev 'Foo: getDerivedStateFromError() is defined as an instance method and will be ignored. Instead, declare it as a static method.'
157
+ ReactDOM.flushSync ->
158
+ root.render React.createElement(Foo, foo: 'foo')
159
+
160
+ assertConsoleErrorDev [
161
+ 'Foo: getDerivedStateFromError() is defined as an instance method and will be ignored. Instead, declare it as a static method.\n' +
162
+ ' in Foo (at **)'
163
+ ]
164
165
it 'warns if getSnapshotBeforeUpdate is static', ->
166
class Foo extends React.Component
@@ -160,11 +168,13 @@ describe 'ReactCoffeeScriptClass', ->
168
React.createElement('div')
169
Foo.getSnapshotBeforeUpdate = () ->
170
{}
163
- expect(->
164
- ReactDOM.flushSync ->
165
- root.render React.createElement(Foo, foo: 'foo')
166
- return
167
- ).toErrorDev 'Foo: getSnapshotBeforeUpdate() is defined as a static method and will be ignored. Instead, declare it as an instance method.'
171
+ ReactDOM.flushSync ->
172
+ root.render React.createElement(Foo, foo: 'foo')
173
+
174
+ assertConsoleErrorDev [
175
+ 'Foo: getSnapshotBeforeUpdate() is defined as a static method and will be ignored. Instead, declare it as an instance method.\n' +
176
+ ' in Foo (at **)'
177
+ ]
178
179
it 'warns if state not initialized before static getDerivedStateFromProps', ->
180
class Foo extends React.Component
@@ -177,16 +187,16 @@ describe 'ReactCoffeeScriptClass', ->
187
foo: nextProps.foo
188
bar: 'bar'
189
}
180
- expect(->
181
- ReactDOM.flushSync ->
182
- root.render React.createElement(Foo, foo: 'foo')
183
- return
184
- ).toErrorDev (
185
- '`Foo` uses `getDerivedStateFromProps` but its initial state is ' +
186
- 'undefined. This is not recommended. Instead, define the initial state by ' +
187
- 'assigning an object to `this.state` in the constructor of `Foo`. ' +
188
- 'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.'
189
- )
190
+ ReactDOM.flushSync ->
191
+ root.render React.createElement(Foo, foo: 'foo')
192
+
193
+ assertConsoleErrorDev [
194
+ '`Foo` uses `getDerivedStateFromProps` but its initial state is
195
+ undefined. This is not recommended. Instead, define the initial state by
196
+ assigning an object to `this.state` in the constructor of `Foo`.
197
+ This ensures that `getDerivedStateFromProps` arguments have a consistent shape.\n' +
198
+ ' in Foo (at **)'
199
+ ]
200
201
it 'updates initial state with values returned by static getDerivedStateFromProps', ->
202
class Foo extends React.Component
@@ -254,12 +264,28 @@ describe 'ReactCoffeeScriptClass', ->
264
render: ->
265
React.createElement Foo
266
257
- expect(->
258
- test React.createElement(Outer), 'SPAN', 'foo'
259
- ).toErrorDev([
260
- 'Outer uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
261
- 'Foo uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
262
- ])
267
+ test React.createElement(Outer), 'SPAN', 'foo'
268
+
269
+ if featureFlags.enableOwnerStacks
270
+ assertConsoleErrorDev([
271
+ 'Outer uses the legacy childContextTypes API which will soon be removed.
272
+ Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
273
+ ' in Outer (at **)',
274
+ 'Foo uses the legacy contextTypes API which will soon be removed.
275
+ Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
276
+ ' in Outer (at **)',
277
+ ]);
278
+ else
279
+ assertConsoleErrorDev([
280
+ 'Outer uses the legacy childContextTypes API which will soon be removed.
281
+ Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
282
+ ' in Outer (at **)',
283
+ 'Foo uses the legacy contextTypes API which will soon be removed.
284
+ Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
285
+ ' in Foo (at **)\n' +
286
+ ' in Outer (at **)',
287
+ ]);
288
+
289
290
it 'renders only once when setting state in componentWillMount', ->
291
renderCount = 0
@@ -286,9 +312,11 @@ describe 'ReactCoffeeScriptClass', ->
312
render: ->
313
React.createElement('span')
314
289
- expect(->
290
- test React.createElement(Foo), 'SPAN', ''
291
- ).toErrorDev('Foo.state: must be set to an object or null')
315
+ test React.createElement(Foo), 'SPAN', ''
316
+ assertConsoleErrorDev [
317
+ 'Foo.state: must be set to an object or null\n' +
318
+ ' in Foo (at **)'
319
+ ]
320
321
it 'should render with null in the initial state property', ->
322
class Foo extends React.Component
@@ -430,14 +458,21 @@ describe 'ReactCoffeeScriptClass', ->
458
className: 'foo'
459
)
460
433
- expect(->
434
- test React.createElement(Foo), 'SPAN', 'foo'
435
- ).toErrorDev([
436
- 'getInitialState was defined on Foo, a plain JavaScript class.',
437
- 'getDefaultProps was defined on Foo, a plain JavaScript class.',
438
- 'contextTypes was defined as an instance property on Foo.',
439
- 'contextType was defined as an instance property on Foo.',
440
- ])
461
+ test React.createElement(Foo), 'SPAN', 'foo'
462
+ assertConsoleErrorDev [
463
+ 'getInitialState was defined on Foo, a plain JavaScript class.
464
+ This is only supported for classes created using React.createClass.
465
+ Did you mean to define a state property instead?\n' +
466
+ ' in Foo (at **)',
467
+ 'getDefaultProps was defined on Foo, a plain JavaScript class.
468
+ This is only supported for classes created using React.createClass.
469
+ Use a static property to define defaultProps instead.\n' +
470
+ ' in Foo (at **)',
471
+ 'contextType was defined as an instance property on Foo. Use a static property to define contextType instead.\n' +
472
+ ' in Foo (at **)',
473
+ 'contextTypes was defined as an instance property on Foo. Use a static property to define contextTypes instead.\n' +
474
+ ' in Foo (at **)',
475
+ ]
476
expect(getInitialStateWasCalled).toBe false
477
expect(getDefaultPropsWasCalled).toBe false
478
@@ -468,13 +503,13 @@ describe 'ReactCoffeeScriptClass', ->
503
className: 'foo'
504
)
505
471
- expect(->
472
- test React.createElement(NamedComponent), 'SPAN', 'foo'
473
- ).toErrorDev(
506
+ test React.createElement(NamedComponent), 'SPAN', 'foo'
507
+ assertConsoleErrorDev [
508
'NamedComponent has a method called componentShouldUpdate().
509
Did you mean shouldComponentUpdate()? The name is phrased as a
476
- question because the function is expected to return a value.'
477
- )
510
+ question because the function is expected to return a value.\n' +
511
+ ' in NamedComponent (at **)'
512
+ ]
513
514
it 'should warn when misspelling componentWillReceiveProps', ->
515
class NamedComponent extends React.Component
@@ -486,12 +521,12 @@ describe 'ReactCoffeeScriptClass', ->
521
className: 'foo'
522
)
523
489
- expect(->
490
- test React.createElement(NamedComponent), 'SPAN', 'foo'
491
- ).toErrorDev(
524
+ test React.createElement(NamedComponent), 'SPAN', 'foo'
525
+ assertConsoleErrorDev [
526
'NamedComponent has a method called componentWillRecieveProps().
493
- Did you mean componentWillReceiveProps()?'
494
- )
527
+ Did you mean componentWillReceiveProps()?\n' +
528
+ ' in NamedComponent (at **)'
529
+ ]
530
531
it 'should warn when misspelling UNSAFE_componentWillReceiveProps', ->
532
class NamedComponent extends React.Component
@@ -503,28 +538,28 @@ describe 'ReactCoffeeScriptClass', ->
538
className: 'foo'
539
)
540
506
- expect(->
507
- test React.createElement(NamedComponent), 'SPAN', 'foo'
508
- ).toErrorDev(
541
+ test React.createElement(NamedComponent), 'SPAN', 'foo'
542
+ assertConsoleErrorDev [
543
'NamedComponent has a method called UNSAFE_componentWillRecieveProps().
510
- Did you mean UNSAFE_componentWillReceiveProps()?'
511
- )
544
+ Did you mean UNSAFE_componentWillReceiveProps()?\n' +
545
+ ' in NamedComponent (at **)'
546
+ ]
547
548
it 'should throw AND warn when trying to access classic APIs', ->
549
ref = React.createRef()
550
test React.createElement(InnerComponent, name: 'foo', ref: ref), 'DIV', 'foo'
516
- expect(->
517
- expect(-> ref.current.replaceState {}).toThrow()
518
- ).toWarnDev(
519
- 'replaceState(...) is deprecated in plain JavaScript React classes',
520
- {withoutStack: true}
521
- )
522
- expect(->
523
- expect(-> ref.current.isMounted()).toThrow()
524
- ).toWarnDev(
525
- 'isMounted(...) is deprecated in plain JavaScript React classes',
526
- {withoutStack: true}
527
- )
551
+
552
+ expect(-> ref.current.replaceState {}).toThrow()
553
+ assertConsoleWarnDev([
554
+ 'replaceState(...) is deprecated in plain JavaScript React classes.
555
+ Refactor your code to use setState instead (see https://github.com/facebook/react/issues/3236).'
556
+ ], {withoutStack: true})
557
+
558
+ expect(-> ref.current.isMounted()).toThrow()
559
+ assertConsoleWarnDev([
560
+ 'isMounted(...) is deprecated in plain JavaScript React classes.
561
+ Instead, make sure to clean up subscriptions and pending requests in componentWillUnmount to prevent memory leaks.',
562
+ ], {withoutStack: true})
563
564
if !featureFlags.disableLegacyContext
565
it 'supports this.context passed via getChildContext', ->
@@ -542,13 +577,25 @@ describe 'ReactCoffeeScriptClass', ->
577
render: ->
578
React.createElement Bar
579
545
- expect(->
546
- test React.createElement(Foo), 'DIV', 'bar-through-context'
547
- ).toErrorDev(
548
- [
549
- 'Foo uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
550
- 'Bar uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
551
- ],
552
- )
580
+ test React.createElement(Foo), 'DIV', 'bar-through-context'
581
+ if featureFlags.enableOwnerStacks
582
+ assertConsoleErrorDev [
583
+ 'Foo uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.
584
+ (https://react.dev/link/legacy-context)\n' +
585
+ ' in Foo (at **)',
586
+ 'Bar uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.
587
+ (https://react.dev/link/legacy-context)\n' +
588
+ ' in Foo (at **)'
589
+ ]
590
+ else
591
+ assertConsoleErrorDev [
592
+ 'Foo uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.
593
+ (https://react.dev/link/legacy-context)\n' +
594
+ ' in Foo (at **)',
595
+ 'Bar uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.
596
+ (https://react.dev/link/legacy-context)\n' +
597
+ ' in Bar (at **)\n' +
598
+ ' in Foo (at **)'
599
+ ]
600
601
undefined
packages/react/src/__tests__/ReactContextValidator-test.js
+166
-147
@@ -19,6 +19,7 @@ let PropTypes;
19
let React;
20
let ReactDOMClient;
21
let act;
22
+let assertConsoleErrorDev;
23
24
describe('ReactContextValidator', () => {
25
beforeEach(() => {
@@ -27,7 +28,7 @@ describe('ReactContextValidator', () => {
28
PropTypes = require('prop-types');
29
React = require('react');
30
ReactDOMClient = require('react-dom/client');
30
- act = require('internal-test-utils').act;
31
+ ({act, assertConsoleErrorDev} = require('internal-test-utils'));
32
});
33
34
// TODO: This behavior creates a runtime dependency on propTypes. We should
@@ -66,15 +67,20 @@ describe('ReactContextValidator', () => {
67
let instance;
68
const container = document.createElement('div');
69
const root = ReactDOMClient.createRoot(container);
69
- await expect(async () => {
70
- await act(() => {
71
- root.render(
72
- <ComponentInFooBarContext ref={current => (instance = current)} />,
73
- );
74
- });
75
- }).toErrorDev([
76
- 'ComponentInFooBarContext uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
77
- 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
70
+ await act(() => {
71
+ root.render(
72
+ <ComponentInFooBarContext ref={current => (instance = current)} />,
73
+ );
74
+ });
75
+ assertConsoleErrorDev([
76
+ 'ComponentInFooBarContext uses the legacy childContextTypes API which will soon be removed. ' +
77
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
78
+ ' in ComponentInFooBarContext (at **)',
79
+ 'Component uses the legacy contextTypes API which will soon be removed. ' +
80
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
81
+ (gate(flags => flags.enableOwnerStacks)
82
+ ? ' in ComponentInFooBarContext (at **)'
83
+ : ' in Component (at **)'),
84
]);
85
expect(instance.childRef.current.context).toEqual({foo: 'abc'});
86
});
@@ -144,13 +150,18 @@ describe('ReactContextValidator', () => {
150
151
const container = document.createElement('div');
152
const root = ReactDOMClient.createRoot(container);
147
- await expect(async () => {
148
- await act(() => {
149
- root.render(<Parent foo="abc" />);
150
- });
151
- }).toErrorDev([
152
- 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
153
- 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
153
+ await act(() => {
154
+ root.render(<Parent foo="abc" />);
155
+ });
156
+ assertConsoleErrorDev([
157
+ 'Parent uses the legacy childContextTypes API which will soon be removed. ' +
158
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
159
+ ' in Parent (at **)',
160
+ 'Component uses the legacy contextTypes API which will soon be removed. ' +
161
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
162
+ (gate(flags => flags.enableOwnerStacks)
163
+ ? ' in Parent (at **)'
164
+ : ' in Component (at **)'),
165
]);
166
167
expect(constructorContext).toEqual({foo: 'abc'});
@@ -191,33 +202,33 @@ describe('ReactContextValidator', () => {
202
}
203
}
204
194
- await expect(async () => {
195
- const container = document.createElement('div');
196
- const root = ReactDOMClient.createRoot(container);
197
- await act(() => {
198
- root.render(<ComponentA />);
199
- });
200
- }).toErrorDev([
201
- 'ComponentA uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
202
- 'ComponentA.childContextTypes is specified but there is no getChildContext() method on the instance. You can either define getChildContext() on ComponentA or remove childContextTypes from it.',
205
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
206
+ await act(() => {
207
+ root.render(<ComponentA />);
208
+ });
209
+ assertConsoleErrorDev([
210
+ 'ComponentA uses the legacy childContextTypes API which will soon be removed. ' +
211
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
212
+ ' in ComponentA (at **)',
213
+ 'ComponentA.childContextTypes is specified but there is no getChildContext() method on the instance. ' +
214
+ 'You can either define getChildContext() on ComponentA or remove childContextTypes from it.\n' +
215
+ ' in ComponentA (at **)',
216
]);
217
218
// Warnings should be deduped by component type
206
- let container = document.createElement('div');
207
- let root = ReactDOMClient.createRoot(container);
219
await act(() => {
220
root.render(<ComponentA />);
221
});
211
-
212
- await expect(async () => {
213
- container = document.createElement('div');
214
- root = ReactDOMClient.createRoot(container);
215
- await act(() => {
216
- root.render(<ComponentB />);
217
- });
218
- }).toErrorDev([
219
- 'ComponentB uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
220
- 'ComponentB.childContextTypes is specified but there is no getChildContext() method on the instance. You can either define getChildContext() on ComponentB or remove childContextTypes from it.',
222
+ await act(() => {
223
+ root.render(<ComponentB />);
224
+ });
225
+ assertConsoleErrorDev([
226
+ 'ComponentB uses the legacy childContextTypes API which will soon be removed. ' +
227
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
228
+ ' in ComponentB (at **)',
229
+ 'ComponentB.childContextTypes is specified but there is no getChildContext() method on the instance. ' +
230
+ 'You can either define getChildContext() on ComponentB or remove childContextTypes from it.\n' +
231
+ ' in ComponentB (at **)',
232
]);
233
});
234
@@ -260,17 +271,34 @@ describe('ReactContextValidator', () => {
271
foo: PropTypes.string.isRequired,
272
};
273
263
- await expect(async () => {
264
- const container = document.createElement('div');
265
- const root = ReactDOMClient.createRoot(container);
266
- await act(() => {
267
- root.render(<ParentContextProvider />);
268
- });
269
- }).toErrorDev([
270
- 'ParentContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
271
- 'MiddleMissingContext uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
272
- 'MiddleMissingContext.childContextTypes is specified but there is no getChildContext() method on the instance. You can either define getChildContext() on MiddleMissingContext or remove childContextTypes from it.',
273
- 'ChildContextConsumer uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
274
+ const container = document.createElement('div');
275
+ const root = ReactDOMClient.createRoot(container);
276
+ await act(() => {
277
+ root.render(<ParentContextProvider />);
278
+ });
279
+ assertConsoleErrorDev([
280
+ 'ParentContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
281
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
282
+ ' in ParentContextProvider (at **)',
283
+ 'MiddleMissingContext uses the legacy childContextTypes API which will soon be removed. ' +
284
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
285
+ (gate(flags => flags.enableOwnerStacks)
286
+ ? ''
287
+ : ' in MiddleMissingContext (at **)\n') +
288
+ ' in ParentContextProvider (at **)',
289
+ 'MiddleMissingContext.childContextTypes is specified but there is no getChildContext() method on the instance. ' +
290
+ 'You can either define getChildContext() on MiddleMissingContext or remove childContextTypes from it.\n' +
291
+ (gate(flags => flags.enableOwnerStacks)
292
+ ? ''
293
+ : ' in MiddleMissingContext (at **)\n') +
294
+ ' in ParentContextProvider (at **)',
295
+ 'ChildContextConsumer uses the legacy contextTypes API which will soon be removed. ' +
296
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
297
+ (gate(flags => flags.enableOwnerStacks)
298
+ ? ''
299
+ : ' in ChildContextConsumer (at **)\n') +
300
+ ' in MiddleMissingContext (at **)\n' +
301
+ ' in ParentContextProvider (at **)',
302
]);
303
expect(childContext.bar).toBeUndefined();
304
expect(childContext.foo).toBe('FOO');
@@ -428,25 +456,28 @@ describe('ReactContextValidator', () => {
456
}
457
}
458
431
- await expect(async () => {
432
- const container = document.createElement('div');
433
- const root = ReactDOMClient.createRoot(container);
434
- await act(() => {
435
- root.render(
436
- <ParentContextProvider>
437
- <ComponentA />
438
- </ParentContextProvider>,
439
- );
440
- });
441
- }).toErrorDev([
442
- 'ParentContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead',
443
- 'ComponentA uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
444
- 'ComponentA declares both contextTypes and contextType static properties. The legacy contextTypes property will be ignored.',
459
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
460
+ await act(() => {
461
+ root.render(
462
+ <ParentContextProvider>
463
+ <ComponentA />
464
+ </ParentContextProvider>,
465
+ );
466
+ });
467
+
468
+ assertConsoleErrorDev([
469
+ 'ParentContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
470
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
471
+ ' in ParentContextProvider (at **)',
472
+ 'ComponentA declares both contextTypes and contextType static properties. ' +
473
+ 'The legacy contextTypes property will be ignored.\n' +
474
+ ' in ComponentA (at **)',
475
+ 'ComponentA uses the legacy contextTypes API which will soon be removed. ' +
476
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
477
+ ' in ComponentA (at **)',
478
]);
479
480
// Warnings should be deduped by component type
448
- let container = document.createElement('div');
449
- let root = ReactDOMClient.createRoot(container);
481
await act(() => {
482
root.render(
483
<ParentContextProvider>
@@ -455,19 +486,20 @@ describe('ReactContextValidator', () => {
486
);
487
});
488
458
- await expect(async () => {
459
- container = document.createElement('div');
460
- root = ReactDOMClient.createRoot(container);
461
- await act(() => {
462
- root.render(
463
- <ParentContextProvider>
464
- <ComponentB />
465
- </ParentContextProvider>,
466
- );
467
- });
468
- }).toErrorDev([
469
- 'ComponentB declares both contextTypes and contextType static properties. The legacy contextTypes property will be ignored.',
470
- 'ComponentB uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
489
+ await act(() => {
490
+ root.render(
491
+ <ParentContextProvider>
492
+ <ComponentB />
493
+ </ParentContextProvider>,
494
+ );
495
+ });
496
+ assertConsoleErrorDev([
497
+ 'ComponentB declares both contextTypes and contextType static properties. ' +
498
+ 'The legacy contextTypes property will be ignored.\n' +
499
+ ' in ComponentB (at **)',
500
+ 'ComponentB uses the legacy contextTypes API which will soon be removed. ' +
501
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
502
+ ' in ComponentB (at **)',
503
]);
504
});
505
@@ -481,20 +513,17 @@ describe('ReactContextValidator', () => {
513
}
514
}
515
484
- await expect(async () => {
485
- const container = document.createElement('div');
486
- const root = ReactDOMClient.createRoot(container);
487
- await act(() => {
488
- root.render(<ComponentA />);
489
- });
490
- }).toErrorDev(
516
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
517
+ await act(() => {
518
+ root.render(<ComponentA />);
519
+ });
520
+ assertConsoleErrorDev([
521
'ComponentA defines an invalid contextType. ' +
522
'contextType should point to the Context object returned by React.createContext(). ' +
493
- 'Did you accidentally pass the Context.Consumer instead?',
494
- );
523
+ 'Did you accidentally pass the Context.Consumer instead?\n' +
524
+ ' in ComponentA (at **)',
525
+ ]);
526
496
- let container = document.createElement('div');
497
- let root = ReactDOMClient.createRoot(container);
527
await act(() => {
528
root.render(<ComponentA />);
529
});
@@ -505,8 +534,6 @@ describe('ReactContextValidator', () => {
534
return <div />;
535
}
536
}
508
- container = document.createElement('div');
509
- root = ReactDOMClient.createRoot(container);
537
await act(() => {
538
root.render(<ComponentB />);
539
});
@@ -539,23 +566,22 @@ describe('ReactContextValidator', () => {
566
}
567
568
await expect(async () => {
542
- await expect(async () => {
543
- const container = document.createElement('div');
544
- const root = ReactDOMClient.createRoot(container);
545
- await act(() => {
546
- root.render(<Foo />);
547
- });
548
- }).rejects.toThrow(
549
- "Cannot read properties of undefined (reading 'world')",
550
- );
551
- }).toErrorDev(
569
+ const container = document.createElement('div');
570
+ const root = ReactDOMClient.createRoot(container);
571
+ await act(() => {
572
+ root.render(<Foo />);
573
+ });
574
+ }).rejects.toThrow("Cannot read properties of undefined (reading 'world')");
575
+
576
+ assertConsoleErrorDev([
577
'Foo defines an invalid contextType. ' +
578
'contextType should point to the Context object returned by React.createContext(). ' +
579
'However, it is set to undefined. ' +
580
'This can be caused by a typo or by mixing up named and default imports. ' +
581
'This can also happen due to a circular dependency, ' +
557
- 'so try moving the createContext() call to a separate file.',
558
- );
582
+ 'so try moving the createContext() call to a separate file.\n' +
583
+ ' in Foo (at **)',
584
+ ]);
585
});
586
587
it('should warn when class contextType is an object', async () => {
@@ -571,20 +597,19 @@ describe('ReactContextValidator', () => {
597
}
598
599
await expect(async () => {
574
- await expect(async () => {
575
- const container = document.createElement('div');
576
- const root = ReactDOMClient.createRoot(container);
577
- await act(() => {
578
- root.render(<Foo />);
579
- });
580
- }).rejects.toThrow(
581
- "Cannot read properties of undefined (reading 'hello')",
582
- );
583
- }).toErrorDev(
600
+ const container = document.createElement('div');
601
+ const root = ReactDOMClient.createRoot(container);
602
+ await act(() => {
603
+ root.render(<Foo />);
604
+ });
605
+ }).rejects.toThrow("Cannot read properties of undefined (reading 'hello')");
606
+
607
+ assertConsoleErrorDev([
608
'Foo defines an invalid contextType. ' +
609
'contextType should point to the Context object returned by React.createContext(). ' +
586
- 'However, it is set to an object with keys {x, y}.',
587
- );
610
+ 'However, it is set to an object with keys {x, y}.\n' +
611
+ ' in Foo (at **)',
612
+ ]);
613
});
614
615
it('should warn when class contextType is a primitive', async () => {
@@ -596,20 +621,19 @@ describe('ReactContextValidator', () => {
621
}
622
623
await expect(async () => {
599
- await expect(async () => {
600
- const container = document.createElement('div');
601
- const root = ReactDOMClient.createRoot(container);
602
- await act(() => {
603
- root.render(<Foo />);
604
- });
605
- }).rejects.toThrow(
606
- "Cannot read properties of undefined (reading 'world')",
607
- );
608
- }).toErrorDev(
624
+ const container = document.createElement('div');
625
+ const root = ReactDOMClient.createRoot(container);
626
+ await act(() => {
627
+ root.render(<Foo />);
628
+ });
629
+ }).rejects.toThrow("Cannot read properties of undefined (reading 'world')");
630
+
631
+ assertConsoleErrorDev([
632
'Foo defines an invalid contextType. ' +
633
'contextType should point to the Context object returned by React.createContext(). ' +
611
- 'However, it is set to a string.',
612
- );
634
+ 'However, it is set to a string.\n' +
635
+ ' in Foo (at **)',
636
+ ]);
637
});
638
639
it('should warn if you define contextType on a function component', async () => {
@@ -625,31 +649,26 @@ describe('ReactContextValidator', () => {
649
}
650
ComponentB.contextType = Context;
651
628
- await expect(async () => {
629
- const container = document.createElement('div');
630
- const root = ReactDOMClient.createRoot(container);
631
- await act(() => {
632
- root.render(<ComponentA />);
633
- });
634
- }).toErrorDev(
635
- 'ComponentA: Function components do not support contextType.',
636
- );
652
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
653
+ await act(() => {
654
+ root.render(<ComponentA />);
655
+ });
656
+ assertConsoleErrorDev([
657
+ 'ComponentA: Function components do not support contextType.\n' +
658
+ ' in ComponentA (at **)',
659
+ ]);
660
661
// Warnings should be deduped by component type
639
- let container = document.createElement('div');
640
- let root = ReactDOMClient.createRoot(container);
662
await act(() => {
663
root.render(<ComponentA />);
664
});
665
645
- await expect(async () => {
646
- container = document.createElement('div');
647
- root = ReactDOMClient.createRoot(container);
648
- await act(() => {
649
- root.render(<ComponentB />);
650
- });
651
- }).toErrorDev(
652
- 'ComponentB: Function components do not support contextType.',
653
- );
666
+ await act(() => {
667
+ root.render(<ComponentB />);
668
+ });
669
+ assertConsoleErrorDev([
670
+ 'ComponentB: Function components do not support contextType.\n' +
671
+ ' in ComponentB (at **)',
672
+ ]);
673
});
674
});
packages/react/src/__tests__/ReactCreateElement-test.js
+45
-21
@@ -13,6 +13,8 @@ let act;
13
14
let React;
15
let ReactDOMClient;
16
+let assertConsoleErrorDev;
17
+let assertConsoleWarnDev;
18
19
// NOTE: This module tests the old, "classic" JSX runtime, React.createElement.
20
// Do not use JSX syntax in this module; call React.createElement directly.
@@ -22,7 +24,11 @@ describe('ReactCreateElement', () => {
24
beforeEach(() => {
25
jest.resetModules();
26
25
- act = require('internal-test-utils').act;
27
+ ({
28
+ act,
29
+ assertConsoleErrorDev,
30
+ assertConsoleWarnDev,
31
+ } = require('internal-test-utils'));
32
33
React = require('react');
34
ReactDOMClient = require('react-dom/client');
@@ -63,25 +69,34 @@ describe('ReactCreateElement', () => {
69
}
70
}
71
const root = ReactDOMClient.createRoot(document.createElement('div'));
66
- await expect(async () => {
67
- await act(() => {
68
- root.render(React.createElement(Parent));
69
- });
70
- }).toErrorDev(
72
+ await act(() => {
73
+ root.render(React.createElement(Parent));
74
+ });
75
+ assertConsoleErrorDev([
76
'Child: `key` is not a prop. Trying to access it will result ' +
77
'in `undefined` being returned. If you need to access the same ' +
78
'value within the child component, you should pass it as a different ' +
74
- 'prop. (https://react.dev/link/special-props)',
75
- );
79
+ 'prop. (https://react.dev/link/special-props)\n' +
80
+ (gate(flags => flags.enableOwnerStacks)
81
+ ? [' in Parent (at **)']
82
+ : [
83
+ ' in Child (at **)\n' +
84
+ ' in div (at **)\n' +
85
+ ' in Parent (at **)',
86
+ ]),
87
+ ]);
88
});
89
90
it('should warn when `key` is being accessed on a host element', () => {
91
const element = React.createElement('div', {key: '3'});
80
- expect(() => void element.props.key).toErrorDev(
81
- 'div: `key` is not a prop. Trying to access it will result ' +
82
- 'in `undefined` being returned. If you need to access the same ' +
83
- 'value within the child component, you should pass it as a different ' +
84
- 'prop. (https://react.dev/link/special-props)',
92
+ void element.props.key;
93
+ assertConsoleErrorDev(
94
+ [
95
+ 'div: `key` is not a prop. Trying to access it will result ' +
96
+ 'in `undefined` being returned. If you need to access the same ' +
97
+ 'value within the child component, you should pass it as a different ' +
98
+ 'prop. (https://react.dev/link/special-props)',
99
+ ],
100
{withoutStack: true},
101
);
102
});
@@ -141,9 +156,16 @@ describe('ReactCreateElement', () => {
156
foo: '56',
157
});
158
expect(element.type).toBe(ComponentClass);
144
- expect(() => expect(element.ref).toBe(ref)).toErrorDev(
145
- 'Accessing element.ref was removed in React 19',
146
- {withoutStack: true},
159
+ expect(element.ref).toBe(ref);
160
+ assertConsoleErrorDev(
161
+ [
162
+ 'Accessing element.ref was removed in React 19. ref is now a ' +
163
+ 'regular prop. It will be removed from the JSX Element ' +
164
+ 'type in a future release.',
165
+ ],
166
+ {
167
+ withoutStack: true,
168
+ },
169
);
170
const expectation = {foo: '56', ref};
171
Object.freeze(expectation);
@@ -412,11 +434,13 @@ describe('ReactCreateElement', () => {
434
435
it('warns if outdated JSX transform is detected', async () => {
436
// Warns if __self is detected, because that's only passed by a compiler
415
- expect(() => {
416
- React.createElement('div', {className: 'foo', __self: this});
417
- }).toWarnDev(
418
- 'Your app (or one of its dependencies) is using an outdated ' +
419
- 'JSX transform.',
437
+ React.createElement('div', {className: 'foo', __self: this});
438
+ assertConsoleWarnDev(
439
+ [
440
+ 'Your app (or one of its dependencies) is using an outdated JSX ' +
441
+ 'transform. Update to the modern JSX transform for ' +
442
+ 'faster performance: https://react.dev/link/new-jsx-transform',
443
+ ],
444
{
445
withoutStack: true,
446
},
packages/react/src/__tests__/ReactCreateRef-test.js
+20
-20
@@ -12,6 +12,7 @@
12
let React;
13
let ReactDOM;
14
let ReactDOMClient;
15
+let assertConsoleErrorDev;
16
17
describe('ReactCreateRef', () => {
18
beforeEach(() => {
@@ -20,6 +21,7 @@ describe('ReactCreateRef', () => {
21
React = require('react');
22
ReactDOM = require('react-dom');
23
ReactDOMClient = require('react-dom/client');
24
+ ({assertConsoleErrorDev} = require('internal-test-utils'));
25
});
26
27
it('should warn in dev if an invalid ref object is provided', () => {
@@ -34,38 +36,36 @@ describe('ReactCreateRef', () => {
36
}
37
38
const root = ReactDOMClient.createRoot(document.createElement('div'));
37
- expect(() =>
38
- ReactDOM.flushSync(() => {
39
- root.render(
40
- <Wrapper>
41
- <div ref={{}} />
42
- </Wrapper>,
43
- );
44
- }),
45
- ).toErrorDev(
39
+ ReactDOM.flushSync(() => {
40
+ root.render(
41
+ <Wrapper>
42
+ <div ref={{}} />
43
+ </Wrapper>,
44
+ );
45
+ });
46
+ assertConsoleErrorDev([
47
'Unexpected ref object provided for div. ' +
48
'Use either a ref-setter function or React.createRef().\n' +
49
' in div (at **)' +
50
(gate(flags => flags.enableOwnerStacks)
51
? ''
52
: '\n in Wrapper (at **)'),
52
- );
53
+ ]);
54
54
- expect(() =>
55
- ReactDOM.flushSync(() => {
56
- root.render(
57
- <Wrapper>
58
- <ExampleComponent ref={{}} />
59
- </Wrapper>,
60
- );
61
- }),
62
- ).toErrorDev(
55
+ ReactDOM.flushSync(() => {
56
+ root.render(
57
+ <Wrapper>
58
+ <ExampleComponent ref={{}} />
59
+ </Wrapper>,
60
+ );
61
+ });
62
+ assertConsoleErrorDev([
63
'Unexpected ref object provided for ExampleComponent. ' +
64
'Use either a ref-setter function or React.createRef().\n' +
65
' in ExampleComponent (at **)' +
66
(gate(flags => flags.enableOwnerStacks)
67
? ''
68
: '\n in Wrapper (at **)'),
69
- );
69
+ ]);
70
});
71
});
packages/react/src/__tests__/ReactES6Class-test.js
+106
-53
@@ -14,6 +14,7 @@ let React;
14
let ReactDOM;
15
let ReactDOMClient;
16
let assertConsoleErrorDev;
17
+let assertConsoleWarnDev;
18
19
describe('ReactES6Class', () => {
20
let container;
@@ -31,7 +32,10 @@ describe('ReactES6Class', () => {
32
React = require('react');
33
ReactDOM = require('react-dom');
34
ReactDOMClient = require('react-dom/client');
34
- ({assertConsoleErrorDev} = require('internal-test-utils'));
35
+ ({
36
+ assertConsoleErrorDev,
37
+ assertConsoleWarnDev,
38
+ } = require('internal-test-utils'));
39
container = document.createElement('div');
40
root = ReactDOMClient.createRoot(container);
41
attachedListener = null;
@@ -69,14 +73,15 @@ describe('ReactES6Class', () => {
73
}
74
window.addEventListener('error', errorHandler);
75
try {
72
- expect(() => {
73
- ReactDOM.flushSync(() => root.render(<Foo />));
74
- }).toErrorDev([
76
+ ReactDOM.flushSync(() => root.render(<Foo />));
77
+ assertConsoleErrorDev([
78
// A failed component renders twice in DEV in concurrent mode
79
'No `render` method found on the Foo instance: ' +
77
- 'you may have forgotten to define `render`.',
80
+ 'you may have forgotten to define `render`.\n' +
81
+ ' in Foo (at **)',
82
'No `render` method found on the Foo instance: ' +
79
- 'you may have forgotten to define `render`.',
83
+ 'you may have forgotten to define `render`.\n' +
84
+ ' in Foo (at **)',
85
]);
86
} finally {
87
window.removeEventListener('error', errorHandler);
@@ -158,12 +163,12 @@ describe('ReactES6Class', () => {
163
return <div />;
164
}
165
}
161
- expect(() => {
162
- ReactDOM.flushSync(() => root.render(<Foo foo="foo" />));
163
- }).toErrorDev(
166
+ ReactDOM.flushSync(() => root.render(<Foo foo="foo" />));
167
+ assertConsoleErrorDev([
168
'Foo: getDerivedStateFromProps() is defined as an instance method ' +
165
- 'and will be ignored. Instead, declare it as a static method.',
166
- );
169
+ 'and will be ignored. Instead, declare it as a static method.\n' +
170
+ ' in Foo (at **)',
171
+ ]);
172
});
173
174
it('warns if getDerivedStateFromError is not static', () => {
@@ -175,12 +180,12 @@ describe('ReactES6Class', () => {
180
return <div />;
181
}
182
}
178
- expect(() => {
179
- ReactDOM.flushSync(() => root.render(<Foo foo="foo" />));
180
- }).toErrorDev(
183
+ ReactDOM.flushSync(() => root.render(<Foo foo="foo" />));
184
+ assertConsoleErrorDev([
185
'Foo: getDerivedStateFromError() is defined as an instance method ' +
182
- 'and will be ignored. Instead, declare it as a static method.',
183
- );
186
+ 'and will be ignored. Instead, declare it as a static method.\n' +
187
+ ' in Foo (at **)',
188
+ ]);
189
});
190
191
it('warns if getSnapshotBeforeUpdate is static', () => {
@@ -190,12 +195,12 @@ describe('ReactES6Class', () => {
195
return <div />;
196
}
197
}
193
- expect(() => {
194
- ReactDOM.flushSync(() => root.render(<Foo foo="foo" />));
195
- }).toErrorDev(
198
+ ReactDOM.flushSync(() => root.render(<Foo foo="foo" />));
199
+ assertConsoleErrorDev([
200
'Foo: getSnapshotBeforeUpdate() is defined as a static method ' +
197
- 'and will be ignored. Instead, declare it as an instance method.',
198
- );
201
+ 'and will be ignored. Instead, declare it as an instance method.\n' +
202
+ ' in Foo (at **)',
203
+ ]);
204
});
205
206
it('warns if state not initialized before static getDerivedStateFromProps', () => {
@@ -210,14 +215,14 @@ describe('ReactES6Class', () => {
215
return <div className={`${this.state.foo} ${this.state.bar}`} />;
216
}
217
}
213
- expect(() => {
214
- ReactDOM.flushSync(() => root.render(<Foo foo="foo" />));
215
- }).toErrorDev(
218
+ ReactDOM.flushSync(() => root.render(<Foo foo="foo" />));
219
+ assertConsoleErrorDev([
220
'`Foo` uses `getDerivedStateFromProps` but its initial state is ' +
221
'undefined. This is not recommended. Instead, define the initial state by ' +
222
'assigning an object to `this.state` in the constructor of `Foo`. ' +
219
- 'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.',
220
- );
223
+ 'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.\n' +
224
+ ' in Foo (at **)',
225
+ ]);
226
});
227
228
it('updates initial state with values returned by static getDerivedStateFromProps', () => {
@@ -266,11 +271,13 @@ describe('ReactES6Class', () => {
271
super(props, context);
272
this.state = {tag: context.tag, className: this.context.className};
273
}
274
+
275
render() {
276
const Tag = this.state.tag;
277
return <Tag className={this.state.className} />;
278
}
279
}
280
+
281
Foo.contextTypes = {
282
tag: PropTypes.string,
283
className: PropTypes.string,
@@ -280,10 +287,12 @@ describe('ReactES6Class', () => {
287
getChildContext() {
288
return {tag: 'span', className: 'foo'};
289
}
290
+
291
render() {
292
return <Foo />;
293
}
294
}
295
+
296
Outer.childContextTypes = {
297
tag: PropTypes.string,
298
className: PropTypes.string,
@@ -291,8 +300,15 @@ describe('ReactES6Class', () => {
300
runTest(<Outer />, 'SPAN', 'foo');
301
302
assertConsoleErrorDev([
294
- 'Outer uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
295
- 'Foo uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
303
+ 'Outer uses the legacy childContextTypes API which will soon be removed. ' +
304
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
305
+ ' in Outer (at **)',
306
+ 'Foo uses the legacy contextTypes API which will soon be removed. ' +
307
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
308
+ (gate(flags => flags.enableOwnerStacks)
309
+ ? ''
310
+ : ' in Foo (at **)\n') +
311
+ ' in Outer (at **)',
312
]);
313
});
314
}
@@ -327,9 +343,10 @@ describe('ReactES6Class', () => {
343
return <span />;
344
}
345
}
330
- expect(() => runTest(<Foo />, 'SPAN', '')).toErrorDev(
331
- 'Foo.state: must be set to an object or null',
332
- );
346
+ runTest(<Foo />, 'SPAN', '');
347
+ assertConsoleErrorDev([
348
+ 'Foo.state: must be set to an object or null\n in Foo (at **)',
349
+ ]);
350
});
351
});
352
@@ -480,11 +497,22 @@ describe('ReactES6Class', () => {
497
}
498
}
499
483
- expect(() => runTest(<Foo />, 'SPAN', 'foo')).toErrorDev([
484
- 'getInitialState was defined on Foo, a plain JavaScript class.',
485
- 'getDefaultProps was defined on Foo, a plain JavaScript class.',
486
- 'contextType was defined as an instance property on Foo.',
487
- 'contextTypes was defined as an instance property on Foo.',
500
+ runTest(<Foo />, 'SPAN', 'foo');
501
+ assertConsoleErrorDev([
502
+ 'getInitialState was defined on Foo, a plain JavaScript class. ' +
503
+ 'This is only supported for classes created using React.createClass. ' +
504
+ 'Did you mean to define a state property instead?\n' +
505
+ ' in Foo (at **)',
506
+ 'getDefaultProps was defined on Foo, a plain JavaScript class. ' +
507
+ 'This is only supported for classes created using React.createClass. ' +
508
+ 'Use a static property to define defaultProps instead.\n' +
509
+ ' in Foo (at **)',
510
+ 'contextType was defined as an instance property on Foo. ' +
511
+ 'Use a static property to define contextType instead.\n' +
512
+ ' in Foo (at **)',
513
+ 'contextTypes was defined as an instance property on Foo. ' +
514
+ 'Use a static property to define contextTypes instead.\n' +
515
+ ' in Foo (at **)',
516
]);
517
expect(getInitialStateWasCalled).toBe(false);
518
expect(getDefaultPropsWasCalled).toBe(false);
@@ -514,11 +542,13 @@ describe('ReactES6Class', () => {
542
}
543
}
544
517
- expect(() => runTest(<NamedComponent />, 'SPAN', 'foo')).toErrorDev(
545
+ runTest(<NamedComponent />, 'SPAN', 'foo');
546
+ assertConsoleErrorDev([
547
'NamedComponent has a method called componentShouldUpdate(). Did you ' +
548
'mean shouldComponentUpdate()? The name is phrased as a question ' +
520
- 'because the function is expected to return a value.',
521
- );
549
+ 'because the function is expected to return a value.\n' +
550
+ ' in NamedComponent (at **)',
551
+ ]);
552
});
553
554
it('should warn when misspelling componentWillReceiveProps', () => {
@@ -531,10 +561,12 @@ describe('ReactES6Class', () => {
561
}
562
}
563
534
- expect(() => runTest(<NamedComponent />, 'SPAN', 'foo')).toErrorDev(
564
+ runTest(<NamedComponent />, 'SPAN', 'foo');
565
+ assertConsoleErrorDev([
566
'NamedComponent has a method called componentWillRecieveProps(). Did ' +
536
- 'you mean componentWillReceiveProps()?',
537
- );
567
+ 'you mean componentWillReceiveProps()?\n' +
568
+ ' in NamedComponent (at **)',
569
+ ]);
570
});
571
572
it('should warn when misspelling UNSAFE_componentWillReceiveProps', () => {
@@ -547,23 +579,33 @@ describe('ReactES6Class', () => {
579
}
580
}
581
550
- expect(() => runTest(<NamedComponent />, 'SPAN', 'foo')).toErrorDev(
582
+ runTest(<NamedComponent />, 'SPAN', 'foo');
583
+ assertConsoleErrorDev([
584
'NamedComponent has a method called UNSAFE_componentWillRecieveProps(). ' +
552
- 'Did you mean UNSAFE_componentWillReceiveProps()?',
553
- );
585
+ 'Did you mean UNSAFE_componentWillReceiveProps()?\n' +
586
+ ' in NamedComponent (at **)',
587
+ ]);
588
});
589
590
it('should throw AND warn when trying to access classic APIs', () => {
591
const ref = React.createRef();
592
runTest(<Inner name="foo" ref={ref} />, 'DIV', 'foo');
559
- expect(() =>
560
- expect(() => ref.current.replaceState({})).toThrow(),
561
- ).toWarnDev(
562
- 'replaceState(...) is deprecated in plain JavaScript React classes',
593
+
594
+ expect(() => ref.current.replaceState({})).toThrow();
595
+ assertConsoleWarnDev(
596
+ [
597
+ 'replaceState(...) is deprecated in plain JavaScript React classes. ' +
598
+ 'Refactor your code to use setState instead (see https://github.com/facebook/react/issues/3236).',
599
+ ],
600
{withoutStack: true},
601
);
565
- expect(() => expect(() => ref.current.isMounted()).toThrow()).toWarnDev(
566
- 'isMounted(...) is deprecated in plain JavaScript React classes',
602
+ expect(() => ref.current.isMounted()).toThrow();
603
+ assertConsoleWarnDev(
604
+ [
605
+ 'isMounted(...) is deprecated in plain JavaScript React classes. ' +
606
+ 'Instead, make sure to clean up subscriptions and pending requests in ' +
607
+ 'componentWillUnmount to prevent memory leaks.',
608
+ ],
609
{withoutStack: true},
610
);
611
});
@@ -575,20 +617,31 @@ describe('ReactES6Class', () => {
617
return <div className={this.context.bar} />;
618
}
619
}
620
+
621
Bar.contextTypes = {bar: PropTypes.string};
622
+
623
class Foo extends React.Component {
624
getChildContext() {
625
return {bar: 'bar-through-context'};
626
}
627
+
628
render() {
629
return <Bar />;
630
}
631
}
632
+
633
Foo.childContextTypes = {bar: PropTypes.string};
634
runTest(<Foo />, 'DIV', 'bar-through-context');
635
assertConsoleErrorDev([
590
- 'Foo uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
591
- 'Bar uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
636
+ 'Foo uses the legacy childContextTypes API which will soon be removed. ' +
637
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
638
+ ' in Foo (at **)',
639
+ 'Bar uses the legacy contextTypes API which will soon be removed. ' +
640
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
641
+ (gate(flags => flags.enableOwnerStacks)
642
+ ? ''
643
+ : ' in Bar (at **)\n') +
644
+ ' in Foo (at **)',
645
]);
646
});
647
}
packages/react/src/__tests__/ReactElementClone-test.js
+20
-9
@@ -12,6 +12,7 @@
12
let act;
13
let React;
14
let ReactDOMClient;
15
+let assertConsoleErrorDev;
16
17
describe('ReactElementClone', () => {
18
let ComponentClass;
@@ -19,7 +20,7 @@ describe('ReactElementClone', () => {
20
beforeEach(() => {
21
jest.resetModules();
22
22
- act = require('internal-test-utils').act;
23
+ ({act, assertConsoleErrorDev} = require('internal-test-utils'));
24
25
React = require('react');
26
ReactDOMClient = require('react-dom/client');
@@ -314,11 +315,14 @@ describe('ReactElementClone', () => {
315
316
it('warns for keys for arrays of elements in rest args', async () => {
317
const root = ReactDOMClient.createRoot(document.createElement('div'));
317
- await expect(async () => {
318
- await act(() => {
319
- root.render(React.cloneElement(<div />, null, [<div />, <div />]));
320
- });
321
- }).toErrorDev('Each child in a list should have a unique "key" prop.');
318
+ await act(() => {
319
+ root.render(React.cloneElement(<div />, null, [<div />, <div />]));
320
+ });
321
+ assertConsoleErrorDev([
322
+ 'Each child in a list should have a unique "key" prop.\n\n' +
323
+ 'Check the top-level render call using <div>. See https://react.dev/link/warning-keys for more information.\n' +
324
+ ' in div (at **)',
325
+ ]);
326
});
327
328
it('does not warns for arrays of elements with keys', async () => {
@@ -363,9 +367,16 @@ describe('ReactElementClone', () => {
367
expect(clone.type).toBe(ComponentClass);
368
expect(clone.key).toBe('12');
369
expect(clone.props.ref).toBe('34');
366
- expect(() => expect(clone.ref).toBe('34')).toErrorDev(
367
- 'Accessing element.ref was removed in React 19',
368
- {withoutStack: true},
370
+ expect(clone.ref).toBe('34');
371
+ assertConsoleErrorDev(
372
+ [
373
+ 'Accessing element.ref was removed in React 19. ref is now a ' +
374
+ 'regular prop. It will be removed from the JSX Element ' +
375
+ 'type in a future release.',
376
+ ],
377
+ {
378
+ withoutStack: true,
379
+ },
380
);
381
expect(clone.props).toEqual({foo: 'ef', ref: '34'});
382
if (__DEV__) {
packages/react/src/__tests__/ReactElementValidator-test.internal.js
+183
-138
@@ -18,6 +18,7 @@
18
let React;
19
let ReactDOMClient;
20
let act;
21
+let assertConsoleErrorDev;
22
23
describe('ReactElementValidator', () => {
24
let ComponentClass;
@@ -27,7 +28,7 @@ describe('ReactElementValidator', () => {
28
29
React = require('react');
30
ReactDOMClient = require('react-dom/client');
30
- act = require('internal-test-utils').act;
31
+ ({act, assertConsoleErrorDev} = require('internal-test-utils'));
32
ComponentClass = class extends React.Component {
33
render() {
34
return React.createElement('div', null, this.props.children);
@@ -37,16 +38,27 @@ describe('ReactElementValidator', () => {
38
39
it('warns for keys for arrays of elements in rest args', async () => {
40
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.');
41
+ await act(() =>
42
+ root.render(
43
+ React.createElement(ComponentClass, null, [
44
+ React.createElement(ComponentClass),
45
+ React.createElement(ComponentClass),
46
+ ]),
47
+ ),
48
+ );
49
+ assertConsoleErrorDev(
50
+ gate(flags => flags.enableOwnerStacks)
51
+ ? [
52
+ 'Each child in a list should have a unique "key" prop.\n\n' +
53
+ 'Check the render method of `ComponentClass`. See https://react.dev/link/warning-keys for more information.\n' +
54
+ ' in ComponentClass (at **)',
55
+ ]
56
+ : [
57
+ 'Each child in a list should have a unique "key" prop.\n\n' +
58
+ 'Check the top-level render call using <ComponentClass>. See https://react.dev/link/warning-keys for more information.\n' +
59
+ ' in ComponentClass (at **)',
60
+ ],
61
+ );
62
});
63
64
it('warns for keys for arrays of elements with owner info', async () => {
@@ -67,18 +79,23 @@ describe('ReactElementValidator', () => {
79
}
80
}
81
70
- await expect(async () => {
71
- const root = ReactDOMClient.createRoot(document.createElement('div'));
72
- await act(() => root.render(React.createElement(ComponentWrapper)));
73
- }).toErrorDev(
82
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
83
+ await act(() => root.render(React.createElement(ComponentWrapper)));
84
+ assertConsoleErrorDev([
85
'Each child in a list should have a unique "key" prop.' +
86
'\n\nCheck the render method of `' +
87
(gate(flags => flags.enableOwnerStacks)
88
? 'ComponentClass'
89
: 'InnerClass') +
90
'`. ' +
80
- 'It was passed a child from ComponentWrapper. ',
81
- );
91
+ 'It was passed a child from ComponentWrapper. ' +
92
+ 'See https://react.dev/link/warning-keys for more information.\n' +
93
+ (gate(flags => flags.enableOwnerStacks)
94
+ ? ' in ComponentWrapper (at **)'
95
+ : ' in ComponentClass (at **)\n' +
96
+ ' in InnerClass (at **)\n' +
97
+ ' in ComponentWrapper (at **)'),
98
+ ]);
99
});
100
101
it('warns for keys for arrays with no owner or parent info', async () => {
@@ -89,36 +106,35 @@ describe('ReactElementValidator', () => {
106
107
const divs = [<div />, <div />];
108
92
- await expect(async () => {
93
- const root = ReactDOMClient.createRoot(document.createElement('div'));
94
- await act(() => root.render(<Anonymous>{divs}</Anonymous>));
95
- }).toErrorDev(
109
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
110
+ await act(() => root.render(<Anonymous>{divs}</Anonymous>));
111
+ assertConsoleErrorDev([
112
gate(flags => flags.enableOwnerStacks)
113
? // For owner stacks the parent being validated is the div.
114
'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 **)'
115
+ '"key" prop.' +
116
+ '\n\nCheck the top-level render call using <div>. ' +
117
+ 'See https://react.dev/link/warning-keys for more information.\n' +
118
+ ' in div (at **)'
119
: '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
- );
120
+ '"key" prop. See https://react.dev/link/warning-keys for more information.\n' +
121
+ ' in div (at **)',
122
+ ]);
123
});
124
125
it('warns for keys for arrays of elements with no owner info', async () => {
126
const divs = [<div />, <div />];
127
112
- await expect(async () => {
113
- const root = ReactDOMClient.createRoot(document.createElement('div'));
128
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
129
115
- await act(() => root.render(<div>{divs}</div>));
116
- }).toErrorDev(
130
+ await act(() => root.render(<div>{divs}</div>));
131
+ assertConsoleErrorDev([
132
'Each child in a list should have a unique ' +
118
- '"key" prop.\n\nCheck the top-level render call using <div>. See ' +
119
- 'https://react.dev/link/warning-keys for more information.\n' +
133
+ '"key" prop.' +
134
+ '\n\nCheck the top-level render call using <div>. ' +
135
+ 'See https://react.dev/link/warning-keys for more information.\n' +
136
' in div (at **)',
121
- );
137
+ ]);
138
});
139
140
it('warns for keys with component stack info', async () => {
@@ -134,10 +150,9 @@ describe('ReactElementValidator', () => {
150
return <Parent child={<Component />} />;
151
}
152
137
- await expect(async () => {
138
- const root = ReactDOMClient.createRoot(document.createElement('div'));
139
- await act(() => root.render(<GrandParent />));
140
- }).toErrorDev(
153
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
154
+ await act(() => root.render(<GrandParent />));
155
+ assertConsoleErrorDev([
156
'Each child in a list should have a unique ' +
157
'"key" prop.\n\nCheck the render method of `Component`. See ' +
158
'https://react.dev/link/warning-keys for more information.\n' +
@@ -147,7 +162,7 @@ describe('ReactElementValidator', () => {
162
? ''
163
: ' in Parent (at **)\n') +
164
' in GrandParent (at **)',
150
- );
165
+ ]);
166
});
167
168
it('does not warn for keys when passing children down', async () => {
@@ -187,21 +202,37 @@ describe('ReactElementValidator', () => {
202
},
203
};
204
190
- await expect(async () => {
191
- const root = ReactDOMClient.createRoot(document.createElement('div'));
192
- await act(() =>
193
- root.render(React.createElement(ComponentClass, null, iterable)),
194
- );
195
- }).toErrorDev(
205
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
206
+ await act(() =>
207
+ root.render(React.createElement(ComponentClass, null, iterable)),
208
+ );
209
+ assertConsoleErrorDev(
210
gate(flag => flag.enableOwnerStacks)
197
- ? 'Each child in a list should have a unique "key" prop.'
211
+ ? [
212
+ 'Each child in a list should have a unique "key" prop.\n\n' +
213
+ 'Check the render method of `ComponentClass`. It was passed a child from div. ' +
214
+ 'See https://react.dev/link/warning-keys for more information.\n' +
215
+ ' in ComponentClass (at **)',
216
+ ]
217
: // Since each pass generates a new element, it doesn't get marked as
218
// validated and it gets rechecked each time.
200
-
219
[
202
- 'Each child in a list should have a unique "key" prop.',
203
- 'Each child in a list should have a unique "key" prop.',
204
- 'Each child in a list should have a unique "key" prop.',
220
+ 'Each child in a list should have a unique "key" prop.\n\n' +
221
+ 'Check the top-level render call using <ComponentClass>. ' +
222
+ 'See https://react.dev/link/warning-keys for more information.\n' +
223
+ ' in ComponentClass (at **)',
224
+
225
+ 'Each child in a list should have a unique "key" prop.\n\n' +
226
+ 'Check the render method of `ComponentClass`. ' +
227
+ 'See https://react.dev/link/warning-keys for more information.\n' +
228
+ ' in ComponentClass (at **)\n' +
229
+ ' in ComponentClass (at **)',
230
+ 'Each child in a list should have a unique "key" prop.\n\n' +
231
+ 'Check the render method of `ComponentClass`. It was passed a child from div. ' +
232
+ 'See https://react.dev/link/warning-keys for more information.\n' +
233
+ ' in ComponentClass (at **)\n' +
234
+ ' in div (at **)\n' +
235
+ ' in ComponentClass (at **)',
236
],
237
);
238
});
@@ -254,86 +285,102 @@ describe('ReactElementValidator', () => {
285
function ParentComp() {
286
return React.createElement(MyComp);
287
}
257
- await expect(async () => {
258
- const root = ReactDOMClient.createRoot(document.createElement('div'));
259
- await act(() => root.render(React.createElement(ParentComp)));
260
- }).toErrorDev(
288
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
289
+ await act(() => root.render(React.createElement(ParentComp)));
290
+ assertConsoleErrorDev([
291
'Each child in a list should have a unique "key" prop.' +
292
'\n\nCheck the render method of `ParentComp`. It was passed a child from MyComp. ' +
293
'See https://react.dev/link/warning-keys for more information.\n' +
294
' in div (at **)\n' +
295
' in MyComp (at **)\n' +
296
' in ParentComp (at **)',
267
- );
297
+ ]);
298
});
299
300
it('gives a helpful error when passing invalid types', async () => {
301
function Foo() {}
302
const errors = [];
273
- await expect(async () => {
274
- const root = ReactDOMClient.createRoot(document.createElement('div'), {
275
- onUncaughtError(error) {
276
- errors.push(error.message);
277
- },
278
- });
279
- const cases = [
280
- React.createElement(undefined),
281
- React.createElement(null),
282
- React.createElement(true),
283
- React.createElement({x: 17}),
284
- React.createElement({}),
285
- React.createElement(React.createElement('div')),
286
- React.createElement(React.createElement(Foo)),
287
- React.createElement(
288
- React.createElement(React.createContext().Consumer),
289
- ),
290
- React.createElement({$$typeof: 'non-react-thing'}),
291
- ];
292
- for (let i = 0; i < cases.length; i++) {
293
- await act(() => root.render(cases[i]));
294
- }
295
- }).toErrorDev(
296
- gate(flag => flag.enableOwnerStacks)
297
- ? // We don't need these extra warnings because we already have the errors.
298
- []
299
- : [
300
- 'React.createElement: type is invalid -- expected a string ' +
301
- '(for built-in components) or a class/function (for composite ' +
302
- 'components) but got: undefined. You likely forgot to export your ' +
303
- "component from the file it's defined in, or you might have mixed up " +
304
- 'default and named imports.',
305
- 'React.createElement: type is invalid -- expected a string ' +
306
- '(for built-in components) or a class/function (for composite ' +
307
- 'components) but got: null.',
308
- 'React.createElement: type is invalid -- expected a string ' +
309
- '(for built-in components) or a class/function (for composite ' +
310
- 'components) but got: boolean.',
311
- 'React.createElement: type is invalid -- expected a string ' +
312
- '(for built-in components) or a class/function (for composite ' +
313
- 'components) but got: object.',
314
- 'React.createElement: type is invalid -- expected a string ' +
315
- '(for built-in components) or a class/function (for composite ' +
316
- 'components) but got: object. You likely forgot to export your ' +
317
- "component from the file it's defined in, or you might have mixed up " +
318
- 'default and named imports.',
319
- 'React.createElement: type is invalid -- expected a string ' +
320
- '(for built-in components) or a class/function (for composite ' +
321
- 'components) but got: <div />. Did you accidentally export a JSX literal ' +
322
- 'instead of a component?',
323
- 'React.createElement: type is invalid -- expected a string ' +
324
- '(for built-in components) or a class/function (for composite ' +
325
- 'components) but got: <Foo />. Did you accidentally export a JSX literal ' +
326
- 'instead of a component?',
327
- 'React.createElement: type is invalid -- expected a string ' +
328
- '(for built-in components) or a class/function (for composite ' +
329
- 'components) but got: <Context.Consumer />. Did you accidentally ' +
330
- 'export a JSX literal instead of a component?',
331
- 'React.createElement: type is invalid -- expected a string ' +
332
- '(for built-in components) or a class/function (for composite ' +
333
- 'components) but got: object.',
334
- ],
335
- {withoutStack: true},
336
- );
303
+ const root = ReactDOMClient.createRoot(document.createElement('div'), {
304
+ onUncaughtError(error) {
305
+ errors.push(error.message);
306
+ },
307
+ });
308
+ const cases = [
309
+ [
310
+ () => React.createElement(undefined),
311
+ 'React.createElement: type is invalid -- expected a string ' +
312
+ '(for built-in components) or a class/function (for composite ' +
313
+ 'components) but got: undefined. You likely forgot to export your ' +
314
+ "component from the file it's defined in, or you might have mixed up " +
315
+ 'default and named imports.',
316
+ ],
317
+ [
318
+ () => React.createElement(null),
319
+ 'React.createElement: type is invalid -- expected a string ' +
320
+ '(for built-in components) or a class/function (for composite ' +
321
+ 'components) but got: null.',
322
+ ],
323
+ [
324
+ () => React.createElement(true),
325
+ 'React.createElement: type is invalid -- expected a string ' +
326
+ '(for built-in components) or a class/function (for composite ' +
327
+ 'components) but got: boolean.',
328
+ ],
329
+ [
330
+ () => React.createElement({x: 17}),
331
+ 'React.createElement: type is invalid -- expected a string ' +
332
+ '(for built-in components) or a class/function (for composite ' +
333
+ 'components) but got: object.',
334
+ ],
335
+ [
336
+ () => React.createElement({}),
337
+ 'React.createElement: type is invalid -- expected a string ' +
338
+ '(for built-in components) or a class/function (for composite ' +
339
+ 'components) but got: object. You likely forgot to export your ' +
340
+ "component from the file it's defined in, or you might have mixed up " +
341
+ 'default and named imports.',
342
+ ],
343
+ [
344
+ () => React.createElement(React.createElement('div')),
345
+ 'React.createElement: type is invalid -- expected a string ' +
346
+ '(for built-in components) or a class/function (for composite ' +
347
+ 'components) but got: <div />. Did you accidentally export a JSX literal ' +
348
+ 'instead of a component?',
349
+ ],
350
+ [
351
+ () => React.createElement(React.createElement(Foo)),
352
+ 'React.createElement: type is invalid -- expected a string ' +
353
+ '(for built-in components) or a class/function (for composite ' +
354
+ 'components) but got: <Foo />. Did you accidentally export a JSX literal ' +
355
+ 'instead of a component?',
356
+ ],
357
+ [
358
+ () =>
359
+ React.createElement(
360
+ React.createElement(React.createContext().Consumer),
361
+ ),
362
+ 'React.createElement: type is invalid -- expected a string ' +
363
+ '(for built-in components) or a class/function (for composite ' +
364
+ 'components) but got: <Context.Consumer />. Did you accidentally ' +
365
+ 'export a JSX literal instead of a component?',
366
+ ],
367
+ [
368
+ () => React.createElement({$$typeof: 'non-react-thing'}),
369
+ 'React.createElement: type is invalid -- expected a string ' +
370
+ '(for built-in components) or a class/function (for composite ' +
371
+ 'components) but got: object.',
372
+ ],
373
+ ];
374
+ for (let i = 0; i < cases.length; i++) {
375
+ await act(async () => root.render(cases[i][0]()));
376
+ assertConsoleErrorDev(
377
+ gate(flag => flag.enableOwnerStacks)
378
+ ? // We don't need these extra warnings because we already have the errors.
379
+ []
380
+ : [cases[i][1]],
381
+ {withoutStack: true},
382
+ );
383
+ }
384
385
expect(errors).toEqual(
386
__DEV__
@@ -414,15 +461,14 @@ describe('ReactElementValidator', () => {
461
}
462
463
await expect(async () => {
417
- await expect(async () => {
418
- const root = ReactDOMClient.createRoot(document.createElement('div'));
419
- await act(() => root.render(React.createElement(ParentComp)));
420
- }).rejects.toThrowError(
421
- 'Element type is invalid: expected a string (for built-in components) ' +
422
- 'or a class/function (for composite components) but got: null.' +
423
- (__DEV__ ? '\n\nCheck the render method of `ParentComp`.' : ''),
424
- );
425
- }).toErrorDev(
464
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
465
+ await act(() => root.render(React.createElement(ParentComp)));
466
+ }).rejects.toThrowError(
467
+ 'Element type is invalid: expected a string (for built-in components) ' +
468
+ 'or a class/function (for composite components) but got: null.' +
469
+ (__DEV__ ? '\n\nCheck the render method of `ParentComp`.' : ''),
470
+ );
471
+ assertConsoleErrorDev(
472
gate(flag => flag.enableOwnerStacks)
473
? // We don't need these extra warnings because we already have the errors.
474
[]
@@ -446,13 +492,13 @@ describe('ReactElementValidator', () => {
492
}
493
}
494
449
- await expect(async () => {
450
- const root = ReactDOMClient.createRoot(document.createElement('div'));
451
- await act(() => root.render(React.createElement(Foo)));
452
- }).toErrorDev(
495
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
496
+ await act(() => root.render(React.createElement(Foo)));
497
+ assertConsoleErrorDev([
498
'Invalid prop `a` supplied to `React.Fragment`. React.Fragment ' +
454
- 'can only have `key` and `children` props.',
455
- );
499
+ 'can only have `key` and `children` props.\n' +
500
+ ' in Foo (at **)',
501
+ ]);
502
});
503
504
it('does not warn when using DOM node as children', async () => {
@@ -512,9 +558,8 @@ describe('ReactElementValidator', () => {
558
559
it('does not blow up on key warning with undefined type', () => {
560
const Foo = undefined;
515
- expect(() => {
516
- void (<Foo>{[<div />]}</Foo>);
517
- }).toErrorDev(
561
+ void (<Foo>{[<div />]}</Foo>);
562
+ assertConsoleErrorDev(
563
gate(flags => flags.enableOwnerStacks)
564
? []
565
: [
packages/react/src/__tests__/ReactJSXElementValidator-test.js
+101
-66
@@ -14,6 +14,7 @@
14
let act;
15
let React;
16
let ReactDOMClient;
17
+let assertConsoleErrorDev;
18
19
describe('ReactJSXElementValidator', () => {
20
let Component;
@@ -22,7 +23,7 @@ describe('ReactJSXElementValidator', () => {
23
beforeEach(() => {
24
jest.resetModules();
25
25
- act = require('internal-test-utils').act;
26
+ ({act, assertConsoleErrorDev} = require('internal-test-utils'));
27
React = require('react');
28
ReactDOMClient = require('react-dom/client');
29
@@ -41,14 +42,21 @@ describe('ReactJSXElementValidator', () => {
42
});
43
44
it('warns for keys for arrays of elements in children position', async () => {
44
- await expect(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
- }).toErrorDev('Each child in a list should have a unique "key" prop.');
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
+ gate(flags => flags.enableOwnerStacks)
53
+ ? 'Each child in a list should have a unique "key" prop.\n\n' +
54
+ 'Check the render method of `Component`. See https://react.dev/link/warning-keys for more information.\n' +
55
+ ' in Component (at **)'
56
+ : 'Each child in a list should have a unique "key" prop.\n\n' +
57
+ 'Check the top-level render call using <Component>. See https://react.dev/link/warning-keys for more information.\n' +
58
+ ' in Component (at **)',
59
+ ]);
60
});
61
62
it('warns for keys for arrays of elements with owner info', async () => {
@@ -64,20 +72,24 @@ describe('ReactJSXElementValidator', () => {
72
}
73
}
74
67
- await expect(async () => {
68
- const container = document.createElement('div');
69
- const root = ReactDOMClient.createRoot(container);
70
- await act(() => {
71
- root.render(<ComponentWrapper />);
72
- });
73
- }).toErrorDev([
75
+ const container = document.createElement('div');
76
+ const root = ReactDOMClient.createRoot(container);
77
+ await act(() => {
78
+ root.render(<ComponentWrapper />);
79
+ });
80
+ assertConsoleErrorDev([
81
'Each child in a list should have a unique "key" prop.' +
82
'\n\nCheck the render method of `' +
83
(gate(flag => flag.enableOwnerStacks)
84
? 'Component'
85
: 'InnerComponent') +
86
'`. ' +
80
- 'It was passed a child from ComponentWrapper. ',
87
+ 'It was passed a child from ComponentWrapper. See https://react.dev/link/warning-keys for more information.\n' +
88
+ (gate(flag => flag.enableOwnerStacks)
89
+ ? ' in ComponentWrapper (at **)'
90
+ : ' in Component (at **)\n' +
91
+ ' in InnerComponent (at **)\n' +
92
+ ' in ComponentWrapper (at **)'),
93
]);
94
});
95
@@ -94,22 +106,38 @@ describe('ReactJSXElementValidator', () => {
106
},
107
};
108
97
- await expect(async () => {
98
- const container = document.createElement('div');
99
- const root = ReactDOMClient.createRoot(container);
109
+ const container = document.createElement('div');
110
+ const root = ReactDOMClient.createRoot(container);
111
101
- await act(() => {
102
- root.render(<Component>{iterable}</Component>);
103
- });
104
- }).toErrorDev(
112
+ await act(() => {
113
+ root.render(<Component>{iterable}</Component>);
114
+ });
115
+ assertConsoleErrorDev(
116
gate(flag => flag.enableOwnerStacks)
106
- ? ['Each child in a list should have a unique "key" prop.']
117
+ ? [
118
+ 'Each child in a list should have a unique "key" prop.\n\n' +
119
+ 'Check the render method of `Component`. It was passed a child from div. ' +
120
+ 'See https://react.dev/link/warning-keys for more information.\n' +
121
+ ' in Component (at **)',
122
+ ]
123
: // Since each pass generates a new element, it doesn't get marked as
124
// validated and it gets rechecked each time.
125
[
110
- 'Each child in a list should have a unique "key" prop.',
111
- 'Each child in a list should have a unique "key" prop.',
112
- 'Each child in a list should have a unique "key" prop.',
126
+ 'Each child in a list should have a unique "key" prop.\n\n' +
127
+ 'Check the top-level render call using <Component>. ' +
128
+ 'See https://react.dev/link/warning-keys for more information.\n' +
129
+ ' in Component (at **)',
130
+ 'Each child in a list should have a unique "key" prop.\n\n' +
131
+ 'Check the render method of `Component`. ' +
132
+ 'See https://react.dev/link/warning-keys for more information.\n' +
133
+ ' in Component (at **)\n' +
134
+ ' in Component (at **)',
135
+ 'Each child in a list should have a unique "key" prop.\n\n' +
136
+ 'Check the render method of `Component`. It was passed a child from div. ' +
137
+ 'See https://react.dev/link/warning-keys for more information.\n' +
138
+ ' in Component (at **)\n' +
139
+ ' in div (at **)\n' +
140
+ ' in Component (at **)',
141
],
142
);
143
});
@@ -198,21 +226,20 @@ describe('ReactJSXElementValidator', () => {
226
return <MyComp />;
227
}
228
}
201
- await expect(async () => {
202
- const container = document.createElement('div');
203
- const root = ReactDOMClient.createRoot(container);
204
-
205
- await act(() => {
206
- root.render(<ParentComp />);
207
- });
208
- }).toErrorDev(
229
+ const container = document.createElement('div');
230
+ const root = ReactDOMClient.createRoot(container);
231
+
232
+ await act(() => {
233
+ root.render(<ParentComp />);
234
+ });
235
+ assertConsoleErrorDev([
236
'Each child in a list should have a unique "key" prop.' +
237
'\n\nCheck the render method of `ParentComp`. It was passed a child from MyComp. ' +
238
'See https://react.dev/link/warning-keys for more information.\n' +
239
' in div (at **)\n' +
240
' in MyComp (at **)\n' +
241
' in ParentComp (at **)',
215
- );
242
+ ]);
243
});
244
245
it('warns for fragments with illegal attributes', async () => {
@@ -222,16 +249,16 @@ describe('ReactJSXElementValidator', () => {
249
}
250
}
251
225
- await expect(async () => {
226
- const container = document.createElement('div');
227
- const root = ReactDOMClient.createRoot(container);
228
- await act(() => {
229
- root.render(<Foo />);
230
- });
231
- }).toErrorDev(
252
+ const container = document.createElement('div');
253
+ const root = ReactDOMClient.createRoot(container);
254
+ await act(() => {
255
+ root.render(<Foo />);
256
+ });
257
+ assertConsoleErrorDev([
258
'Invalid prop `a` supplied to `React.Fragment`. React.Fragment ' +
233
- 'can only have `key` and `children` props.',
234
- );
259
+ 'can only have `key` and `children` props.\n' +
260
+ ' in Foo (at **)',
261
+ ]);
262
});
263
264
it('warns for fragments with refs', async () => {
@@ -248,13 +275,16 @@ describe('ReactJSXElementValidator', () => {
275
}
276
}
277
251
- await expect(async () => {
252
- const container = document.createElement('div');
253
- const root = ReactDOMClient.createRoot(container);
254
- await act(() => {
255
- root.render(<Foo />);
256
- });
257
- }).toErrorDev('Invalid prop `ref` supplied to `React.Fragment`.');
278
+ const container = document.createElement('div');
279
+ const root = ReactDOMClient.createRoot(container);
280
+ await act(() => {
281
+ root.render(<Foo />);
282
+ });
283
+ assertConsoleErrorDev([
284
+ 'Invalid prop `ref` supplied to `React.Fragment`.' +
285
+ ' React.Fragment can only have `key` and `children` props.\n' +
286
+ ' in Foo (at **)',
287
+ ]);
288
});
289
290
it('does not warn for fragments of multiple elements without keys', async () => {
@@ -271,19 +301,24 @@ describe('ReactJSXElementValidator', () => {
301
});
302
303
it('warns for fragments of multiple elements with same key', async () => {
274
- await expect(async () => {
275
- const container = document.createElement('div');
276
- const root = ReactDOMClient.createRoot(container);
277
- await act(() => {
278
- root.render(
279
- <>
280
- <span key="a">1</span>
281
- <span key="a">2</span>
282
- <span key="b">3</span>
283
- </>,
284
- );
285
- });
286
- }).toErrorDev('Encountered two children with the same key, `a`.');
304
+ const container = document.createElement('div');
305
+ const root = ReactDOMClient.createRoot(container);
306
+ await act(() => {
307
+ root.render(
308
+ <>
309
+ <span key="a">1</span>
310
+ <span key="a">2</span>
311
+ <span key="b">3</span>
312
+ </>,
313
+ );
314
+ });
315
+ assertConsoleErrorDev([
316
+ 'Encountered two children with the same key, `a`. ' +
317
+ 'Keys should be unique so that components maintain their identity across updates. ' +
318
+ 'Non-unique keys may cause children to be duplicated and/or omitted — ' +
319
+ 'the behavior is unsupported and could change in a future version.\n' +
320
+ ' in span (at **)',
321
+ ]);
322
});
323
324
it('does not call lazy initializers eagerly', () => {
packages/react/src/__tests__/ReactJSXRuntime-test.js
+58
-45
@@ -14,6 +14,7 @@ let ReactDOMClient;
14
let JSXRuntime;
15
let JSXDEVRuntime;
16
let act;
17
+let assertConsoleErrorDev;
18
19
// NOTE: Prefer to call the JSXRuntime directly in these tests so we can be
20
// certain that we are testing the runtime behavior, as opposed to the Babel
@@ -26,7 +27,7 @@ describe('ReactJSXRuntime', () => {
27
JSXRuntime = require('react/jsx-runtime');
28
JSXDEVRuntime = require('react/jsx-dev-runtime');
29
ReactDOMClient = require('react-dom/client');
29
- act = require('internal-test-utils').act;
30
+ ({act, assertConsoleErrorDev} = require('internal-test-utils'));
31
});
32
33
it('allows static methods to be called using the type property', () => {
@@ -205,41 +206,49 @@ describe('ReactJSXRuntime', () => {
206
});
207
}
208
}
208
- await expect(async () => {
209
- const root = ReactDOMClient.createRoot(container);
210
- await act(() => {
211
- root.render(JSXRuntime.jsx(Parent, {}));
212
- });
213
- }).toErrorDev(
209
+ const root = ReactDOMClient.createRoot(container);
210
+ await act(() => {
211
+ root.render(JSXRuntime.jsx(Parent, {}));
212
+ });
213
+ assertConsoleErrorDev([
214
'Child: `key` is not a prop. Trying to access it will result ' +
215
'in `undefined` being returned. If you need to access the same ' +
216
'value within the child component, you should pass it as a different ' +
217
- 'prop. (https://react.dev/link/special-props)',
218
- );
217
+ 'prop. (https://react.dev/link/special-props)\n' +
218
+ (gate(flags => flags.enableOwnerStacks)
219
+ ? ' in Parent (at **)'
220
+ : ' in Child (at **)\n' +
221
+ ' in div (at **)\n' +
222
+ ' in Parent (at **)'),
223
+ ]);
224
});
225
226
it('warns when a jsxs is passed something that is not an array', async () => {
227
const container = document.createElement('div');
223
- await expect(async () => {
224
- const root = ReactDOMClient.createRoot(container);
225
- await act(() => {
226
- root.render(JSXRuntime.jsxs('div', {children: 'foo'}, null));
227
- });
228
- }).toErrorDev(
229
- 'React.jsx: Static children should always be an array. ' +
230
- 'You are likely explicitly calling React.jsxs or React.jsxDEV. ' +
231
- 'Use the Babel transform instead.',
228
+ const root = ReactDOMClient.createRoot(container);
229
+ await act(() => {
230
+ root.render(JSXRuntime.jsxs('div', {children: 'foo'}, null));
231
+ });
232
+ assertConsoleErrorDev(
233
+ [
234
+ 'React.jsx: Static children should always be an array. ' +
235
+ 'You are likely explicitly calling React.jsxs or React.jsxDEV. ' +
236
+ 'Use the Babel transform instead.',
237
+ ],
238
{withoutStack: true},
239
);
240
});
241
242
it('should warn when `key` is being accessed on a host element', () => {
243
const element = JSXRuntime.jsxs('div', {}, '3');
238
- expect(() => void element.props.key).toErrorDev(
239
- 'div: `key` is not a prop. Trying to access it will result ' +
240
- 'in `undefined` being returned. If you need to access the same ' +
241
- 'value within the child component, you should pass it as a different ' +
242
- 'prop. (https://react.dev/link/special-props)',
244
+ void element.props.key;
245
+ assertConsoleErrorDev(
246
+ [
247
+ 'div: `key` is not a prop. Trying to access it will result ' +
248
+ 'in `undefined` being returned. If you need to access the same ' +
249
+ 'value within the child component, you should pass it as a different ' +
250
+ 'prop. (https://react.dev/link/special-props)',
251
+ ],
252
{withoutStack: true},
253
);
254
});
@@ -263,19 +272,18 @@ describe('ReactJSXRuntime', () => {
272
});
273
}
274
}
266
- await expect(async () => {
267
- const root = ReactDOMClient.createRoot(container);
268
- await act(() => {
269
- root.render(JSXRuntime.jsx(Parent, {}));
270
- });
271
- }).toErrorDev(
275
+ const root = ReactDOMClient.createRoot(container);
276
+ await act(() => {
277
+ root.render(JSXRuntime.jsx(Parent, {}));
278
+ });
279
+ assertConsoleErrorDev([
280
'Each child in a list should have a unique "key" prop.\n\n' +
281
'Check the render method of `Parent`. See https://react.dev/link/warning-keys for more information.\n' +
282
(gate(flags => flags.enableOwnerStacks)
283
? ''
284
: ' in Child (at **)\n') +
285
' in Parent (at **)',
278
- );
286
+ ]);
287
});
288
289
it('should warn when keys are passed as part of props', async () => {
@@ -292,19 +300,19 @@ describe('ReactJSXRuntime', () => {
300
});
301
}
302
}
295
- await expect(async () => {
296
- const root = ReactDOMClient.createRoot(container);
297
- await act(() => {
298
- root.render(JSXRuntime.jsx(Parent, {}));
299
- });
300
- }).toErrorDev(
303
+ const root = ReactDOMClient.createRoot(container);
304
+ await act(() => {
305
+ root.render(JSXRuntime.jsx(Parent, {}));
306
+ });
307
+ assertConsoleErrorDev([
308
'A props object containing a "key" prop is being spread into JSX:\n' +
309
' let props = {key: someKey, prop: ...};\n' +
310
' <Child {...props} />\n' +
311
'React keys must be passed directly to JSX without using spread:\n' +
312
' let props = {prop: ...};\n' +
306
- ' <Child key={someKey} {...props} />',
307
- );
313
+ ' <Child key={someKey} {...props} />\n' +
314
+ ' in Parent (at **)',
315
+ ]);
316
});
317
318
it('should not warn when unkeyed children are passed to jsxs', async () => {
@@ -368,13 +376,18 @@ describe('ReactJSXRuntime', () => {
376
key: 'key',
377
};
378
371
- let elementWithSpreadKey;
372
- expect(() => {
373
- elementWithSpreadKey = __DEV__
374
- ? JSXDEVRuntime.jsxDEV('div', configWithKey)
375
- : JSXRuntime.jsx('div', configWithKey);
376
- }).toErrorDev(
377
- 'A props object containing a "key" prop is being spread into JSX',
379
+ const elementWithSpreadKey = __DEV__
380
+ ? JSXDEVRuntime.jsxDEV('div', configWithKey)
381
+ : JSXRuntime.jsx('div', configWithKey);
382
+ assertConsoleErrorDev(
383
+ [
384
+ 'A props object containing a "key" prop is being spread into JSX:\n' +
385
+ ' let props = {key: someKey, foo: ..., bar: ...};\n' +
386
+ ' <div {...props} />\n' +
387
+ 'React keys must be passed directly to JSX without using spread:\n' +
388
+ ' let props = {foo: ..., bar: ...};\n' +
389
+ ' <div key={someKey} {...props} />',
390
+ ],
391
{withoutStack: true},
392
);
393
expect(elementWithSpreadKey.props).not.toBe(configWithKey);
packages/react/src/__tests__/ReactJSXTransformIntegration-test.js
+9
-3
@@ -12,6 +12,7 @@
12
let React;
13
let ReactDOMClient;
14
let act;
15
+let assertConsoleErrorDev;
16
17
// TODO: Historically this module was used to confirm that the JSX transform
18
// produces the correct output. However, most users (and indeed our own test
@@ -29,7 +30,7 @@ describe('ReactJSXTransformIntegration', () => {
30
31
React = require('react');
32
ReactDOMClient = require('react-dom/client');
32
- act = require('internal-test-utils').act;
33
+ ({act, assertConsoleErrorDev} = require('internal-test-utils'));
34
35
Component = class extends React.Component {
36
render() {
@@ -112,8 +113,13 @@ describe('ReactJSXTransformIntegration', () => {
113
const ref = React.createRef();
114
const element = <Component ref={ref} foo="56" />;
115
expect(element.type).toBe(Component);
115
- expect(() => expect(element.ref).toBe(ref)).toErrorDev(
116
- 'Accessing element.ref was removed in React 19',
116
+ expect(element.ref).toBe(ref);
117
+ assertConsoleErrorDev(
118
+ [
119
+ 'Accessing element.ref was removed in React 19. ref is now a ' +
120
+ 'regular prop. It will be removed from the JSX Element ' +
121
+ 'type in a future release.',
122
+ ],
123
{withoutStack: true},
124
);
125
const expectation = {foo: '56', ref};
packages/react/src/__tests__/ReactProfilerComponent-test.internal.js
+9
-6
@@ -14,6 +14,7 @@ let ReactDOMClient;
14
let ReactFeatureFlags;
15
let act;
16
let container;
17
+let assertConsoleErrorDev;
18
19
function loadModules({
20
enableProfilerTimer = true,
@@ -31,6 +32,7 @@ function loadModules({
32
ReactDOMClient = require('react-dom/client');
33
const InternalTestUtils = require('internal-test-utils');
34
act = InternalTestUtils.act;
35
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
36
}
37
38
describe('Profiler', () => {
@@ -54,12 +56,13 @@ describe('Profiler', () => {
56
if (__DEV__ && enableProfilerTimer) {
57
it('should warn if required params are missing', async () => {
58
const root = ReactDOMClient.createRoot(container);
57
- await expect(async () => {
58
- await act(() => {
59
- root.render(<React.Profiler />);
60
- });
61
- }).toErrorDev(
62
- 'Profiler must specify an "id" of type `string` as a prop. Received the type `undefined` instead.',
59
+ await act(() => {
60
+ root.render(<React.Profiler />);
61
+ });
62
+ assertConsoleErrorDev(
63
+ [
64
+ 'Profiler must specify an "id" of type `string` as a prop. Received the type `undefined` instead.',
65
+ ],
66
{
67
withoutStack: true,
68
},
packages/react/src/__tests__/ReactPureComponent-test.js
+18
-20
@@ -10,13 +10,13 @@
10
'use strict';
11
12
let act;
13
-
13
+let assertConsoleErrorDev;
14
let React;
15
let ReactDOMClient;
16
17
describe('ReactPureComponent', () => {
18
beforeEach(() => {
19
- act = require('internal-test-utils').act;
19
+ ({act, assertConsoleErrorDev} = require('internal-test-utils'));
20
21
React = require('react');
22
ReactDOMClient = require('react-dom/client');
@@ -90,16 +90,15 @@ describe('ReactPureComponent', () => {
90
91
const container = document.createElement('div');
92
const root = ReactDOMClient.createRoot(container);
93
- await expect(async () => {
94
- await act(() => {
95
- root.render(<Component />);
96
- });
97
- }).toErrorDev(
98
- '' +
99
- 'Component has a method called shouldComponentUpdate(). ' +
93
+ await act(() => {
94
+ root.render(<Component />);
95
+ });
96
+ assertConsoleErrorDev([
97
+ 'Component has a method called shouldComponentUpdate(). ' +
98
'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
101
- 'Please extend React.Component if shouldComponentUpdate is used.',
102
- );
99
+ 'Please extend React.Component if shouldComponentUpdate is used.\n' +
100
+ ' in Component (at **)',
101
+ ]);
102
await act(() => {
103
root.render(<Component />);
104
});
@@ -133,15 +132,14 @@ describe('ReactPureComponent', () => {
132
}
133
}
134
const root = ReactDOMClient.createRoot(document.createElement('div'));
136
- await expect(async () => {
137
- await act(() => {
138
- root.render(<PureComponent />);
139
- });
140
- }).toErrorDev(
141
- '' +
142
- 'PureComponent has a method called shouldComponentUpdate(). ' +
135
+ await act(() => {
136
+ root.render(<PureComponent />);
137
+ });
138
+ assertConsoleErrorDev([
139
+ 'PureComponent has a method called shouldComponentUpdate(). ' +
140
'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
144
- 'Please extend React.Component if shouldComponentUpdate is used.',
145
- );
141
+ 'Please extend React.Component if shouldComponentUpdate is used.\n' +
142
+ ' in PureComponent (at **)',
143
+ ]);
144
});
145
});
packages/react/src/__tests__/ReactStartTransition-test.js
+14
-15
@@ -12,6 +12,7 @@
12
let React;
13
let ReactTestRenderer;
14
let act;
15
+let assertConsoleWarnDev;
16
let useState;
17
let useTransition;
18
@@ -22,7 +23,7 @@ describe('ReactStartTransition', () => {
23
jest.resetModules();
24
React = require('react');
25
ReactTestRenderer = require('react-test-renderer');
25
- act = require('internal-test-utils').act;
26
+ ({act, assertConsoleWarnDev} = require('internal-test-utils'));
27
useState = React.useState;
28
useTransition = React.useTransition;
29
});
@@ -53,15 +54,14 @@ describe('ReactStartTransition', () => {
54
});
55
});
56
56
- await expect(async () => {
57
- await act(() => {
58
- React.startTransition(() => {
59
- subs.forEach(setState => {
60
- setState(state => state + 1);
61
- });
57
+ await act(() => {
58
+ React.startTransition(() => {
59
+ subs.forEach(setState => {
60
+ setState(state => state + 1);
61
});
62
});
64
- }).toWarnDev(
63
+ });
64
+ assertConsoleWarnDev(
65
[
66
'Detected a large number of updates inside startTransition. ' +
67
'If this is due to a subscription please re-write it to use React provided hooks. ' +
@@ -70,15 +70,14 @@ describe('ReactStartTransition', () => {
70
{withoutStack: true},
71
);
72
73
- await expect(async () => {
74
- await act(() => {
75
- triggerHookTransition(() => {
76
- subs.forEach(setState => {
77
- setState(state => state + 1);
78
- });
73
+ await act(() => {
74
+ triggerHookTransition(() => {
75
+ subs.forEach(setState => {
76
+ setState(state => state + 1);
77
});
78
});
81
- }).toWarnDev(
79
+ });
80
+ assertConsoleWarnDev(
81
[
82
'Detected a large number of updates inside startTransition. ' +
83
'If this is due to a subscription please re-write it to use React provided hooks. ' +
packages/react/src/__tests__/ReactStrictMode-test.js
+64
-48
@@ -19,6 +19,7 @@ let useMemo;
19
let useState;
20
let useReducer;
21
let assertConsoleErrorDev;
22
+let assertConsoleWarnDev;
23
24
describe('ReactStrictMode', () => {
25
beforeEach(() => {
@@ -27,7 +28,11 @@ describe('ReactStrictMode', () => {
28
ReactDOM = require('react-dom');
29
ReactDOMClient = require('react-dom/client');
30
ReactDOMServer = require('react-dom/server');
30
- ({act, assertConsoleErrorDev} = require('internal-test-utils'));
31
+ ({
32
+ act,
33
+ assertConsoleErrorDev,
34
+ assertConsoleWarnDev,
35
+ } = require('internal-test-utils'));
36
useMemo = React.useMemo;
37
useState = React.useState;
38
useReducer = React.useReducer;
@@ -40,20 +45,19 @@ describe('ReactStrictMode', () => {
45
46
const container = document.createElement('div');
47
const root = ReactDOMClient.createRoot(container);
43
- await expect(async () => {
44
- await act(() => {
45
- root.render(
46
- <React.StrictMode>
47
- <Foo />
48
- </React.StrictMode>,
49
- );
50
- });
51
- }).toErrorDev(
48
+ await act(() => {
49
+ root.render(
50
+ <React.StrictMode>
51
+ <Foo />
52
+ </React.StrictMode>,
53
+ );
54
+ });
55
+ assertConsoleErrorDev([
56
'Invalid ARIA attribute `ariaTypo`. ' +
57
'ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
58
' in div (at **)\n' +
59
' in Foo (at **)',
56
- );
60
+ ]);
61
});
62
63
it('should appear in the SSR component stack', () => {
@@ -61,18 +65,17 @@ describe('ReactStrictMode', () => {
65
return <div ariaTypo="" />;
66
}
67
64
- expect(() => {
65
- ReactDOMServer.renderToString(
66
- <React.StrictMode>
67
- <Foo />
68
- </React.StrictMode>,
69
- );
70
- }).toErrorDev(
68
+ ReactDOMServer.renderToString(
69
+ <React.StrictMode>
70
+ <Foo />
71
+ </React.StrictMode>,
72
+ );
73
+ assertConsoleErrorDev([
74
'Invalid ARIA attribute `ariaTypo`. ' +
75
'ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
76
' in div (at **)\n' +
77
' in Foo (at **)',
75
- );
78
+ ]);
79
});
80
81
// @gate __DEV__
@@ -620,9 +623,8 @@ describe('Concurrent Mode', () => {
623
624
const container = document.createElement('div');
625
const root = ReactDOMClient.createRoot(container);
623
- await expect(
624
- async () => await act(() => root.render(<StrictRoot />)),
625
- ).toErrorDev(
626
+ await act(() => root.render(<StrictRoot />));
627
+ assertConsoleErrorDev(
628
[
629
`Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. See https://react.dev/link/unsafe-component-lifecycles for details.
630
@@ -681,31 +683,29 @@ Please update the following components: App`,
683
const container = document.createElement('div');
684
const root = ReactDOMClient.createRoot(container);
685
684
- await expect(async () => {
685
- await expect(
686
- async () => await act(() => root.render(<StrictRoot />)),
687
- ).toErrorDev(
688
- [
689
- `Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. See https://react.dev/link/unsafe-component-lifecycles for details.
686
+ await act(() => root.render(<StrictRoot />));
687
+ assertConsoleErrorDev(
688
+ [
689
+ `Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. See https://react.dev/link/unsafe-component-lifecycles for details.
690
691
* Move code with side effects to componentDidMount, and set initial state in the constructor.
692
693
Please update the following components: App`,
694
- `Using UNSAFE_componentWillReceiveProps in strict mode is not recommended and may indicate bugs in your code. See https://react.dev/link/unsafe-component-lifecycles for details.
694
+ `Using UNSAFE_componentWillReceiveProps in strict mode is not recommended and may indicate bugs in your code. See https://react.dev/link/unsafe-component-lifecycles for details.
695
696
* Move data fetching code or side effects to componentDidUpdate.
697
* If you're updating state whenever props change, refactor your code to use memoization techniques or move it to static getDerivedStateFromProps. Learn more at: https://react.dev/link/derived-state
698
699
Please update the following components: Child`,
700
- `Using UNSAFE_componentWillUpdate in strict mode is not recommended and may indicate bugs in your code. See https://react.dev/link/unsafe-component-lifecycles for details.
700
+ `Using UNSAFE_componentWillUpdate in strict mode is not recommended and may indicate bugs in your code. See https://react.dev/link/unsafe-component-lifecycles for details.
701
702
* Move data fetching code or side effects to componentDidUpdate.
703
704
Please update the following components: App`,
705
- ],
706
- {withoutStack: true},
707
- );
708
- }).toWarnDev(
705
+ ],
706
+ {withoutStack: true},
707
+ );
708
+ assertConsoleWarnDev(
709
[
710
`componentWillMount has been renamed, and is not recommended for use. See https://react.dev/link/unsafe-component-lifecycles for details.
711
@@ -752,17 +752,25 @@ Please update the following components: Parent`,
752
753
const container = document.createElement('div');
754
const root = ReactDOMClient.createRoot(container);
755
- await expect(async () => {
756
- await act(() => root.render(<StrictRoot foo={true} />));
757
- }).toErrorDev(
758
- 'Using UNSAFE_componentWillMount in strict mode is not recommended',
755
+ await act(() => root.render(<StrictRoot foo={true} />));
756
+ assertConsoleErrorDev(
757
+ [
758
+ 'Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. ' +
759
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
760
+ '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n\n' +
761
+ 'Please update the following components: Foo',
762
+ ],
763
{withoutStack: true},
764
);
765
762
- await expect(async () => {
763
- await act(() => root.render(<StrictRoot foo={false} />));
764
- }).toErrorDev(
765
- 'Using UNSAFE_componentWillMount in strict mode is not recommended',
766
+ await act(() => root.render(<StrictRoot foo={false} />));
767
+ assertConsoleErrorDev(
768
+ [
769
+ 'Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. ' +
770
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
771
+ '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n\n' +
772
+ 'Please update the following components: Bar',
773
+ ],
774
{withoutStack: true},
775
);
776
@@ -810,12 +818,20 @@ Please update the following components: Parent`,
818
const container = document.createElement('div');
819
820
const root = ReactDOMClient.createRoot(container);
813
- await expect(async () => {
814
- await act(() => {
815
- root.render(<SyncRoot />);
816
- });
817
- }).toErrorDev(
818
- 'Using UNSAFE_componentWillReceiveProps in strict mode is not recommended',
821
+ await act(() => {
822
+ root.render(<SyncRoot />);
823
+ });
824
+ assertConsoleErrorDev(
825
+ [
826
+ 'Using UNSAFE_componentWillReceiveProps in strict mode is not recommended ' +
827
+ 'and may indicate bugs in your code. ' +
828
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
829
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
830
+ "* If you're updating state whenever props change, " +
831
+ 'refactor your code to use memoization techniques or move it to ' +
832
+ 'static getDerivedStateFromProps. Learn more at: https://react.dev/link/derived-state\n\n' +
833
+ 'Please update the following components: Bar, Foo',
834
+ ],
835
{withoutStack: true},
836
);
837
packages/react/src/__tests__/ReactTypeScriptClass-test.ts
+158
-124
@@ -16,9 +16,11 @@ import ReactDOM = require('react-dom');
16
import ReactDOMClient = require('react-dom/client');
17
import PropTypes = require('prop-types');
18
import ReactFeatureFlags = require('shared/ReactFeatureFlags');
19
+import TestUtils = require('internal-test-utils');
20
21
// Before Each
21
-
22
+const assertConsoleErrorDev = TestUtils.assertConsoleErrorDev;
23
+const assertConsoleWarnDev = TestUtils.assertConsoleWarnDev;
24
let container;
25
let root;
26
let attachedListener = null;
@@ -313,19 +315,19 @@ class ClassicRefs extends React.Component {
315
316
// Describe the actual test cases.
317
316
-describe('ReactTypeScriptClass', function() {
317
- beforeEach(function() {
318
+describe('ReactTypeScriptClass', function () {
319
+ beforeEach(function () {
320
container = document.createElement('div');
321
root = ReactDOMClient.createRoot(container);
322
attachedListener = null;
323
renderedName = null;
324
});
325
324
- it('preserves the name of the class for use in error messages', function() {
326
+ it('preserves the name of the class for use in error messages', function () {
327
expect(Empty.name).toBe('Empty');
328
});
329
328
- it('throws if no render function is defined', function() {
330
+ it('throws if no render function is defined', function () {
331
class Foo extends React.Component {}
332
const caughtErrors = [];
333
function errorHandler(event) {
@@ -334,14 +336,15 @@ describe('ReactTypeScriptClass', function() {
336
}
337
window.addEventListener('error', errorHandler);
338
try {
337
- expect(() => {
338
- ReactDOM.flushSync(() => root.render(React.createElement(Empty)))
339
- }).toErrorDev([
339
+ ReactDOM.flushSync(() => root.render(React.createElement(Empty)));
340
+ assertConsoleErrorDev([
341
// A failed component renders twice in DEV in concurrent mode
342
'No `render` method found on the Empty instance: ' +
342
- 'you may have forgotten to define `render`.',
343
+ 'you may have forgotten to define `render`.\n' +
344
+ ' in Empty (at **)',
345
'No `render` method found on the Empty instance: ' +
344
- 'you may have forgotten to define `render`.',
346
+ 'you may have forgotten to define `render`.\n' +
347
+ ' in Empty (at **)',
348
]);
349
} finally {
350
window.removeEventListener('error', errorHandler);
@@ -349,31 +352,31 @@ describe('ReactTypeScriptClass', function() {
352
expect(caughtErrors.length).toBe(1);
353
});
354
352
- it('renders a simple stateless component with prop', function() {
355
+ it('renders a simple stateless component with prop', function () {
356
test(React.createElement(SimpleStateless, {bar: 'foo'}), 'DIV', 'foo');
357
test(React.createElement(SimpleStateless, {bar: 'bar'}), 'DIV', 'bar');
358
});
359
357
- it('renders based on state using initial values in this.props', function() {
360
+ it('renders based on state using initial values in this.props', function () {
361
test(
362
React.createElement(InitialState, {initialValue: 'foo'}),
363
'SPAN',
361
- 'foo'
364
+ 'foo',
365
);
366
});
367
365
- it('renders based on state using props in the constructor', function() {
368
+ it('renders based on state using props in the constructor', function () {
369
const ref = React.createRef();
370
test(
371
React.createElement(StateBasedOnProps, {initialValue: 'foo', ref: ref}),
372
'DIV',
370
- 'foo'
373
+ 'foo',
374
);
375
ReactDOM.flushSync(() => ref.current.changeState());
376
test(React.createElement(StateBasedOnProps), 'SPAN', 'bar');
377
});
378
376
- it('sets initial state with value returned by static getDerivedStateFromProps', function() {
379
+ it('sets initial state with value returned by static getDerivedStateFromProps', function () {
380
class Foo extends React.Component {
381
state = {
382
foo: null,
@@ -394,7 +397,7 @@ describe('ReactTypeScriptClass', function() {
397
test(React.createElement(Foo, {foo: 'foo'}), 'DIV', 'foo bar');
398
});
399
397
- it('warns if getDerivedStateFromProps is not static', function() {
400
+ it('warns if getDerivedStateFromProps is not static', function () {
401
class Foo extends React.Component {
402
getDerivedStateFromProps() {
403
return {};
@@ -403,17 +406,17 @@ describe('ReactTypeScriptClass', function() {
406
return React.createElement('div', {});
407
}
408
}
406
- expect(function() {
407
- ReactDOM.flushSync(() =>
408
- root.render(React.createElement(Foo, {foo: 'foo'}))
409
- );
410
- }).toErrorDev(
411
- 'Foo: getDerivedStateFromProps() is defined as an instance method ' +
412
- 'and will be ignored. Instead, declare it as a static method.'
409
+ ReactDOM.flushSync(() =>
410
+ root.render(React.createElement(Foo, {foo: 'foo'})),
411
);
412
+ assertConsoleErrorDev([
413
+ 'Foo: getDerivedStateFromProps() is defined as an instance method ' +
414
+ 'and will be ignored. Instead, declare it as a static method.\n' +
415
+ ' in Foo (at **)',
416
+ ]);
417
});
418
416
- it('warns if getDerivedStateFromError is not static', function() {
419
+ it('warns if getDerivedStateFromError is not static', function () {
420
class Foo extends React.Component {
421
getDerivedStateFromError() {
422
return {};
@@ -422,34 +425,34 @@ describe('ReactTypeScriptClass', function() {
425
return React.createElement('div');
426
}
427
}
425
- expect(function() {
426
- ReactDOM.flushSync(() =>
427
- root.render(React.createElement(Foo, {foo: 'foo'}))
428
- );
429
- }).toErrorDev(
430
- 'Foo: getDerivedStateFromError() is defined as an instance method ' +
431
- 'and will be ignored. Instead, declare it as a static method.'
428
+ ReactDOM.flushSync(() =>
429
+ root.render(React.createElement(Foo, {foo: 'foo'})),
430
);
431
+ assertConsoleErrorDev([
432
+ 'Foo: getDerivedStateFromError() is defined as an instance method ' +
433
+ 'and will be ignored. Instead, declare it as a static method.\n' +
434
+ ' in Foo (at **)',
435
+ ]);
436
});
437
435
- it('warns if getSnapshotBeforeUpdate is static', function() {
438
+ it('warns if getSnapshotBeforeUpdate is static', function () {
439
class Foo extends React.Component {
440
static getSnapshotBeforeUpdate() {}
441
render() {
442
return React.createElement('div', {});
443
}
444
}
442
- expect(function() {
443
- ReactDOM.flushSync(() =>
444
- root.render(React.createElement(Foo, {foo: 'foo'}))
445
- );
446
- }).toErrorDev(
447
- 'Foo: getSnapshotBeforeUpdate() is defined as a static method ' +
448
- 'and will be ignored. Instead, declare it as an instance method.'
445
+ ReactDOM.flushSync(() =>
446
+ root.render(React.createElement(Foo, {foo: 'foo'})),
447
);
448
+ assertConsoleErrorDev([
449
+ 'Foo: getSnapshotBeforeUpdate() is defined as a static method ' +
450
+ 'and will be ignored. Instead, declare it as an instance method.\n' +
451
+ ' in Foo (at **)',
452
+ ]);
453
});
454
452
- it('warns if state not initialized before static getDerivedStateFromProps', function() {
455
+ it('warns if state not initialized before static getDerivedStateFromProps', function () {
456
class Foo extends React.Component {
457
static getDerivedStateFromProps(nextProps, prevState) {
458
return {
@@ -463,19 +466,19 @@ describe('ReactTypeScriptClass', function() {
466
});
467
}
468
}
466
- expect(function() {
467
- ReactDOM.flushSync(() =>
468
- root.render(React.createElement(Foo, {foo: 'foo'}))
469
- );
470
- }).toErrorDev(
469
+ ReactDOM.flushSync(() =>
470
+ root.render(React.createElement(Foo, {foo: 'foo'})),
471
+ );
472
+ assertConsoleErrorDev([
473
'`Foo` uses `getDerivedStateFromProps` but its initial state is ' +
474
'undefined. This is not recommended. Instead, define the initial state by ' +
475
'assigning an object to `this.state` in the constructor of `Foo`. ' +
474
- 'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.'
475
- );
476
+ 'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.\n' +
477
+ ' in Foo (at **)',
478
+ ]);
479
});
480
478
- it('updates initial state with values returned by static getDerivedStateFromProps', function() {
481
+ it('updates initial state with values returned by static getDerivedStateFromProps', function () {
482
class Foo extends React.Component {
483
state = {
484
foo: 'foo',
@@ -495,7 +498,7 @@ describe('ReactTypeScriptClass', function() {
498
test(React.createElement(Foo), 'DIV', 'not-foo bar');
499
});
500
498
- it('renders updated state with values returned by static getDerivedStateFromProps', function() {
501
+ it('renders updated state with values returned by static getDerivedStateFromProps', function () {
502
class Foo extends React.Component {
503
state = {
504
value: 'initial',
@@ -517,66 +520,80 @@ describe('ReactTypeScriptClass', function() {
520
});
521
522
if (!ReactFeatureFlags.disableLegacyContext) {
520
- it('renders based on context in the constructor', function() {
521
- expect(() => test(React.createElement(ProvideChildContextTypes), 'SPAN', 'foo')).toErrorDev([
522
- 'ProvideChildContextTypes uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
523
- 'StateBasedOnContext uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.'
523
+ it('renders based on context in the constructor', function () {
524
+ test(React.createElement(ProvideChildContextTypes), 'SPAN', 'foo');
525
+ assertConsoleErrorDev([
526
+ 'ProvideChildContextTypes uses the legacy childContextTypes API which will soon be removed. ' +
527
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
528
+ ' in ProvideChildContextTypes (at **)',
529
+ 'StateBasedOnContext uses the legacy contextTypes API which will soon be removed. ' +
530
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
531
+ (ReactFeatureFlags.enableOwnerStacks
532
+ ? ' in ProvideChildContextTypes.Object.<anonymous>.ProvideChildContextTypes (at **)'
533
+ : ' in StateBasedOnContext (at **)\n') +
534
+ ' in ProvideChildContextTypes (at **)',
535
]);
536
});
537
}
538
528
- it('renders only once when setting state in componentWillMount', function() {
539
+ it('renders only once when setting state in componentWillMount', function () {
540
renderCount = 0;
541
test(React.createElement(RenderOnce, {initialValue: 'foo'}), 'SPAN', 'bar');
542
expect(renderCount).toBe(1);
543
});
544
534
- it('should warn with non-object in the initial state property', function() {
535
- expect(() => test(React.createElement(ArrayState), 'SPAN', '')).toErrorDev(
536
- 'ArrayState.state: must be set to an object or null'
537
- );
538
- expect(() => test(React.createElement(StringState), 'SPAN', '')).toErrorDev(
539
- 'StringState.state: must be set to an object or null'
540
- );
541
- expect(() => test(React.createElement(NumberState), 'SPAN', '')).toErrorDev(
542
- 'NumberState.state: must be set to an object or null'
543
- );
545
+ it('should warn with non-object in the initial state property', function () {
546
+ test(React.createElement(ArrayState), 'SPAN', '');
547
+ assertConsoleErrorDev([
548
+ 'ArrayState.state: must be set to an object or null\n' +
549
+ ' in ArrayState (at **)',
550
+ ]);
551
+ test(React.createElement(StringState), 'SPAN', '');
552
+ assertConsoleErrorDev([
553
+ 'StringState.state: must be set to an object or null\n' +
554
+ ' in StringState (at **)',
555
+ ]);
556
+ test(React.createElement(NumberState), 'SPAN', '');
557
+ assertConsoleErrorDev([
558
+ 'NumberState.state: must be set to an object or null\n' +
559
+ ' in NumberState (at **)',
560
+ ]);
561
});
562
546
- it('should render with null in the initial state property', function() {
563
+ it('should render with null in the initial state property', function () {
564
test(React.createElement(NullState), 'SPAN', '');
565
});
566
550
- it('setState through an event handler', function() {
567
+ it('setState through an event handler', function () {
568
test(
569
React.createElement(BoundEventHandler, {initialValue: 'foo'}),
570
'DIV',
554
- 'foo'
571
+ 'foo',
572
);
573
ReactDOM.flushSync(() => attachedListener());
574
expect(renderedName).toBe('bar');
575
});
576
560
- it('should not implicitly bind event handlers', function() {
577
+ it('should not implicitly bind event handlers', function () {
578
test(
579
React.createElement(UnboundEventHandler, {initialValue: 'foo'}),
580
'DIV',
564
- 'foo'
581
+ 'foo',
582
);
583
expect(attachedListener).toThrow();
584
});
585
569
- it('renders using forceUpdate even when there is no state', function() {
586
+ it('renders using forceUpdate even when there is no state', function () {
587
test(
588
React.createElement(ForceUpdateWithNoState, {initialValue: 'foo'}),
589
'DIV',
573
- 'foo'
590
+ 'foo',
591
);
592
ReactDOM.flushSync(() => attachedListener());
593
expect(renderedName).toBe('bar');
594
});
595
579
- it('will call all the normal life cycle methods', function() {
596
+ it('will call all the normal life cycle methods', function () {
597
lifeCycles = [];
598
test(React.createElement(NormalLifeCycles, {value: 'foo'}), 'SPAN', 'foo');
599
expect(lifeCycles).toEqual(['will-mount', 'did-mount']);
@@ -604,22 +621,29 @@ describe('ReactTypeScriptClass', function() {
621
it(
622
'warns when classic properties are defined on the instance, ' +
623
'but does not invoke them.',
607
- function() {
624
+ function () {
625
getInitialStateWasCalled = false;
626
getDefaultPropsWasCalled = false;
610
- expect(() =>
611
- test(React.createElement(ClassicProperties), 'SPAN', 'foo')
612
- ).toErrorDev([
613
- 'getInitialState was defined on ClassicProperties, ' +
614
- 'a plain JavaScript class.',
615
- 'getDefaultProps was defined on ClassicProperties, ' +
616
- 'a plain JavaScript class.',
617
- 'contextTypes was defined as an instance property on ClassicProperties.',
618
- 'contextType was defined as an instance property on ClassicProperties.',
627
+ test(React.createElement(ClassicProperties), 'SPAN', 'foo');
628
+ assertConsoleErrorDev([
629
+ 'getInitialState was defined on ClassicProperties, a plain JavaScript class. ' +
630
+ 'This is only supported for classes created using React.createClass. ' +
631
+ 'Did you mean to define a state property instead?\n' +
632
+ ' in ClassicProperties (at **)',
633
+ 'getDefaultProps was defined on ClassicProperties, a plain JavaScript class. ' +
634
+ 'This is only supported for classes created using React.createClass. ' +
635
+ 'Use a static property to define defaultProps instead.\n' +
636
+ ' in ClassicProperties (at **)',
637
+ 'contextType was defined as an instance property on ClassicProperties. ' +
638
+ 'Use a static property to define contextType instead.\n' +
639
+ ' in ClassicProperties (at **)',
640
+ 'contextTypes was defined as an instance property on ClassicProperties. ' +
641
+ 'Use a static property to define contextTypes instead.\n' +
642
+ ' in ClassicProperties (at **)',
643
]);
644
expect(getInitialStateWasCalled).toBe(false);
645
expect(getDefaultPropsWasCalled).toBe(false);
622
- }
646
+ },
647
);
648
}
649
@@ -638,63 +662,73 @@ describe('ReactTypeScriptClass', function() {
662
}
663
664
test(React.createElement(Example), 'SPAN', 'foo');
641
- }
665
+ },
666
);
667
644
- it('should warn when misspelling shouldComponentUpdate', function() {
645
- expect(() =>
646
- test(React.createElement(MisspelledComponent1), 'SPAN', 'foo')
647
- ).toErrorDev(
648
- '' +
649
- 'MisspelledComponent1 has a method called componentShouldUpdate(). Did ' +
668
+ it('should warn when misspelling shouldComponentUpdate', function () {
669
+ test(React.createElement(MisspelledComponent1), 'SPAN', 'foo');
670
+ assertConsoleErrorDev([
671
+ 'MisspelledComponent1 has a method called componentShouldUpdate(). Did ' +
672
'you mean shouldComponentUpdate()? The name is phrased as a question ' +
651
- 'because the function is expected to return a value.'
652
- );
673
+ 'because the function is expected to return a value.\n' +
674
+ ' in MisspelledComponent1 (at **)',
675
+ ]);
676
});
677
655
- it('should warn when misspelling componentWillReceiveProps', function() {
656
- expect(() =>
657
- test(React.createElement(MisspelledComponent2), 'SPAN', 'foo')
658
- ).toErrorDev(
659
- '' +
660
- 'MisspelledComponent2 has a method called componentWillRecieveProps(). ' +
661
- 'Did you mean componentWillReceiveProps()?'
662
- );
678
+ it('should warn when misspelling componentWillReceiveProps', function () {
679
+ test(React.createElement(MisspelledComponent2), 'SPAN', 'foo');
680
+ assertConsoleErrorDev([
681
+ 'MisspelledComponent2 has a method called componentWillRecieveProps(). ' +
682
+ 'Did you mean componentWillReceiveProps()?\n' +
683
+ ' in MisspelledComponent2 (at **)',
684
+ ]);
685
});
686
665
- it('should warn when misspelling UNSAFE_componentWillReceiveProps', function() {
666
- expect(() =>
667
- test(React.createElement(MisspelledComponent3), 'SPAN', 'foo')
668
- ).toErrorDev(
669
- '' +
670
- 'MisspelledComponent3 has a method called UNSAFE_componentWillRecieveProps(). ' +
671
- 'Did you mean UNSAFE_componentWillReceiveProps()?'
672
- );
687
+ it('should warn when misspelling UNSAFE_componentWillReceiveProps', function () {
688
+ test(React.createElement(MisspelledComponent3), 'SPAN', 'foo');
689
+ assertConsoleErrorDev([
690
+ 'MisspelledComponent3 has a method called UNSAFE_componentWillRecieveProps(). ' +
691
+ 'Did you mean UNSAFE_componentWillReceiveProps()?\n' +
692
+ ' in MisspelledComponent3 (at **)',
693
+ ]);
694
});
695
675
- it('should throw AND warn when trying to access classic APIs', function() {
696
+ it('should throw AND warn when trying to access classic APIs', function () {
697
const ref = React.createRef();
698
test(React.createElement(Inner, {name: 'foo', ref: ref}), 'DIV', 'foo');
678
- expect(() =>
679
- expect(() => ref.current.replaceState({})).toThrow()
680
- ).toWarnDev(
681
- 'replaceState(...) is deprecated in plain JavaScript React classes',
682
- {withoutStack: true}
699
+ expect(() => ref.current.replaceState({})).toThrow();
700
+ assertConsoleWarnDev(
701
+ [
702
+ 'replaceState(...) is deprecated in plain JavaScript React classes. ' +
703
+ 'Refactor your code to use setState instead (see https://github.com/facebook/react/issues/3236).',
704
+ ],
705
+ {withoutStack: true},
706
);
684
- expect(() =>
685
- expect(() => ref.current.isMounted()).toThrow()
686
- ).toWarnDev(
687
- 'isMounted(...) is deprecated in plain JavaScript React classes',
688
- {withoutStack: true}
707
+ expect(() => ref.current.isMounted()).toThrow();
708
+ assertConsoleWarnDev(
709
+ [
710
+ 'isMounted(...) is deprecated in plain JavaScript React classes. ' +
711
+ 'Instead, make sure to clean up subscriptions and pending requests in ' +
712
+ 'componentWillUnmount to prevent memory leaks.',
713
+ ],
714
+ {withoutStack: true},
715
);
716
});
717
718
if (!ReactFeatureFlags.disableLegacyContext) {
719
it('supports this.context passed via getChildContext', () => {
694
- expect(() => test(React.createElement(ProvideContext), 'DIV', 'bar-through-context')).toErrorDev([
695
- 'ProvideContext uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
696
- 'ReadContext uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
697
-] );
720
+ test(React.createElement(ProvideContext), 'DIV', 'bar-through-context');
721
+ assertConsoleErrorDev([
722
+ 'ProvideContext uses the legacy childContextTypes API which will soon be removed. ' +
723
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
724
+ ' in ProvideContext (at **)',
725
+ 'ReadContext uses the legacy contextTypes API which will soon be removed. ' +
726
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
727
+ (ReactFeatureFlags.enableOwnerStacks
728
+ ? ' in ProvideContext.Object.<anonymous>.ProvideContext (at **)'
729
+ : ' in ReadContext (at **)\n') +
730
+ ' in ProvideContext (at **)',
731
+ ]);
732
});
733
}
734
});
packages/react/src/__tests__/createReactClassIntegration-test.js
+281
-198
@@ -11,6 +11,7 @@
11
12
let act;
13
let assertConsoleErrorDev;
14
+let assertConsoleWarnDev;
15
16
let PropTypes;
17
let React;
@@ -20,7 +21,11 @@ let createReactClass;
21
describe('create-react-class-integration', () => {
22
beforeEach(() => {
23
jest.resetModules();
23
- ({act, assertConsoleErrorDev} = require('internal-test-utils'));
24
+ ({
25
+ act,
26
+ assertConsoleErrorDev,
27
+ assertConsoleWarnDev,
28
+ } = require('internal-test-utils'));
29
PropTypes = require('prop-types');
30
React = require('react');
31
ReactDOMClient = require('react-dom/client');
@@ -53,124 +58,130 @@ describe('create-react-class-integration', () => {
58
});
59
60
it('should warn on invalid prop types', () => {
56
- expect(() =>
57
- createReactClass({
58
- displayName: 'Component',
59
- propTypes: {
60
- prop: null,
61
- },
62
- render: function () {
63
- return <span>{this.props.prop}</span>;
64
- },
65
- }),
66
- ).toErrorDev(
67
- 'Component: prop type `prop` is invalid; ' +
68
- 'it must be a function, usually from React.PropTypes.',
61
+ createReactClass({
62
+ displayName: 'Component',
63
+ propTypes: {
64
+ prop: null,
65
+ },
66
+ render: function () {
67
+ return <span>{this.props.prop}</span>;
68
+ },
69
+ });
70
+ assertConsoleErrorDev(
71
+ [
72
+ 'Warning: Component: prop type `prop` is invalid; ' +
73
+ 'it must be a function, usually from React.PropTypes.',
74
+ ],
75
{withoutStack: true},
76
);
77
});
78
79
it('should warn on invalid context types', () => {
74
- expect(() =>
75
- createReactClass({
76
- displayName: 'Component',
77
- contextTypes: {
78
- prop: null,
79
- },
80
- render: function () {
81
- return <span>{this.props.prop}</span>;
82
- },
83
- }),
84
- ).toErrorDev(
85
- 'Component: context type `prop` is invalid; ' +
86
- 'it must be a function, usually from React.PropTypes.',
80
+ createReactClass({
81
+ displayName: 'Component',
82
+ contextTypes: {
83
+ prop: null,
84
+ },
85
+ render: function () {
86
+ return <span>{this.props.prop}</span>;
87
+ },
88
+ });
89
+ assertConsoleErrorDev(
90
+ [
91
+ 'Warning: Component: context type `prop` is invalid; ' +
92
+ 'it must be a function, usually from React.PropTypes.',
93
+ ],
94
{withoutStack: true},
95
);
96
});
97
98
it('should throw on invalid child context types', () => {
92
- expect(() =>
93
- createReactClass({
94
- displayName: 'Component',
95
- childContextTypes: {
96
- prop: null,
97
- },
98
- render: function () {
99
- return <span>{this.props.prop}</span>;
100
- },
101
- }),
102
- ).toErrorDev(
103
- 'Component: child context type `prop` is invalid; ' +
104
- 'it must be a function, usually from React.PropTypes.',
99
+ createReactClass({
100
+ displayName: 'Component',
101
+ childContextTypes: {
102
+ prop: null,
103
+ },
104
+ render: function () {
105
+ return <span>{this.props.prop}</span>;
106
+ },
107
+ });
108
+ assertConsoleErrorDev(
109
+ [
110
+ 'Warning: Component: child context type `prop` is invalid; it must be a function, usually from React.PropTypes.',
111
+ ],
112
{withoutStack: true},
113
);
114
});
115
116
it('should warn when misspelling shouldComponentUpdate', () => {
110
- expect(() =>
111
- createReactClass({
112
- componentShouldUpdate: function () {
113
- return false;
114
- },
115
- render: function () {
116
- return <div />;
117
- },
118
- }),
119
- ).toErrorDev(
120
- 'A component has a method called componentShouldUpdate(). Did you ' +
121
- 'mean shouldComponentUpdate()? The name is phrased as a question ' +
122
- 'because the function is expected to return a value.',
117
+ createReactClass({
118
+ componentShouldUpdate: function () {
119
+ return false;
120
+ },
121
+ render: function () {
122
+ return <div />;
123
+ },
124
+ });
125
+ assertConsoleErrorDev(
126
+ [
127
+ 'Warning: A component has a method called componentShouldUpdate(). Did you ' +
128
+ 'mean shouldComponentUpdate()? The name is phrased as a question ' +
129
+ 'because the function is expected to return a value.',
130
+ ],
131
{withoutStack: true},
132
);
133
126
- expect(() =>
127
- createReactClass({
128
- displayName: 'NamedComponent',
129
- componentShouldUpdate: function () {
130
- return false;
131
- },
132
- render: function () {
133
- return <div />;
134
- },
135
- }),
136
- ).toErrorDev(
137
- 'NamedComponent has a method called componentShouldUpdate(). Did you ' +
138
- 'mean shouldComponentUpdate()? The name is phrased as a question ' +
139
- 'because the function is expected to return a value.',
134
+ createReactClass({
135
+ displayName: 'NamedComponent',
136
+ componentShouldUpdate: function () {
137
+ return false;
138
+ },
139
+ render: function () {
140
+ return <div />;
141
+ },
142
+ });
143
+ assertConsoleErrorDev(
144
+ [
145
+ 'Warning: NamedComponent has a method called componentShouldUpdate(). Did you ' +
146
+ 'mean shouldComponentUpdate()? The name is phrased as a question ' +
147
+ 'because the function is expected to return a value.',
148
+ ],
149
{withoutStack: true},
150
);
151
});
152
153
it('should warn when misspelling componentWillReceiveProps', () => {
145
- expect(() =>
146
- createReactClass({
147
- componentWillRecieveProps: function () {
148
- return false;
149
- },
150
- render: function () {
151
- return <div />;
152
- },
153
- }),
154
- ).toErrorDev(
155
- 'A component has a method called componentWillRecieveProps(). Did you ' +
156
- 'mean componentWillReceiveProps()?',
154
+ createReactClass({
155
+ componentWillRecieveProps: function () {
156
+ return false;
157
+ },
158
+ render: function () {
159
+ return <div />;
160
+ },
161
+ });
162
+ assertConsoleErrorDev(
163
+ [
164
+ 'Warning: A component has a method called componentWillRecieveProps(). Did you ' +
165
+ 'mean componentWillReceiveProps()?',
166
+ ],
167
{withoutStack: true},
168
);
169
});
170
171
it('should warn when misspelling UNSAFE_componentWillReceiveProps', () => {
162
- expect(() =>
163
- createReactClass({
164
- UNSAFE_componentWillRecieveProps: function () {
165
- return false;
166
- },
167
- render: function () {
168
- return <div />;
169
- },
170
- }),
171
- ).toErrorDev(
172
- 'A component has a method called UNSAFE_componentWillRecieveProps(). ' +
173
- 'Did you mean UNSAFE_componentWillReceiveProps()?',
172
+ createReactClass({
173
+ UNSAFE_componentWillRecieveProps: function () {
174
+ return false;
175
+ },
176
+ render: function () {
177
+ return <div />;
178
+ },
179
+ });
180
+ assertConsoleErrorDev(
181
+ [
182
+ 'Warning: A component has a method called UNSAFE_componentWillRecieveProps(). ' +
183
+ 'Did you mean UNSAFE_componentWillReceiveProps()?',
184
+ ],
185
{withoutStack: true},
186
);
187
});
@@ -201,23 +212,22 @@ describe('create-react-class-integration', () => {
212
// TODO: Consider actually moving these to statics or drop this unit test.
213
// eslint-disable-next-line jest/no-disabled-tests
214
it.skip('should warn when using deprecated non-static spec keys', () => {
204
- expect(() =>
205
- createReactClass({
206
- mixins: [{}],
207
- propTypes: {
208
- foo: PropTypes.string,
209
- },
210
- contextTypes: {
211
- foo: PropTypes.string,
212
- },
213
- childContextTypes: {
214
- foo: PropTypes.string,
215
- },
216
- render: function () {
217
- return <div />;
218
- },
219
- }),
220
- ).toErrorDev([
215
+ createReactClass({
216
+ mixins: [{}],
217
+ propTypes: {
218
+ foo: PropTypes.string,
219
+ },
220
+ contextTypes: {
221
+ foo: PropTypes.string,
222
+ },
223
+ childContextTypes: {
224
+ foo: PropTypes.string,
225
+ },
226
+ render: function () {
227
+ return <div />;
228
+ },
229
+ });
230
+ assertConsoleErrorDev([
231
'`mixins` is now a static property and should ' +
232
'be defined inside "statics".',
233
'`propTypes` is now a static property and should ' +
@@ -399,9 +409,12 @@ describe('create-react-class-integration', () => {
409
},
410
});
411
402
- expect(() => expect(() => Component()).toThrow()).toErrorDev(
403
- 'Something is calling a React component directly. Use a ' +
404
- 'factory or JSX instead. See: https://fb.me/react-legacyfactory',
412
+ expect(() => Component()).toThrow();
413
+ assertConsoleErrorDev(
414
+ [
415
+ 'Warning: Something is calling a React component directly. Use a ' +
416
+ 'factory or JSX instead. See: https://fb.me/react-legacyfactory',
417
+ ],
418
{withoutStack: true},
419
);
420
});
@@ -504,15 +517,15 @@ describe('create-react-class-integration', () => {
517
return <div />;
518
},
519
});
507
- await expect(async () => {
508
- const root = ReactDOMClient.createRoot(document.createElement('div'));
509
- await act(() => {
510
- root.render(<Foo foo="foo" />);
511
- });
512
- }).toErrorDev(
520
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
521
+ await act(() => {
522
+ root.render(<Foo foo="foo" />);
523
+ });
524
+ assertConsoleErrorDev([
525
'Foo: getDerivedStateFromProps() is defined as an instance method ' +
514
- 'and will be ignored. Instead, declare it as a static method.',
515
- );
526
+ 'and will be ignored. Instead, declare it as a static method.\n' +
527
+ ' in Foo (at **)',
528
+ ]);
529
});
530
531
it('warns if getDerivedStateFromError is not static', async () => {
@@ -525,15 +538,15 @@ describe('create-react-class-integration', () => {
538
return <div />;
539
},
540
});
528
- await expect(async () => {
529
- const root = ReactDOMClient.createRoot(document.createElement('div'));
530
- await act(() => {
531
- root.render(<Foo foo="foo" />);
532
- });
533
- }).toErrorDev(
541
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
542
+ await act(() => {
543
+ root.render(<Foo foo="foo" />);
544
+ });
545
+ assertConsoleErrorDev([
546
'Foo: getDerivedStateFromError() is defined as an instance method ' +
535
- 'and will be ignored. Instead, declare it as a static method.',
536
- );
547
+ 'and will be ignored. Instead, declare it as a static method.\n' +
548
+ ' in Foo (at **)',
549
+ ]);
550
});
551
552
it('warns if getSnapshotBeforeUpdate is static', async () => {
@@ -548,15 +561,15 @@ describe('create-react-class-integration', () => {
561
return <div />;
562
},
563
});
551
- await expect(async () => {
552
- const root = ReactDOMClient.createRoot(document.createElement('div'));
553
- await act(() => {
554
- root.render(<Foo foo="foo" />);
555
- });
556
- }).toErrorDev(
564
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
565
+ await act(() => {
566
+ root.render(<Foo foo="foo" />);
567
+ });
568
+ assertConsoleErrorDev([
569
'Foo: getSnapshotBeforeUpdate() is defined as a static method ' +
558
- 'and will be ignored. Instead, declare it as an instance method.',
559
- );
570
+ 'and will be ignored. Instead, declare it as an instance method.\n' +
571
+ ' in Foo (at **)',
572
+ ]);
573
});
574
575
it('should warn if state is not properly initialized before getDerivedStateFromProps', async () => {
@@ -571,17 +584,17 @@ describe('create-react-class-integration', () => {
584
return null;
585
},
586
});
574
- await expect(async () => {
575
- const root = ReactDOMClient.createRoot(document.createElement('div'));
576
- await act(() => {
577
- root.render(<Component />);
578
- });
579
- }).toErrorDev(
587
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
588
+ await act(() => {
589
+ root.render(<Component />);
590
+ });
591
+ assertConsoleErrorDev([
592
'`Component` uses `getDerivedStateFromProps` but its initial state is ' +
593
'null. This is not recommended. Instead, define the initial state by ' +
594
'assigning an object to `this.state` in the constructor of `Component`. ' +
583
- 'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.',
584
- );
595
+ 'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.\n' +
596
+ ' in Component (at **)',
597
+ ]);
598
});
599
600
it('should not invoke deprecated lifecycles (cWM/cWRP/cWU) if new static gDSFP is present', async () => {
@@ -609,30 +622,52 @@ describe('create-react-class-integration', () => {
622
});
623
Component.displayName = 'Component';
624
612
- await expect(async () => {
613
- await expect(async () => {
614
- const root = ReactDOMClient.createRoot(document.createElement('div'));
615
- await act(() => {
616
- root.render(<Component />);
617
- });
618
- }).toErrorDev(
619
- 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
620
- 'Component uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
621
- ' componentWillMount\n' +
622
- ' componentWillReceiveProps\n' +
623
- ' componentWillUpdate\n\n' +
624
- 'The above lifecycles should be removed. Learn more about this warning here:\n' +
625
- 'https://react.dev/link/unsafe-component-lifecycles',
626
- );
627
- }).toWarnDev(
625
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
626
+ await act(() => {
627
+ root.render(<Component />);
628
+ });
629
+ assertConsoleErrorDev([
630
+ 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
631
+ 'Component uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
632
+ ' componentWillMount\n' +
633
+ ' componentWillReceiveProps\n' +
634
+ ' componentWillUpdate\n\n' +
635
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
636
+ 'https://react.dev/link/unsafe-component-lifecycles\n' +
637
+ ' in Component (at **)',
638
+ ]);
639
+ assertConsoleWarnDev(
640
[
629
- 'componentWillMount has been renamed',
630
- 'componentWillReceiveProps has been renamed',
631
- 'componentWillUpdate has been renamed',
641
+ 'componentWillMount has been renamed, and is not recommended for use. ' +
642
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
643
+ '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
644
+ '* Rename componentWillMount to UNSAFE_componentWillMount to suppress ' +
645
+ 'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' +
646
+ 'To rename all deprecated lifecycles to their new names, you can run ' +
647
+ '`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
648
+ '\nPlease update the following components: Component',
649
+ 'componentWillReceiveProps has been renamed, and is not recommended for use. ' +
650
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
651
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
652
+ "* If you're updating state whenever props change, refactor your " +
653
+ 'code to use memoization techniques or move it to ' +
654
+ 'static getDerivedStateFromProps. Learn more at: https://react.dev/link/derived-state\n' +
655
+ '* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress ' +
656
+ 'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' +
657
+ 'To rename all deprecated lifecycles to their new names, you can run ' +
658
+ '`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
659
+ '\nPlease update the following components: Component',
660
+ 'componentWillUpdate has been renamed, and is not recommended for use. ' +
661
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
662
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
663
+ '* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress ' +
664
+ 'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' +
665
+ 'To rename all deprecated lifecycles to their new names, you can run ' +
666
+ '`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
667
+ '\nPlease update the following components: Component',
668
],
669
{withoutStack: true},
670
);
635
- const root = ReactDOMClient.createRoot(document.createElement('div'));
671
await act(() => {
672
root.render(<Component foo={1} />);
673
});
@@ -659,26 +694,49 @@ describe('create-react-class-integration', () => {
694
});
695
Component.displayName = 'Component';
696
662
- await expect(async () => {
663
- await expect(async () => {
664
- const root = ReactDOMClient.createRoot(document.createElement('div'));
665
- await act(() => {
666
- root.render(<Component />);
667
- });
668
- }).toErrorDev(
669
- 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
670
- 'Component uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
671
- ' componentWillMount\n' +
672
- ' componentWillReceiveProps\n' +
673
- ' componentWillUpdate\n\n' +
674
- 'The above lifecycles should be removed. Learn more about this warning here:\n' +
675
- 'https://react.dev/link/unsafe-component-lifecycles',
676
- );
677
- }).toWarnDev(
697
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
698
+ await act(() => {
699
+ root.render(<Component />);
700
+ });
701
+ assertConsoleErrorDev([
702
+ 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
703
+ 'Component uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
704
+ ' componentWillMount\n' +
705
+ ' componentWillReceiveProps\n' +
706
+ ' componentWillUpdate\n\n' +
707
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
708
+ 'https://react.dev/link/unsafe-component-lifecycles\n' +
709
+ ' in Component (at **)',
710
+ ]);
711
+ assertConsoleWarnDev(
712
[
679
- 'componentWillMount has been renamed',
680
- 'componentWillReceiveProps has been renamed',
681
- 'componentWillUpdate has been renamed',
713
+ 'componentWillMount has been renamed, and is not recommended for use. ' +
714
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
715
+ '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
716
+ '* Rename componentWillMount to UNSAFE_componentWillMount to suppress ' +
717
+ 'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' +
718
+ 'To rename all deprecated lifecycles to their new names, you can run ' +
719
+ '`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
720
+ '\nPlease update the following components: Component',
721
+ 'componentWillReceiveProps has been renamed, and is not recommended for use. ' +
722
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
723
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
724
+ "* If you're updating state whenever props change, refactor your " +
725
+ 'code to use memoization techniques or move it to ' +
726
+ 'static getDerivedStateFromProps. Learn more at: https://react.dev/link/derived-state\n' +
727
+ '* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress ' +
728
+ 'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' +
729
+ 'To rename all deprecated lifecycles to their new names, you can run ' +
730
+ '`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
731
+ '\nPlease update the following components: Component',
732
+ 'componentWillUpdate has been renamed, and is not recommended for use. ' +
733
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
734
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
735
+ '* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress ' +
736
+ 'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' +
737
+ 'To rename all deprecated lifecycles to their new names, you can run ' +
738
+ '`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
739
+ '\nPlease update the following components: Component',
740
],
741
{withoutStack: true},
742
);
@@ -721,15 +779,38 @@ describe('create-react-class-integration', () => {
779
780
const root = ReactDOMClient.createRoot(document.createElement('div'));
781
724
- await expect(async () => {
725
- await act(() => {
726
- root.render(<Component foo="bar" />);
727
- });
728
- }).toWarnDev(
782
+ await act(() => {
783
+ root.render(<Component foo="bar" />);
784
+ });
785
+ assertConsoleWarnDev(
786
[
730
- 'componentWillMount has been renamed',
731
- 'componentWillReceiveProps has been renamed',
732
- 'componentWillUpdate has been renamed',
787
+ 'componentWillMount has been renamed, and is not recommended for use. ' +
788
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
789
+ '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
790
+ '* Rename componentWillMount to UNSAFE_componentWillMount to suppress ' +
791
+ 'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' +
792
+ 'To rename all deprecated lifecycles to their new names, you can run ' +
793
+ '`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
794
+ '\nPlease update the following components: Component',
795
+ 'componentWillReceiveProps has been renamed, and is not recommended for use. ' +
796
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
797
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
798
+ "* If you're updating state whenever props change, refactor your " +
799
+ 'code to use memoization techniques or move it to ' +
800
+ 'static getDerivedStateFromProps. Learn more at: https://react.dev/link/derived-state\n' +
801
+ '* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress ' +
802
+ 'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' +
803
+ 'To rename all deprecated lifecycles to their new names, you can run ' +
804
+ '`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
805
+ '\nPlease update the following components: Component',
806
+ 'componentWillUpdate has been renamed, and is not recommended for use. ' +
807
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
808
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
809
+ '* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress ' +
810
+ 'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' +
811
+ 'To rename all deprecated lifecycles to their new names, you can run ' +
812
+ '`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
813
+ '\nPlease update the following components: Component',
814
],
815
{withoutStack: true},
816
);
@@ -803,14 +884,16 @@ describe('create-react-class-integration', () => {
884
885
const root = ReactDOMClient.createRoot(document.createElement('div'));
886
806
- await expect(async () => {
807
- await act(() => {
808
- root.render(<Component />);
809
- });
810
- }).toErrorDev(
811
- 'MyComponent: isMounted is deprecated. Instead, make sure to ' +
812
- 'clean up subscriptions and pending requests in componentWillUnmount ' +
813
- 'to prevent memory leaks.',
887
+ await act(() => {
888
+ root.render(<Component />);
889
+ });
890
+ assertConsoleErrorDev(
891
+ [
892
+ 'Warning: MyComponent: isMounted is deprecated. Instead, make sure to ' +
893
+ 'clean up subscriptions and pending requests in componentWillUnmount ' +
894
+ 'to prevent memory leaks.\n' +
895
+ ' in MyComponent (at **)',
896
+ ],
897
// This now has a component stack even though it's part of a third-party library.
898
);
899
scripts/jest/spec-equivalence-reporter/setupTests.js
+10
@@ -7,6 +7,11 @@
7
8
'use strict';
9
10
+const {
11
+ patchConsoleMethods,
12
+ resetAllUnexpectedConsoleCalls,
13
+ flushAllUnexpectedConsoleCalls,
14
+} = require('internal-test-utils/consoleMock');
15
const spyOn = jest.spyOn;
16
17
// Spying on console methods in production builds can mask errors.
@@ -36,6 +41,11 @@ global.spyOnProd = function (...args) {
41
}
42
};
43
44
+// Patch the console to assert that all console error/warn/log calls assert.
45
+patchConsoleMethods({includeLog: !!process.env.CI});
46
+beforeEach(resetAllUnexpectedConsoleCalls);
47
+afterEach(flushAllUnexpectedConsoleCalls);
48
+
49
expect.extend({
50
...require('../matchers/reactTestMatchers'),
51
...require('../matchers/toThrow'),