@samitouri / QOS-React-1 / commits / 94867f33be

[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'),