diff --git a/packages/react-dom/src/__tests__/CSSPropertyOperations-test.js b/packages/react-dom/src/__tests__/CSSPropertyOperations-test.js index bda95b8434d46..b5671cf759cef 100644 --- a/packages/react-dom/src/__tests__/CSSPropertyOperations-test.js +++ b/packages/react-dom/src/__tests__/CSSPropertyOperations-test.js @@ -13,6 +13,8 @@ const React = require('react'); const ReactDOMClient = require('react-dom/client'); const ReactDOMServer = require('react-dom/server'); const act = require('internal-test-utils').act; +const assertConsoleErrorDev = + require('internal-test-utils').assertConsoleErrorDev; describe('CSSPropertyOperations', () => { it('should automatically append `px` to relevant styles', () => { @@ -103,15 +105,14 @@ describe('CSSPropertyOperations', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ 'Unsupported style property background-color. Did you mean backgroundColor?' + '\n in div (at **)' + '\n in Comp (at **)', - ); + ]); }); it('should warn when updating hyphenated style names', async () => { @@ -132,11 +133,10 @@ describe('CSSPropertyOperations', () => { await act(() => { root.render(); }); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev([ + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ 'Unsupported style property -ms-transform. Did you mean msTransform?' + '\n in div (at **)' + '\n in Comp (at **)', @@ -165,11 +165,10 @@ describe('CSSPropertyOperations', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev([ + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ // msTransform is correct already and shouldn't warn 'Unsupported vendor-prefixed style property oTransform. ' + 'Did you mean OTransform?' + @@ -202,11 +201,10 @@ describe('CSSPropertyOperations', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev([ + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ "Style property values shouldn't contain a semicolon. " + 'Try "backgroundColor: blue" instead.' + '\n in div (at **)' + @@ -229,15 +227,14 @@ describe('CSSPropertyOperations', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ '`NaN` is an invalid value for the `fontSize` css style property.' + '\n in div (at **)' + '\n in Comp (at **)', - ); + ]); }); it('should not warn when setting CSS custom properties', async () => { @@ -265,15 +262,14 @@ describe('CSSPropertyOperations', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ '`Infinity` is an invalid value for the `fontSize` css style property.' + '\n in div (at **)' + '\n in Comp (at **)', - ); + ]); }); it('should not add units to CSS custom properties', async () => { diff --git a/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js b/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js index cecc71b45a6c0..ef09e49bf36c1 100644 --- a/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js +++ b/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js @@ -1333,7 +1333,8 @@ describe('DOMPropertyOperations', () => { }); assertConsoleErrorDev([ - 'The `popoverTarget` prop expects the ID of an Element as a string. Received HTMLDivElement {} instead.', + 'The `popoverTarget` prop expects the ID of an Element as a string. Received HTMLDivElement {} instead.\n' + + ' in button (at **)', ]); // Dedupe warning @@ -1375,13 +1376,17 @@ describe('DOMPropertyOperations', () => { expect(container.firstChild.getAttribute('value')).toBe('foo'); } expect(container.firstChild.value).toBe('foo'); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( - 'A component is changing a controlled input to be uncontrolled', - ); + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ + 'A component is changing a controlled input to be uncontrolled. ' + + 'This is likely caused by the value changing from a defined to undefined, ' + + 'which should not happen. Decide between using a controlled or uncontrolled ' + + 'input element for the lifetime of the component. ' + + 'More info: https://react.dev/link/controlled-components\n' + + ' in input (at **)', + ]); if (disableInputAttributeSyncing) { expect(container.firstChild.hasAttribute('value')).toBe(false); } else { diff --git a/packages/react-dom/src/__tests__/InvalidEventListeners-test.js b/packages/react-dom/src/__tests__/InvalidEventListeners-test.js index d7045d2756ad4..8bff125c82c32 100644 --- a/packages/react-dom/src/__tests__/InvalidEventListeners-test.js +++ b/packages/react-dom/src/__tests__/InvalidEventListeners-test.js @@ -15,13 +15,14 @@ describe('InvalidEventListeners', () => { let React; let ReactDOMClient; let act; + let assertConsoleErrorDev; let container; beforeEach(() => { jest.resetModules(); React = require('react'); ReactDOMClient = require('react-dom/client'); - act = require('internal-test-utils').act; + ({act, assertConsoleErrorDev} = require('internal-test-utils')); container = document.createElement('div'); document.body.appendChild(container); @@ -34,13 +35,13 @@ describe('InvalidEventListeners', () => { it('should prevent non-function listeners, at dispatch', async () => { const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(
); - }); - }).toErrorDev( - 'Expected `onClick` listener to be a function, instead got a value of `string` type.', - ); + await act(() => { + root.render(
); + }); + assertConsoleErrorDev([ + 'Expected `onClick` listener to be a function, instead got a value of `string` type.\n' + + ' in div (at **)', + ]); const node = container.firstChild; console.error = jest.fn(); diff --git a/packages/react-dom/src/__tests__/ReactChildReconciler-test.js b/packages/react-dom/src/__tests__/ReactChildReconciler-test.js index 1c1475534f3e0..ba0c028fa863a 100644 --- a/packages/react-dom/src/__tests__/ReactChildReconciler-test.js +++ b/packages/react-dom/src/__tests__/ReactChildReconciler-test.js @@ -15,6 +15,7 @@ let React; let ReactDOMClient; let act; +let assertConsoleErrorDev; describe('ReactChildReconciler', () => { beforeEach(() => { @@ -22,7 +23,7 @@ describe('ReactChildReconciler', () => { React = require('react'); ReactDOMClient = require('react-dom/client'); - act = require('internal-test-utils').act; + ({act, assertConsoleErrorDev} = require('internal-test-utils')); }); function createIterable(array) { @@ -62,15 +63,21 @@ describe('ReactChildReconciler', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render( -
-

{iterableFunction}

-
, - ); - }); - }).toErrorDev('Functions are not valid as a React child'); + await act(() => { + root.render( +
+

{iterableFunction}

+
, + ); + }); + assertConsoleErrorDev([ + 'Functions are not valid as a React child. ' + + 'This may happen if you return fn instead of from render. ' + + 'Or maybe you meant to call this function rather than return it.\n' + + '

{fn}

\n' + + ' in h1 (at **)' + + (gate('enableOwnerStacks') ? '' : '\n in div (at **)'), + ]); const node = container.firstChild; expect(node.innerHTML).toContain(''); // h1 @@ -85,16 +92,18 @@ describe('ReactChildReconciler', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( - 'Keys should be unique so that components maintain their identity ' + - 'across updates. Non-unique keys may cause children to be ' + - 'duplicated and/or omitted — the behavior is unsupported and ' + - 'could change in a future version.', - ); + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ + 'Encountered two children with the same key, `1`. ' + + 'Keys should be unique so that components maintain their identity across updates. ' + + 'Non-unique keys may cause children to be duplicated and/or omitted — ' + + 'the behavior is unsupported and could change in a future version.\n' + + (gate('enableOwnerStacks') ? '' : ' in div (at **)\n') + + ' in div (at **)\n' + + ' in Component (at **)', + ]); }); it('warns for duplicated array keys with component stack info', async () => { @@ -118,11 +127,10 @@ describe('ReactChildReconciler', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ 'Encountered two children with the same key, `1`. ' + 'Keys should be unique so that components maintain their identity ' + 'across updates. Non-unique keys may cause children to be ' + @@ -135,7 +143,7 @@ describe('ReactChildReconciler', () => { ? '' : ' in Parent (at **)\n') + ' in GrandParent (at **)', - ); + ]); }); it('warns for duplicated iterable keys', async () => { @@ -147,16 +155,19 @@ describe('ReactChildReconciler', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( - 'Keys should be unique so that components maintain their identity ' + + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ + 'Encountered two children with the same key, `1`. ' + + 'Keys should be unique so that components maintain their identity ' + 'across updates. Non-unique keys may cause children to be ' + 'duplicated and/or omitted — the behavior is unsupported and ' + - 'could change in a future version.', - ); + 'could change in a future version.\n' + + ' in div (at **)\n' + + (gate(flags => flags.enableOwnerStacks) ? '' : ' in div (at **)\n') + + ' in Component (at **)', + ]); }); it('warns for duplicated iterable keys with component stack info', async () => { @@ -180,11 +191,10 @@ describe('ReactChildReconciler', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ 'Encountered two children with the same key, `1`. ' + 'Keys should be unique so that components maintain their identity ' + 'across updates. Non-unique keys may cause children to be ' + @@ -197,6 +207,6 @@ describe('ReactChildReconciler', () => { ? '' : ' in Parent (at **)\n') + ' in GrandParent (at **)', - ); + ]); }); }); diff --git a/packages/react-dom/src/__tests__/ReactComponent-test.js b/packages/react-dom/src/__tests__/ReactComponent-test.js index 6459114697540..c59ba61e01c44 100644 --- a/packages/react-dom/src/__tests__/ReactComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactComponent-test.js @@ -411,7 +411,9 @@ describe('ReactComponent', () => { assertConsoleErrorDev( [ 'React.jsx: type is invalid -- expected a string (for built-in components) ' + - 'or a class/function (for composite components) but got: undefined.', + 'or a class/function (for composite components) but got: undefined. ' + + "You likely forgot to export your component from the file it's defined in, " + + 'or you might have mixed up default and named imports.', ], {withoutStack: true}, ); @@ -491,16 +493,11 @@ describe('ReactComponent', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect( - expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( - 'React.jsx: type is invalid -- expected a string (for built-in components) ' + - 'or a class/function (for composite components) but got: undefined.', - ), - ).rejects.toThrowError( + await expect(async () => { + await act(() => { + root.render(); + }); + }).rejects.toThrowError( 'Element type is invalid: expected a string (for built-in components) ' + 'or a class/function (for composite components) but got: undefined.' + (__DEV__ @@ -509,6 +506,26 @@ describe('ReactComponent', () => { '\n\nCheck the render method of `Bar`.' : ''), ); + if (!gate('enableOwnerStacks')) { + assertConsoleErrorDev([ + 'React.jsx: type is invalid -- expected a string (for built-in components) ' + + 'or a class/function (for composite components) but got: undefined.' + + (__DEV__ + ? " You likely forgot to export your component from the file it's defined in, " + + 'or you might have mixed up default and named imports.\n' + + ' in Bar (at **)\n' + + ' in Foo (at **)' + : ''), + 'React.jsx: type is invalid -- expected a string (for built-in components) ' + + 'or a class/function (for composite components) but got: undefined.' + + (__DEV__ + ? " You likely forgot to export your component from the file it's defined in, " + + 'or you might have mixed up default and named imports.\n' + + ' in Bar (at **)\n' + + ' in Foo (at **)' + : ''), + ]); + } }); it('throws if a plain object is used as a child', async () => { @@ -624,17 +641,16 @@ describe('ReactComponent', () => { } const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ 'Functions are not valid as a React child. This may happen if ' + 'you return Foo instead of from render. ' + 'Or maybe you meant to call this function rather than return it.\n' + ' {Foo}\n' + ' in Foo (at **)', - ); + ]); }); it('warns on function as a return value from a class', async () => { @@ -644,19 +660,18 @@ describe('ReactComponent', () => { } } const container = document.createElement('div'); - await expect(async () => { - const root = ReactDOMClient.createRoot(container); + const root = ReactDOMClient.createRoot(container); - await act(() => { - root.render(); - }); - }).toErrorDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ 'Functions are not valid as a React child. This may happen if ' + 'you return Foo instead of from render. ' + 'Or maybe you meant to call this function rather than return it.\n' + ' {Foo}\n' + ' in Foo (at **)', - ); + ]); }); it('warns on function as a child to host component', async () => { @@ -669,11 +684,10 @@ describe('ReactComponent', () => { } const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ 'Functions are not valid as a React child. This may happen if ' + 'you return Foo instead of from render. ' + 'Or maybe you meant to call this function rather than return it.\n' + @@ -683,7 +697,7 @@ describe('ReactComponent', () => { ? '' : ' in div (at **)\n') + ' in Foo (at **)', - ); + ]); }); it('does not warn for function-as-a-child that gets resolved', async () => { @@ -724,11 +738,10 @@ describe('ReactComponent', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); let component; - await expect(async () => { - await act(() => { - root.render( (component = current)} />); - }); - }).toErrorDev([ + await act(() => { + root.render( (component = current)} />); + }); + assertConsoleErrorDev([ 'Functions are not valid as a React child. This may happen if ' + 'you return Foo instead of from render. ' + 'Or maybe you meant to call this function rather than return it.\n' + diff --git a/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js b/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js index 428dac1f1ea9e..75e4cebe80610 100644 --- a/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js +++ b/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js @@ -14,6 +14,8 @@ let act; let React; let ReactDOM; let ReactDOMClient; +let assertConsoleErrorDev; +let assertConsoleWarnDev; const clone = function (o) { return JSON.parse(JSON.stringify(o)); @@ -90,7 +92,11 @@ describe('ReactComponentLifeCycle', () => { beforeEach(() => { jest.resetModules(); - act = require('internal-test-utils').act; + ({ + act, + assertConsoleErrorDev, + assertConsoleWarnDev, + } = require('internal-test-utils')); React = require('react'); ReactDOM = require('react-dom'); @@ -239,15 +245,15 @@ describe('ReactComponentLifeCycle', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ 'StatefulComponent: It is not recommended to assign props directly to state ' + "because updates to props won't be reflected in state. " + - 'In most cases, it is better to use props directly.', - ); + 'In most cases, it is better to use props directly.\n' + + ' in StatefulComponent (at **)', + ]); }); it('should not allow update state inside of getInitialState', async () => { @@ -266,16 +272,16 @@ describe('ReactComponentLifeCycle', () => { let container = document.createElement('div'); let root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ "Can't call setState on a component that is not yet mounted. " + 'This is a no-op, but it might indicate a bug in your application. ' + 'Instead, assign to `this.state` directly or define a `state = {};` ' + - 'class property with the desired state in the StatefulComponent component.', - ); + 'class property with the desired state in the StatefulComponent component.\n' + + ' in StatefulComponent (at **)', + ]); container = document.createElement('div'); root = ReactDOMClient.createRoot(container); @@ -308,11 +314,17 @@ describe('ReactComponentLifeCycle', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(element); - }); - }).toErrorDev('Component is accessing isMounted inside its render()'); + await act(() => { + root.render(element); + }); + assertConsoleErrorDev([ + 'Component is accessing isMounted inside its render() function. ' + + 'render() should be a pure function of props and state. ' + + 'It should never access something that requires stale data ' + + 'from the previous render, such as refs. ' + + 'Move this logic to componentDidMount and componentDidUpdate instead.\n' + + ' in Component (at **)', + ]); expect(instance._isMounted()).toBeTruthy(); }); @@ -340,11 +352,17 @@ describe('ReactComponentLifeCycle', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(element); - }); - }).toErrorDev('Component is accessing isMounted inside its render()'); + await act(() => { + root.render(element); + }); + assertConsoleErrorDev([ + 'Component is accessing isMounted inside its render() function. ' + + 'render() should be a pure function of props and state. ' + + 'It should never access something that requires stale data ' + + 'from the previous render, such as refs. ' + + 'Move this logic to componentDidMount and componentDidUpdate instead.\n' + + ' in Component (at **)', + ]); expect(instance._isMounted()).toBeTruthy(); }); @@ -390,11 +408,17 @@ describe('ReactComponentLifeCycle', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev('Component is accessing findDOMNode inside its render()'); + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ + 'Component is accessing findDOMNode inside its render(). ' + + 'render() should be a pure function of props and state. ' + + 'It should never access something that requires stale data ' + + 'from the previous render, such as refs. ' + + 'Move this logic to componentDidMount and componentDidUpdate instead.\n' + + ' in Component (at **)', + ]); }); it('should carry through each of the phases of setup', async () => { @@ -453,13 +477,17 @@ describe('ReactComponentLifeCycle', () => { const root = ReactDOMClient.createRoot(document.createElement('div')); const instanceRef = React.createRef(); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( - 'LifeCycleComponent is accessing isMounted inside its render() function', - ); + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ + 'LifeCycleComponent is accessing isMounted inside its render() function. ' + + 'render() should be a pure function of props and state. ' + + 'It should never access something that requires stale data ' + + 'from the previous render, such as refs. ' + + 'Move this logic to componentDidMount and componentDidUpdate instead.\n' + + ' in LifeCycleComponent (at **)', + ]); const instance = instanceRef.current; // getInitialState @@ -781,19 +809,45 @@ describe('ReactComponentLifeCycle', () => { } const root = ReactDOMClient.createRoot(document.createElement('div')); - await expect(async () => { - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( - 'Unsafe legacy lifecycles will not be called for components using new component APIs.', - ); - }).toWarnDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ + 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + + 'Component uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' + + ' componentWillMount\n' + + ' componentWillReceiveProps\n' + + ' componentWillUpdate\n\n' + + 'The above lifecycles should be removed. Learn more about this warning here:\n' + + 'https://react.dev/link/unsafe-component-lifecycles\n' + + ' in Component (at **)', + ]); + assertConsoleWarnDev( [ - 'componentWillMount has been renamed', - 'componentWillReceiveProps has been renamed', - 'componentWillUpdate has been renamed', + 'componentWillMount has been renamed, and is not recommended for use. ' + + 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' + + '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' + + '* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. ' + + 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' + + 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' + + 'Please update the following components: Component', + 'componentWillReceiveProps has been renamed, and is not recommended for use. ' + + 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' + + '* Move data fetching code or side effects to componentDidUpdate.\n' + + "* 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\n' + + '* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress this warning in non-strict mode. ' + + 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' + + 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' + + 'Please update the following components: Component', + 'componentWillUpdate has been renamed, and is not recommended for use. ' + + 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' + + '* Move data fetching code or side effects to componentDidUpdate.\n' + + '* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress this warning in non-strict mode. ' + + 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' + + 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' + + 'Please update the following components: Component', ], {withoutStack: true}, ); @@ -821,20 +875,45 @@ describe('ReactComponentLifeCycle', () => { } const root = ReactDOMClient.createRoot(document.createElement('div')); - await expect(async () => { - await expect( - async () => - await act(() => { - root.render(); - }), - ).toErrorDev( - 'Unsafe legacy lifecycles will not be called for components using new component APIs.', - ); - }).toWarnDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ + 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + + 'Component uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' + + ' componentWillMount\n' + + ' componentWillReceiveProps\n' + + ' componentWillUpdate\n\n' + + 'The above lifecycles should be removed. Learn more about this warning here:\n' + + 'https://react.dev/link/unsafe-component-lifecycles\n' + + ' in Component (at **)', + ]); + assertConsoleWarnDev( [ - 'componentWillMount has been renamed', - 'componentWillReceiveProps has been renamed', - 'componentWillUpdate has been renamed', + 'componentWillMount has been renamed, and is not recommended for use. ' + + 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' + + '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' + + '* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. ' + + 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' + + 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' + + 'Please update the following components: Component', + 'componentWillReceiveProps has been renamed, and is not recommended for use. ' + + 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' + + '* Move data fetching code or side effects to componentDidUpdate.\n' + + "* 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\n' + + '* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress this warning in non-strict mode. ' + + 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' + + 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' + + 'Please update the following components: Component', + 'componentWillUpdate has been renamed, and is not recommended for use. ' + + 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' + + '* Move data fetching code or side effects to componentDidUpdate.\n' + + '* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress this warning in non-strict mode. ' + + 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' + + 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' + + 'Please update the following components: Component', ], {withoutStack: true}, ); @@ -865,14 +944,19 @@ describe('ReactComponentLifeCycle', () => { } const root = ReactDOMClient.createRoot(document.createElement('div')); - await expect( - async () => - await act(() => { - root.render(); - }), - ).toErrorDev( - 'Unsafe legacy lifecycles will not be called for components using new component APIs.', - ); + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ + 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + + 'Component uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' + + ' UNSAFE_componentWillMount\n' + + ' UNSAFE_componentWillReceiveProps\n' + + ' UNSAFE_componentWillUpdate\n\n' + + 'The above lifecycles should be removed. Learn more about this warning here:\n' + + 'https://react.dev/link/unsafe-component-lifecycles\n' + + ' in Component (at **)', + ]); await act(() => { root.render(); }); @@ -893,24 +977,35 @@ describe('ReactComponentLifeCycle', () => { } const root = ReactDOMClient.createRoot(document.createElement('div')); - await expect(async () => { - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( - 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + - 'AllLegacyLifecycles uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' + - ' componentWillMount\n' + - ' UNSAFE_componentWillReceiveProps\n' + - ' componentWillUpdate\n\n' + - 'The above lifecycles should be removed. Learn more about this warning here:\n' + - 'https://react.dev/link/unsafe-component-lifecycles', - ); - }).toWarnDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ + 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + + 'AllLegacyLifecycles uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' + + ' componentWillMount\n' + + ' UNSAFE_componentWillReceiveProps\n' + + ' componentWillUpdate\n\n' + + 'The above lifecycles should be removed. Learn more about this warning here:\n' + + 'https://react.dev/link/unsafe-component-lifecycles\n' + + ' in AllLegacyLifecycles (at **)', + ]); + assertConsoleWarnDev( [ - 'componentWillMount has been renamed', - 'componentWillUpdate has been renamed', + 'componentWillMount has been renamed, and is not recommended for use. ' + + 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' + + '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' + + '* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. ' + + 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' + + 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' + + 'Please update the following components: AllLegacyLifecycles', + 'componentWillUpdate has been renamed, and is not recommended for use. ' + + 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' + + '* Move data fetching code or side effects to componentDidUpdate.\n' + + '* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress this warning in non-strict mode. ' + + 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' + + 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' + + 'Please update the following components: AllLegacyLifecycles', ], {withoutStack: true}, ); @@ -926,17 +1021,17 @@ describe('ReactComponentLifeCycle', () => { } } - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + 'WillMount uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' + ' UNSAFE_componentWillMount\n\n' + 'The above lifecycles should be removed. Learn more about this warning here:\n' + - 'https://react.dev/link/unsafe-component-lifecycles', - ); + 'https://react.dev/link/unsafe-component-lifecycles\n' + + ' in WillMount (at **)', + ]); class WillMountAndUpdate extends React.Component { state = {}; @@ -950,23 +1045,30 @@ describe('ReactComponentLifeCycle', () => { } } - await expect(async () => { - await expect( - async () => - await act(() => { - root.render(); - }), - ).toErrorDev( - 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + - 'WillMountAndUpdate uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' + - ' componentWillMount\n' + - ' UNSAFE_componentWillUpdate\n\n' + - 'The above lifecycles should be removed. Learn more about this warning here:\n' + - 'https://react.dev/link/unsafe-component-lifecycles', - ); - }).toWarnDev(['componentWillMount has been renamed'], { - withoutStack: true, + await act(() => { + root.render(); }); + assertConsoleErrorDev([ + 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + + 'WillMountAndUpdate uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' + + ' componentWillMount\n' + + ' UNSAFE_componentWillUpdate\n\n' + + 'The above lifecycles should be removed. Learn more about this warning here:\n' + + 'https://react.dev/link/unsafe-component-lifecycles\n' + + ' in WillMountAndUpdate (at **)', + ]); + assertConsoleWarnDev( + [ + 'componentWillMount has been renamed, and is not recommended for use. ' + + 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' + + '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' + + '* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. ' + + 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' + + 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' + + 'Please update the following components: WillMountAndUpdate', + ], + {withoutStack: true}, + ); class WillReceiveProps extends React.Component { state = {}; @@ -979,21 +1081,34 @@ describe('ReactComponentLifeCycle', () => { } } - await expect(async () => { - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( - 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + - 'WillReceiveProps uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' + - ' componentWillReceiveProps\n\n' + - 'The above lifecycles should be removed. Learn more about this warning here:\n' + - 'https://react.dev/link/unsafe-component-lifecycles', - ); - }).toWarnDev(['componentWillReceiveProps has been renamed'], { - withoutStack: true, + await act(() => { + root.render(); }); + assertConsoleErrorDev([ + 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + + 'WillReceiveProps uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' + + ' componentWillReceiveProps\n\n' + + 'The above lifecycles should be removed. Learn more about this warning here:\n' + + 'https://react.dev/link/unsafe-component-lifecycles\n' + + ' in WillReceiveProps (at **)', + ]); + assertConsoleWarnDev( + [ + 'componentWillReceiveProps has been renamed, and is not recommended for use. ' + + 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' + + '* Move data fetching code or side effects to componentDidUpdate.\n' + + "* 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\n' + + '* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress this warning in non-strict mode. ' + + 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' + + 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' + + 'Please update the following components: WillReceiveProps', + ], + { + withoutStack: true, + }, + ); }); it('should warn about deprecated lifecycles (cWM/cWRP/cWU) if new getSnapshotBeforeUpdate is present', async () => { @@ -1010,24 +1125,35 @@ describe('ReactComponentLifeCycle', () => { } const root = ReactDOMClient.createRoot(document.createElement('div')); - await expect(async () => { - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( - 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + - 'AllLegacyLifecycles uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' + - ' componentWillMount\n' + - ' UNSAFE_componentWillReceiveProps\n' + - ' componentWillUpdate\n\n' + - 'The above lifecycles should be removed. Learn more about this warning here:\n' + - 'https://react.dev/link/unsafe-component-lifecycles', - ); - }).toWarnDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ + 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + + 'AllLegacyLifecycles uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' + + ' componentWillMount\n' + + ' UNSAFE_componentWillReceiveProps\n' + + ' componentWillUpdate\n\n' + + 'The above lifecycles should be removed. Learn more about this warning here:\n' + + 'https://react.dev/link/unsafe-component-lifecycles\n' + + ' in AllLegacyLifecycles (at **)', + ]); + assertConsoleWarnDev( [ - 'componentWillMount has been renamed', - 'componentWillUpdate has been renamed', + 'componentWillMount has been renamed, and is not recommended for use. ' + + 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' + + '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' + + '* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. ' + + 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' + + 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' + + 'Please update the following components: AllLegacyLifecycles', + 'componentWillUpdate has been renamed, and is not recommended for use. ' + + 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' + + '* Move data fetching code or side effects to componentDidUpdate.\n' + + '* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress this warning in non-strict mode. ' + + 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' + + 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' + + 'Please update the following components: AllLegacyLifecycles', ], {withoutStack: true}, ); @@ -1042,17 +1168,17 @@ describe('ReactComponentLifeCycle', () => { } } - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + 'WillMount uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' + ' UNSAFE_componentWillMount\n\n' + 'The above lifecycles should be removed. Learn more about this warning here:\n' + - 'https://react.dev/link/unsafe-component-lifecycles', - ); + 'https://react.dev/link/unsafe-component-lifecycles\n' + + ' in WillMount (at **)', + ]); class WillMountAndUpdate extends React.Component { state = {}; @@ -1065,22 +1191,32 @@ describe('ReactComponentLifeCycle', () => { } } - await expect(async () => { - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( - 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + - 'WillMountAndUpdate uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' + - ' componentWillMount\n' + - ' UNSAFE_componentWillUpdate\n\n' + - 'The above lifecycles should be removed. Learn more about this warning here:\n' + - 'https://react.dev/link/unsafe-component-lifecycles', - ); - }).toWarnDev(['componentWillMount has been renamed'], { - withoutStack: true, + await act(() => { + root.render(); }); + assertConsoleErrorDev([ + 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + + 'WillMountAndUpdate uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' + + ' componentWillMount\n' + + ' UNSAFE_componentWillUpdate\n\n' + + 'The above lifecycles should be removed. Learn more about this warning here:\n' + + 'https://react.dev/link/unsafe-component-lifecycles\n' + + ' in WillMountAndUpdate (at **)', + ]); + assertConsoleWarnDev( + [ + 'componentWillMount has been renamed, and is not recommended for use. ' + + 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' + + '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' + + '* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. ' + + 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' + + 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' + + 'Please update the following components: WillMountAndUpdate', + ], + { + withoutStack: true, + }, + ); class WillReceiveProps extends React.Component { state = {}; @@ -1092,22 +1228,34 @@ describe('ReactComponentLifeCycle', () => { } } - await expect(async () => { - await expect( - async () => - await act(() => { - root.render(); - }), - ).toErrorDev( - 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + - 'WillReceiveProps uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' + - ' componentWillReceiveProps\n\n' + - 'The above lifecycles should be removed. Learn more about this warning here:\n' + - 'https://react.dev/link/unsafe-component-lifecycles', - ); - }).toWarnDev(['componentWillReceiveProps has been renamed'], { - withoutStack: true, + await act(() => { + root.render(); }); + assertConsoleErrorDev([ + 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + + 'WillReceiveProps uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' + + ' componentWillReceiveProps\n\n' + + 'The above lifecycles should be removed. Learn more about this warning here:\n' + + 'https://react.dev/link/unsafe-component-lifecycles\n' + + ' in WillReceiveProps (at **)', + ]); + assertConsoleWarnDev( + [ + 'componentWillReceiveProps has been renamed, and is not recommended for use. ' + + 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' + + '* Move data fetching code or side effects to componentDidUpdate.\n' + + "* 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\n' + + '* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress this warning in non-strict mode. ' + + 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' + + 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' + + 'Please update the following components: WillReceiveProps', + ], + { + withoutStack: true, + }, + ); }); it('should warn if getDerivedStateFromProps returns undefined', async () => { @@ -1120,14 +1268,14 @@ describe('ReactComponentLifeCycle', () => { } const root = ReactDOMClient.createRoot(document.createElement('div')); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ 'MyComponent.getDerivedStateFromProps(): A valid state object (or null) must ' + - 'be returned. You have returned undefined.', - ); + 'be returned. You have returned undefined.\n' + + ' in MyComponent (at **)', + ]); // De-duped await act(() => { @@ -1146,16 +1294,16 @@ describe('ReactComponentLifeCycle', () => { } const root = ReactDOMClient.createRoot(document.createElement('div')); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ '`MyComponent` uses `getDerivedStateFromProps` but its initial state is ' + 'undefined. This is not recommended. Instead, define the initial state by ' + 'assigning an object to `this.state` in the constructor of `MyComponent`. ' + - 'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.', - ); + 'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.\n' + + ' in MyComponent (at **)', + ]); // De-duped await act(() => { @@ -1191,15 +1339,35 @@ describe('ReactComponentLifeCycle', () => { } const root = ReactDOMClient.createRoot(document.createElement('div')); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toWarnDev( + await act(() => { + root.render(); + }); + assertConsoleWarnDev( [ - 'componentWillMount has been renamed', - 'componentWillReceiveProps has been renamed', - 'componentWillUpdate has been renamed', + 'componentWillMount has been renamed, and is not recommended for use. ' + + 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' + + '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' + + '* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. ' + + 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' + + 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' + + 'Please update the following components: MyComponent', + 'componentWillReceiveProps has been renamed, and is not recommended for use. ' + + 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' + + '* Move data fetching code or side effects to componentDidUpdate.\n' + + "* 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\n' + + '* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress this warning in non-strict mode. ' + + 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' + + 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' + + 'Please update the following components: MyComponent', + 'componentWillUpdate has been renamed, and is not recommended for use. ' + + 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' + + '* Move data fetching code or side effects to componentDidUpdate.\n' + + '* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress this warning in non-strict mode. ' + + 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' + + 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' + + 'Please update the following components: MyComponent', ], {withoutStack: true}, ); @@ -1444,14 +1612,14 @@ describe('ReactComponentLifeCycle', () => { root.render(); }); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ 'MyComponent.getSnapshotBeforeUpdate(): A snapshot value (or null) must ' + - 'be returned. You have returned undefined.', - ); + 'be returned. You have returned undefined.\n' + + ' in MyComponent (at **)', + ]); // De-duped await act(() => { @@ -1470,14 +1638,14 @@ describe('ReactComponentLifeCycle', () => { } const root = ReactDOMClient.createRoot(document.createElement('div')); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ 'MyComponent: getSnapshotBeforeUpdate() should be used with componentDidUpdate(). ' + - 'This component defines getSnapshotBeforeUpdate() only.', - ); + 'This component defines getSnapshotBeforeUpdate() only.\n' + + ' in MyComponent (at **)', + ]); // De-duped await act(() => { @@ -1497,11 +1665,10 @@ describe('ReactComponentLifeCycle', () => { const root = ReactDOMClient.createRoot(document.createElement('div')); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toWarnDev( + await act(() => { + root.render(); + }); + assertConsoleWarnDev( [ `componentWillMount has been renamed, and is not recommended for use. See https://react.dev/link/unsafe-component-lifecycles for details. diff --git a/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js b/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js index 30b5ced5cf2e3..3d5f61c1ed520 100644 --- a/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js @@ -18,6 +18,7 @@ let ReactSharedInternals; let Scheduler; let assertLog; let act; +let assertConsoleErrorDev; describe('ReactCompositeComponent', () => { const hasOwnProperty = Object.prototype.hasOwnProperty; @@ -71,7 +72,7 @@ describe('ReactCompositeComponent', () => { require('react').__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE; Scheduler = require('scheduler'); assertLog = require('internal-test-utils').assertLog; - act = require('internal-test-utils').act; + ({act, assertConsoleErrorDev} = require('internal-test-utils')); }); describe('MorphingComponent', () => { @@ -308,16 +309,16 @@ describe('ReactCompositeComponent', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - expect(() => { - ReactDOM.flushSync(() => { - root.render(); - }); - }).toErrorDev( + ReactDOM.flushSync(() => { + root.render(); + }); + assertConsoleErrorDev([ "Can't call forceUpdate on a component that is not yet mounted. " + 'This is a no-op, but it might indicate a bug in your application. ' + 'Instead, assign to `this.state` directly or define a `state = {};` ' + - 'class property with the desired state in the MyComponent component.', - ); + 'class property with the desired state in the MyComponent component.\n' + + ' in MyComponent (at **)', + ]); // No additional warning should be recorded const container2 = document.createElement('div'); @@ -342,16 +343,16 @@ describe('ReactCompositeComponent', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - expect(() => { - ReactDOM.flushSync(() => { - root.render(); - }); - }).toErrorDev( + ReactDOM.flushSync(() => { + root.render(); + }); + assertConsoleErrorDev([ "Can't call setState on a component that is not yet mounted. " + 'This is a no-op, but it might indicate a bug in your application. ' + 'Instead, assign to `this.state` directly or define a `state = {};` ' + - 'class property with the desired state in the MyComponent component.', - ); + 'class property with the desired state in the MyComponent component.\n' + + ' in MyComponent (at **)', + ]); // No additional warning should be recorded const container2 = document.createElement('div'); @@ -478,16 +479,16 @@ describe('ReactCompositeComponent', () => { } const root = ReactDOMClient.createRoot(container); await expect(async () => { - await expect(async () => { - await act(() => { - root.render(); - }); - }).rejects.toThrow(TypeError); - }).toErrorDev( + await act(() => { + root.render(); + }); + }).rejects.toThrow(TypeError); + assertConsoleErrorDev([ 'The component appears to have a render method, ' + "but doesn't extend React.Component. This is likely to cause errors. " + - 'Change ClassWithRenderNotExtended to extend React.Component instead.', - ); + 'Change ClassWithRenderNotExtended to extend React.Component instead.\n' + + ' in ClassWithRenderNotExtended (at **)', + ]); // Test deduplication await expect(async () => { @@ -514,14 +515,14 @@ describe('ReactCompositeComponent', () => { let instance; const root = ReactDOMClient.createRoot(container); - expect(() => { - ReactDOM.flushSync(() => { - root.render( (instance = ref)} />); - }); - }).toErrorDev( + ReactDOM.flushSync(() => { + root.render( (instance = ref)} />); + }); + assertConsoleErrorDev([ 'Cannot update during an existing state transition (such as within ' + - '`render`). Render methods should be a pure function of props and state.', - ); + '`render`). Render methods should be a pure function of props and state.\n' + + ' in Component (at **)', + ]); // The setState call is queued and then executed as a second pass. This // behavior is undefined though so we're free to change it to suit the @@ -618,14 +619,14 @@ describe('ReactCompositeComponent', () => { root.render( (instance = ref)} />); }); - expect(() => { - ReactDOM.flushSync(() => { - instance.setState({bogus: true}); - }); - }).toErrorDev( + ReactDOM.flushSync(() => { + instance.setState({bogus: true}); + }); + assertConsoleErrorDev([ 'ClassComponent.shouldComponentUpdate(): Returned undefined instead of a ' + - 'boolean value. Make sure to return true or false.', - ); + 'boolean value. Make sure to return true or false.\n' + + ' in ClassComponent (at **)', + ]); }); it('should warn when componentDidUnmount method is defined', async () => { @@ -638,15 +639,15 @@ describe('ReactCompositeComponent', () => { } const root = ReactDOMClient.createRoot(document.createElement('div')); - expect(() => { - ReactDOM.flushSync(() => { - root.render(); - }); - }).toErrorDev( + ReactDOM.flushSync(() => { + root.render(); + }); + assertConsoleErrorDev([ 'Component has a method called ' + 'componentDidUnmount(). But there is no such lifecycle method. ' + - 'Did you mean componentWillUnmount()?', - ); + 'Did you mean componentWillUnmount()?\n' + + ' in Component (at **)', + ]); }); it('should warn when componentDidReceiveProps method is defined', () => { @@ -660,17 +661,17 @@ describe('ReactCompositeComponent', () => { const root = ReactDOMClient.createRoot(document.createElement('div')); - expect(() => { - ReactDOM.flushSync(() => { - root.render(); - }); - }).toErrorDev( + ReactDOM.flushSync(() => { + root.render(); + }); + assertConsoleErrorDev([ 'Component has a method called ' + 'componentDidReceiveProps(). But there is no such lifecycle method. ' + 'If you meant to update the state in response to changing props, ' + 'use componentWillReceiveProps(). If you meant to fetch data or ' + - 'run side-effects or mutations after React has updated the UI, use componentDidUpdate().', - ); + 'run side-effects or mutations after React has updated the UI, use componentDidUpdate().\n' + + ' in Component (at **)', + ]); }); it('should warn when defaultProps was defined as an instance property', () => { @@ -686,14 +687,14 @@ describe('ReactCompositeComponent', () => { } const root = ReactDOMClient.createRoot(document.createElement('div')); - expect(() => { - ReactDOM.flushSync(() => { - root.render(); - }); - }).toErrorDev( + ReactDOM.flushSync(() => { + root.render(); + }); + assertConsoleErrorDev([ 'Setting defaultProps as an instance property on Component is not supported ' + - 'and will be ignored. Instead, define defaultProps as a static property on Component.', - ); + 'and will be ignored. Instead, define defaultProps as a static property on Component.\n' + + ' in Component (at **)', + ]); }); it('should skip update when rerendering element in container', async () => { @@ -739,16 +740,16 @@ describe('ReactCompositeComponent', () => { } } - expect(() => { - ReactDOM.flushSync(() => { - root.render(); - }); - }).toErrorDev( + ReactDOM.flushSync(() => { + root.render(); + }); + assertConsoleErrorDev([ 'Render methods should be a pure function of props and state; ' + 'triggering nested component updates from render is not allowed. If ' + - 'necessary, trigger nested updates in componentDidUpdate.\n\nCheck the ' + - 'render method of Outer.', - ); + 'necessary, trigger nested updates in componentDidUpdate.\n\n' + + 'Check the render method of Outer.\n' + + ' in Outer (at **)', + ]); }); it('only renders once if updated in componentWillReceiveProps', async () => { @@ -836,14 +837,14 @@ describe('ReactCompositeComponent', () => { } const root = ReactDOMClient.createRoot(container); - expect(() => { - ReactDOM.flushSync(() => { - root.render(); - }); - }).toErrorDev( + ReactDOM.flushSync(() => { + root.render(); + }); + assertConsoleErrorDev([ 'When calling super() in `Foo`, make sure to pass ' + - "up the same props that your component's constructor was passed.", - ); + "up the same props that your component's constructor was passed.\n" + + ' in Foo (at **)', + ]); }); it('should only call componentWillUnmount once', async () => { @@ -1185,16 +1186,17 @@ describe('ReactCompositeComponent', () => { const root = ReactDOMClient.createRoot(document.createElement('div')); await expect(async () => { - await expect(async () => { - await act(() => { - root.render(); - }); - }).rejects.toThrow(); - }).toErrorDev([ + await act(() => { + root.render(); + }); + }).rejects.toThrow(); + assertConsoleErrorDev([ 'No `render` method found on the RenderTextInvalidConstructor instance: ' + - 'did you accidentally return an object from the constructor?', + 'did you accidentally return an object from the constructor?\n' + + ' in RenderTextInvalidConstructor (at **)', 'No `render` method found on the RenderTextInvalidConstructor instance: ' + - 'did you accidentally return an object from the constructor?', + 'did you accidentally return an object from the constructor?\n' + + ' in RenderTextInvalidConstructor (at **)', ]); }); @@ -1210,14 +1212,14 @@ describe('ReactCompositeComponent', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - expect(() => { - ReactDOM.flushSync(() => { - root.render(); - }); - }).toErrorDev( + ReactDOM.flushSync(() => { + root.render(); + }); + assertConsoleErrorDev([ 'It looks like Bad is reassigning its own `this.props` while rendering. ' + - 'This is not supported and can lead to confusing bugs.', - ); + 'This is not supported and can lead to confusing bugs.\n' + + ' in Bad (at **)', + ]); }); it('should return error if render is not defined', async () => { @@ -1225,16 +1227,17 @@ describe('ReactCompositeComponent', () => { const root = ReactDOMClient.createRoot(document.createElement('div')); await expect(async () => { - await expect(async () => { - await act(() => { - root.render(); - }); - }).rejects.toThrow(); - }).toErrorDev([ + await act(() => { + root.render(); + }); + }).rejects.toThrow(); + assertConsoleErrorDev([ 'No `render` method found on the RenderTestUndefinedRender instance: ' + - 'you may have forgotten to define `render`.', + 'you may have forgotten to define `render`.\n' + + ' in RenderTestUndefinedRender (at **)', 'No `render` method found on the RenderTestUndefinedRender instance: ' + - 'you may have forgotten to define `render`.', + 'you may have forgotten to define `render`.\n' + + ' in RenderTestUndefinedRender (at **)', ]); }); @@ -1386,13 +1389,18 @@ describe('ReactCompositeComponent', () => { } const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - expect(() => { - ReactDOM.flushSync(() => { - root.render(); - }); - }).toErrorDev( - 'Cannot update a component (`A`) while rendering a different component (`B`)', - ); + ReactDOM.flushSync(() => { + root.render(); + }); + assertConsoleErrorDev([ + 'Cannot update a component (`A`) while rendering a different component (`B`). ' + + 'To locate the bad setState() call inside `B`, ' + + 'follow the stack trace as described in https://react.dev/link/setstate-in-render\n' + + (gate('enableOwnerStacks') + ? '' + : ' in B (at **)\n' + ' in div (at **)\n') + + ' in Parent (at **)', + ]); // We error, but still update the state. expect(ref.textContent).toBe('1'); diff --git a/packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js b/packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js index a651a477a8e76..d0d1c36e514a9 100644 --- a/packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js +++ b/packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js @@ -17,13 +17,14 @@ let Scheduler; let assertLog; let TestComponent; let testComponentInstance; +let assertConsoleErrorDev; describe('ReactCompositeComponent-state', () => { beforeEach(() => { React = require('react'); ReactDOM = require('react-dom'); ReactDOMClient = require('react-dom/client'); - act = require('internal-test-utils').act; + ({act, assertConsoleErrorDev} = require('internal-test-utils')); Scheduler = require('scheduler'); const InternalTestUtils = require('internal-test-utils'); @@ -469,15 +470,15 @@ describe('ReactCompositeComponent-state', () => { root.render(); }); // Update - expect(() => { - ReactDOM.flushSync(() => { - root.render(); - }); - }).toErrorDev( + ReactDOM.flushSync(() => { + root.render(); + }); + assertConsoleErrorDev([ 'Test.componentWillReceiveProps(): Assigning directly to ' + "this.state is deprecated (except inside a component's constructor). " + - 'Use setState instead.', - ); + 'Use setState instead.\n' + + ' in Test (at **)', + ]); assertLog([ 'render -- step: 1, extra: true', @@ -518,15 +519,15 @@ describe('ReactCompositeComponent-state', () => { // Mount const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - expect(() => { - ReactDOM.flushSync(() => { - root.render(); - }); - }).toErrorDev( + ReactDOM.flushSync(() => { + root.render(); + }); + assertConsoleErrorDev([ 'Test.componentWillMount(): Assigning directly to ' + "this.state is deprecated (except inside a component's constructor). " + - 'Use setState instead.', - ); + 'Use setState instead.\n' + + ' in Test (at **)', + ]); assertLog([ 'render -- step: 3, extra: false', @@ -566,13 +567,16 @@ describe('ReactCompositeComponent-state', () => { }); expect(el.textContent).toBe('A'); - expect(() => { - ReactDOM.flushSync(() => { - root.render(); - }); - }).toErrorDev( - "Can't perform a React state update on a component that hasn't mounted yet", - ); + ReactDOM.flushSync(() => { + root.render(); + }); + assertConsoleErrorDev([ + "Can't perform a React state update on a component that hasn't mounted yet. " + + 'This indicates that you have a side-effect in your render function that ' + + 'asynchronously later calls tries to update the component. ' + + 'Move this work to useEffect instead.\n' + + ' in B (at **)', + ]); }); // @gate !disableLegacyMode diff --git a/packages/react-dom/src/__tests__/ReactDOM-test.js b/packages/react-dom/src/__tests__/ReactDOM-test.js index c6c90d1cf15f2..92571ad69e10c 100644 --- a/packages/react-dom/src/__tests__/ReactDOM-test.js +++ b/packages/react-dom/src/__tests__/ReactDOM-test.js @@ -14,7 +14,7 @@ let ReactDOM; let findDOMNode; let ReactDOMClient; let ReactDOMServer; - +let assertConsoleErrorDev; let act; describe('ReactDOM', () => { @@ -28,7 +28,7 @@ describe('ReactDOM', () => { ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE .findDOMNode; - act = require('internal-test-utils').act; + ({act, assertConsoleErrorDev} = require('internal-test-utils')); }); it('should bubble onSubmit', async () => { @@ -188,15 +188,14 @@ describe('ReactDOM', () => { const myDiv = document.createElement('div'); await expect(async () => { - await expect(async () => { - await act(() => { - ReactDOM.render(, myDiv, 'no'); - }); - }).rejects.toThrowError( - 'Invalid argument passed as callback. Expected a function. Instead ' + - 'received: no', - ); - }).toErrorDev( + await act(() => { + ReactDOM.render(, myDiv, 'no'); + }); + }).rejects.toThrowError( + 'Invalid argument passed as callback. Expected a function. Instead ' + + 'received: no', + ); + assertConsoleErrorDev( [ 'Expected the last optional `callback` argument to be a function. Instead received: no.', 'Expected the last optional `callback` argument to be a function. Instead received: no.', @@ -205,15 +204,14 @@ describe('ReactDOM', () => { ); await expect(async () => { - await expect(async () => { - await act(() => { - ReactDOM.render(, myDiv, {foo: 'bar'}); - }); - }).rejects.toThrowError( - 'Invalid argument passed as callback. Expected a function. Instead ' + - 'received: [object Object]', - ); - }).toErrorDev( + await act(() => { + ReactDOM.render(, myDiv, {foo: 'bar'}); + }); + }).rejects.toThrowError( + 'Invalid argument passed as callback. Expected a function. Instead ' + + 'received: [object Object]', + ); + assertConsoleErrorDev( [ "Expected the last optional `callback` argument to be a function. Instead received: { foo: 'bar' }", "Expected the last optional `callback` argument to be a function. Instead received: { foo: 'bar' }.", @@ -222,15 +220,14 @@ describe('ReactDOM', () => { ); await expect(async () => { - await expect(async () => { - await act(() => { - ReactDOM.render(, myDiv, new Foo()); - }); - }).rejects.toThrowError( - 'Invalid argument passed as callback. Expected a function. Instead ' + - 'received: [object Object]', - ); - }).toErrorDev( + await act(() => { + ReactDOM.render(, myDiv, new Foo()); + }); + }).rejects.toThrowError( + 'Invalid argument passed as callback. Expected a function. Instead ' + + 'received: [object Object]', + ); + assertConsoleErrorDev( [ 'Expected the last optional `callback` argument to be a function. Instead received: Foo { a: 1, b: 2 }.', 'Expected the last optional `callback` argument to be a function. Instead received: Foo { a: 1, b: 2 }.', @@ -257,15 +254,14 @@ describe('ReactDOM', () => { const myDiv = document.createElement('div'); ReactDOM.render(, myDiv); await expect(async () => { - await expect(async () => { - await act(() => { - ReactDOM.render(, myDiv, 'no'); - }); - }).rejects.toThrowError( - 'Invalid argument passed as callback. Expected a function. Instead ' + - 'received: no', - ); - }).toErrorDev( + await act(() => { + ReactDOM.render(, myDiv, 'no'); + }); + }).rejects.toThrowError( + 'Invalid argument passed as callback. Expected a function. Instead ' + + 'received: no', + ); + assertConsoleErrorDev( [ 'Expected the last optional `callback` argument to be a function. Instead received: no.', 'Expected the last optional `callback` argument to be a function. Instead received: no.', @@ -275,15 +271,14 @@ describe('ReactDOM', () => { ReactDOM.render(, myDiv); // Re-mount await expect(async () => { - await expect(async () => { - await act(() => { - ReactDOM.render(, myDiv, {foo: 'bar'}); - }); - }).rejects.toThrowError( - 'Invalid argument passed as callback. Expected a function. Instead ' + - 'received: [object Object]', - ); - }).toErrorDev( + await act(() => { + ReactDOM.render(, myDiv, {foo: 'bar'}); + }); + }).rejects.toThrowError( + 'Invalid argument passed as callback. Expected a function. Instead ' + + 'received: [object Object]', + ); + assertConsoleErrorDev( [ "Expected the last optional `callback` argument to be a function. Instead received: { foo: 'bar' }.", "Expected the last optional `callback` argument to be a function. Instead received: { foo: 'bar' }.", @@ -293,15 +288,14 @@ describe('ReactDOM', () => { ReactDOM.render(, myDiv); // Re-mount await expect(async () => { - await expect(async () => { - await act(() => { - ReactDOM.render(, myDiv, new Foo()); - }); - }).rejects.toThrowError( - 'Invalid argument passed as callback. Expected a function. Instead ' + - 'received: [object Object]', - ); - }).toErrorDev( + await act(() => { + ReactDOM.render(, myDiv, new Foo()); + }); + }).rejects.toThrowError( + 'Invalid argument passed as callback. Expected a function. Instead ' + + 'received: [object Object]', + ); + assertConsoleErrorDev( [ 'Expected the last optional `callback` argument to be a function. Instead received: Foo { a: 1, b: 2 }.', 'Expected the last optional `callback` argument to be a function. Instead received: Foo { a: 1, b: 2 }.', @@ -544,11 +538,10 @@ describe('ReactDOM', () => { } const root = ReactDOMClient.createRoot(document.createElement('div')); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev([ + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ // ReactDOM(App > div > span) 'Invalid ARIA attribute `ariaTypo`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' + ' in span (at **)\n' + diff --git a/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js b/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js index 9b7d9a30cbd53..cd1d055c09d1c 100644 --- a/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js @@ -13,12 +13,15 @@ describe('ReactDOM unknown attribute', () => { let React; let ReactDOMClient; let act; + let assertConsoleErrorDev; beforeEach(() => { jest.resetModules(); React = require('react'); ReactDOMClient = require('react-dom/client'); act = require('internal-test-utils').act; + assertConsoleErrorDev = + require('internal-test-utils').assertConsoleErrorDev; }); async function testUnknownAttributeRemoval(givenValue) { @@ -62,12 +65,13 @@ describe('ReactDOM unknown attribute', () => { }); it('changes values true, false to null, and also warns once', async () => { - await expect(() => testUnknownAttributeAssignment(true, null)).toErrorDev( + await testUnknownAttributeAssignment(true, null); + assertConsoleErrorDev([ 'Received `true` for a non-boolean attribute `unknown`.\n\n' + 'If you want to write it to the DOM, pass a string instead: ' + 'unknown="true" or unknown={value.toString()}.\n' + ' in div (at **)', - ); + ]); await testUnknownAttributeAssignment(false, null); }); @@ -92,11 +96,9 @@ describe('ReactDOM unknown attribute', () => { const el = document.createElement('div'); const root = ReactDOMClient.createRoot(el); - await expect(async () => { - await act(() => { - root.render(
); - }); - }).toErrorDev([]); + await act(() => { + root.render(
); + }); expect(el.firstChild.getAttribute('inert')).toBe(true ? '' : null); }); @@ -105,15 +107,15 @@ describe('ReactDOM unknown attribute', () => { const el = document.createElement('div'); const root = ReactDOMClient.createRoot(el); - await expect(async () => { - await act(() => { - root.render(
); - }); - }).toErrorDev([ + await act(() => { + root.render(
); + }); + assertConsoleErrorDev([ 'Received an empty string for a boolean attribute `inert`. ' + 'This will treat the attribute as if it were false. ' + 'Either pass `false` to silence this warning, or ' + - 'pass `true` if you used an empty string in earlier versions of React to indicate this attribute is true.', + 'pass `true` if you used an empty string in earlier versions of React to indicate this attribute is true.\n' + + ' in div (at **)', ]); expect(el.firstChild.getAttribute('inert')).toBe(true ? null : ''); @@ -136,11 +138,12 @@ describe('ReactDOM unknown attribute', () => { }); it('coerces NaN to strings and warns', async () => { - await expect(() => testUnknownAttributeAssignment(NaN, 'NaN')).toErrorDev( + await testUnknownAttributeAssignment(NaN, 'NaN'); + assertConsoleErrorDev([ 'Received NaN for the `unknown` attribute. ' + 'If this is expected, cast the value to a string.\n' + ' in div (at **)', - ); + ]); }); it('coerces objects to strings and warns', async () => { @@ -167,52 +170,52 @@ describe('ReactDOM unknown attribute', () => { } const test = () => testUnknownAttributeAssignment(new TemporalLike(), null); - await expect(() => - expect(test).rejects.toThrowError(new TypeError('prod message')), - ).toErrorDev( + + await expect(test).rejects.toThrowError(new TypeError('prod message')); + assertConsoleErrorDev([ 'The provided `unknown` attribute is an unsupported type TemporalLike.' + - ' This value must be coerced to a string before using it here.', - ); + ' This value must be coerced to a string before using it here.\n' + + ' in div (at **)', + ]); }); it('removes symbols and warns', async () => { - await expect(() => testUnknownAttributeRemoval(Symbol('foo'))).toErrorDev( + await testUnknownAttributeRemoval(Symbol('foo')); + assertConsoleErrorDev([ 'Invalid value for prop `unknown` on
tag. Either remove it ' + 'from the element, or pass a string or number value to keep it ' + 'in the DOM. For details, see https://react.dev/link/attribute-behavior \n' + ' in div (at **)', - ); + ]); }); it('removes functions and warns', async () => { - await expect(() => - testUnknownAttributeRemoval(function someFunction() {}), - ).toErrorDev( + await testUnknownAttributeRemoval(function someFunction() {}); + assertConsoleErrorDev([ 'Invalid value for prop `unknown` on
tag. Either remove ' + 'it from the element, or pass a string or number value to ' + 'keep it in the DOM. For details, see ' + 'https://react.dev/link/attribute-behavior \n' + ' in div (at **)', - ); + ]); }); it('allows camelCase unknown attributes and warns', async () => { const el = document.createElement('div'); - await expect(async () => { - const root = ReactDOMClient.createRoot(el); + const root = ReactDOMClient.createRoot(el); - await act(() => { - root.render(
); - }); - }).toErrorDev( + await act(() => { + root.render(
); + }); + assertConsoleErrorDev([ 'React does not recognize the `helloWorld` prop on a DOM element. ' + 'If you intentionally want it to appear in the DOM as a custom ' + 'attribute, spell it as lowercase `helloworld` instead. ' + 'If you accidentally passed it from a parent component, remove ' + 'it from the DOM element.\n' + ' in div (at **)', - ); + ]); expect(el.firstChild.getAttribute('helloworld')).toBe('something'); }); diff --git a/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js b/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js index e0022dd99004b..a617770cf5f3b 100644 --- a/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js +++ b/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js @@ -12,6 +12,7 @@ let React; let ReactNoop; let waitForAll; +let assertConsoleErrorDev; describe('ReactDeprecationWarnings', () => { beforeEach(() => { @@ -20,6 +21,7 @@ describe('ReactDeprecationWarnings', () => { ReactNoop = require('react-noop-renderer'); const InternalTestUtils = require('internal-test-utils'); waitForAll = InternalTestUtils.waitForAll; + assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev; }); // @gate !disableDefaultPropsExceptForClasses || !__DEV__ @@ -33,11 +35,13 @@ describe('ReactDeprecationWarnings', () => { }; ReactNoop.render(); - await expect(async () => await waitForAll([])).toErrorDev( + await waitForAll([]); + assertConsoleErrorDev([ 'FunctionalComponent: Support for defaultProps ' + 'will be removed from function components in a future major ' + - 'release. Use JavaScript default parameters instead.', - ); + 'release. Use JavaScript default parameters instead.\n' + + ' in FunctionalComponent (at **)', + ]); }); // @gate !disableDefaultPropsExceptForClasses || !__DEV__ @@ -55,10 +59,12 @@ describe('ReactDeprecationWarnings', () => {
, ); - await expect(async () => await waitForAll([])).toErrorDev( + await waitForAll([]); + assertConsoleErrorDev([ 'FunctionalComponent: Support for defaultProps ' + 'will be removed from memo components in a future major ' + - 'release. Use JavaScript default parameters instead.', - ); + 'release. Use JavaScript default parameters instead.\n' + + ' in div (at **)', + ]); }); }); diff --git a/packages/react-dom/src/__tests__/findDOMNodeFB-test.js b/packages/react-dom/src/__tests__/findDOMNodeFB-test.js index 417ae0c40e7f8..28212c0d5cf82 100644 --- a/packages/react-dom/src/__tests__/findDOMNodeFB-test.js +++ b/packages/react-dom/src/__tests__/findDOMNodeFB-test.js @@ -12,6 +12,8 @@ const React = require('react'); const ReactDOM = require('react-dom'); const StrictMode = React.StrictMode; +const assertConsoleErrorDev = + require('internal-test-utils').assertConsoleErrorDev; describe('findDOMNode', () => { // @gate www && classic @@ -128,8 +130,8 @@ describe('findDOMNode', () => { container, ); - let match; - expect(() => (match = ReactDOM.findDOMNode(parent))).toErrorDev([ + const match = ReactDOM.findDOMNode(parent); + assertConsoleErrorDev([ 'findDOMNode is deprecated in StrictMode. ' + 'findDOMNode was passed an instance of ContainsStrictModeChild which renders StrictMode children. ' + 'Instead, add a ref directly to the element you want to reference. ' + @@ -161,8 +163,8 @@ describe('findDOMNode', () => { container, ); - let match; - expect(() => (match = ReactDOM.findDOMNode(parent))).toErrorDev([ + const match = ReactDOM.findDOMNode(parent); + assertConsoleErrorDev([ 'findDOMNode is deprecated in StrictMode. ' + 'findDOMNode was passed an instance of IsInStrictMode which is inside StrictMode. ' + 'Instead, add a ref directly to the element you want to reference. ' + diff --git a/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js b/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js index 76dea3e732632..67d29fd33efdc 100644 --- a/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js +++ b/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js @@ -87,7 +87,7 @@ module.exports = function (initModules) { console.error.mockClear(); } else { // TODO: Rewrite tests that use this helper to enumerate expected errors. - // This will enable the helper to use the .toErrorDev() matcher instead of spying. + // This will enable the helper to use the assertConsoleErrorDev instead of spying. spyOnDev(console, 'error').mockImplementation(() => {}); }