diff --git a/packages/react-dom/src/__tests__/ReactDOMComponentTree-test.js b/packages/react-dom/src/__tests__/ReactDOMComponentTree-test.js
index e0b2b741dd6c0..4dec7df2faaf5 100644
--- a/packages/react-dom/src/__tests__/ReactDOMComponentTree-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMComponentTree-test.js
@@ -14,11 +14,14 @@ describe('ReactDOMComponentTree', () => {
let ReactDOMClient;
let act;
let container;
+ let assertConsoleErrorDev;
beforeEach(() => {
React = require('react');
ReactDOMClient = require('react-dom/client');
act = require('internal-test-utils').act;
+ assertConsoleErrorDev =
+ require('internal-test-utils').assertConsoleErrorDev;
container = document.createElement('div');
document.body.appendChild(container);
@@ -190,18 +193,18 @@ describe('ReactDOMComponentTree', () => {
root.render( );
});
- await expect(
- async () =>
- await act(() => {
- simulateInput(inputRef.current, finishValue);
- }),
- ).toErrorDev(
+ await act(() => {
+ simulateInput(inputRef.current, finishValue);
+ });
+ assertConsoleErrorDev([
'A component is changing an uncontrolled input to be controlled. ' +
'This is likely caused by the value changing from undefined to ' +
'a defined value, 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',
- );
+ 'https://react.dev/link/controlled-components\n' +
+ ' in input (at **)\n' +
+ ' in Controlled (at **)',
+ ]);
});
});
diff --git a/packages/react-dom/src/__tests__/ReactDOMFiber-test.js b/packages/react-dom/src/__tests__/ReactDOMFiber-test.js
index cf0526fd61bf0..9784e4f849897 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFiber-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFiber-test.js
@@ -746,8 +746,13 @@ describe('ReactDOMFiber', () => {
root.render( );
});
assertConsoleErrorDev([
- 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
- 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
+ 'Parent uses the legacy childContextTypes API which will soon be removed. ' +
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
+ ' in Parent (at **)',
+ 'Component uses the legacy contextTypes API which will soon be removed. ' +
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
+ (gate('enableOwnerStacks') ? '' : ' in Component (at **)\n') +
+ ' in Parent (at **)',
]);
expect(container.innerHTML).toBe('');
expect(portalContainer.innerHTML).toBe('
bar
');
@@ -957,15 +962,14 @@ describe('ReactDOMFiber', () => {
return
;
}
}
- expect(() => {
- ReactDOM.flushSync(() => {
- root.render( );
- });
- }).toErrorDev(
+ ReactDOM.flushSync(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
'Expected `onClick` listener to be a function, instead got a value of `string` type.\n' +
' in div (at **)\n' +
' in Example (at **)',
- );
+ ]);
});
it('should warn with a special message for `false` event listeners', () => {
@@ -974,17 +978,16 @@ describe('ReactDOMFiber', () => {
return
;
}
}
- expect(() => {
- ReactDOM.flushSync(() => {
- root.render( );
- });
- }).toErrorDev(
+ ReactDOM.flushSync(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
'Expected `onClick` listener to be a function, instead got `false`.\n\n' +
'If you used to conditionally omit it with onClick={condition && value}, ' +
'pass onClick={condition ? value : undefined} instead.\n' +
' in div (at **)\n' +
' in Example (at **)',
- );
+ ]);
});
it('should not update event handlers until commit', async () => {
diff --git a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
index 027099d54707c..47ae48eb767bd 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
@@ -19,6 +19,7 @@ let waitForAll;
let waitFor;
let waitForMicrotasks;
let assertLog;
+let assertConsoleErrorDev;
const setUntrackedInputValue = Object.getOwnPropertyDescriptor(
HTMLInputElement.prototype,
@@ -34,6 +35,8 @@ describe('ReactDOMFiberAsync', () => {
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
act = require('internal-test-utils').act;
+ assertConsoleErrorDev =
+ require('internal-test-utils').assertConsoleErrorDev;
Scheduler = require('scheduler');
const InternalTestUtils = require('internal-test-utils');
@@ -176,11 +179,15 @@ describe('ReactDOMFiberAsync', () => {
root.render( );
});
// Update
- expect(() => {
- ReactDOM.flushSync(() => {
- root.render( );
- });
- }).toErrorDev('flushSync was called from inside a lifecycle method');
+ ReactDOM.flushSync(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
+ 'flushSync was called from inside a lifecycle method. ' +
+ 'React cannot flush when React is already rendering. ' +
+ 'Consider moving this call to a scheduler task or micro task.\n' +
+ ' in Component (at **)',
+ ]);
});
describe('concurrent mode', () => {
diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzForm-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzForm-test.js
index 2300f4563c4b6..5b88ac54c794b 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFizzForm-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFizzForm-test.js
@@ -26,6 +26,7 @@ let useFormStatus;
let useOptimistic;
let useActionState;
let Scheduler;
+let assertConsoleErrorDev;
describe('ReactDOMFizzForm', () => {
beforeEach(() => {
@@ -38,6 +39,8 @@ describe('ReactDOMFizzForm', () => {
useFormStatus = require('react-dom').useFormStatus;
useOptimistic = require('react').useOptimistic;
act = require('internal-test-utils').act;
+ assertConsoleErrorDev =
+ require('internal-test-utils').assertConsoleErrorDev;
container = document.createElement('div');
document.body.appendChild(container);
// TODO: Test the old api but it warns so needs warnings to be asserted.
@@ -195,12 +198,26 @@ describe('ReactDOMFizzForm', () => {
ReactDOMServer.renderToReadableStream( ),
);
await readIntoContainer(stream);
- await expect(async () => {
- await act(async () => {
- ReactDOMClient.hydrateRoot(container, );
- });
- }).toErrorDev(
- "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties.",
+ await act(async () => {
+ ReactDOMClient.hydrateRoot(container, );
+ });
+ assertConsoleErrorDev(
+ [
+ "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
+ "This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n" +
+ "- A server/client branch `if (typeof window !== 'undefined')`.\n" +
+ "- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
+ "- Date formatting in a user's locale which doesn't match the server.\n" +
+ '- External changing data without sending a snapshot of it along with the HTML.\n' +
+ '- Invalid HTML tag nesting.\n\n' +
+ 'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n\n' +
+ 'https://react.dev/link/hydration-mismatch\n\n' +
+ ' \n' +
+ ' ,
+ );
+ });
+ assertConsoleErrorDev([
'In HTML, cannot be a descendant of .\n' +
'This will cause a hydration error.\n' +
'\n' +
@@ -394,14 +393,14 @@ describe('ReactDOMForm', () => {
'> \n' +
'\n in form (at **)' +
(gate(flags => flags.enableOwnerStacks) ? '' : '\n in form (at **)'),
- );
+ ]);
await submit(ref.current);
expect(data).toBe('innerinner');
});
- it('should only submit once if one root is nested inside the other', async () => {
+ it('should submit once if one root is nested inside the other', async () => {
const ref = React.createRef();
let outerCalled = 0;
let innerCalled = 0;
@@ -444,7 +443,7 @@ describe('ReactDOMForm', () => {
expect(innerCalled).toBe(1);
});
- it('should only submit once if a portal is nested inside its own root', async () => {
+ it('should submit once if a portal is nested inside its own root', async () => {
const ref = React.createRef();
let outerCalled = 0;
let innerCalled = 0;
@@ -565,29 +564,32 @@ describe('ReactDOMForm', () => {
}
const root = ReactDOMClient.createRoot(container);
- await expect(async () => {
- await act(async () => {
- root.render(
-
-
-
- Edit
-
- ,
- );
- });
- }).toErrorDev([
- 'Cannot specify a "name" prop for a button that specifies a function as a formAction.',
+ await act(async () => {
+ root.render(
+
+
+
+ Edit
+
+ ,
+ );
+ });
+ assertConsoleErrorDev([
+ 'Cannot specify a "name" prop for a button that specifies a function as a formAction. ' +
+ 'React needs it to encode which action should be invoked. ' +
+ 'It will get overridden.\n' +
+ ' in input (at **)' +
+ (gate('enableOwnerStacks') ? '' : '\n in form (at **)'),
]);
await submit(inputRef.current);
@@ -1492,8 +1494,9 @@ describe('ReactDOMForm', () => {
await act(() => dispatch());
assertConsoleErrorDev([
[
- 'An async function was passed to useActionState, but it was ' +
- 'dispatched outside of an action context',
+ 'An async function was passed to useActionState, but it was dispatched outside of an action context. ' +
+ 'This is likely not what you intended. ' +
+ 'Either pass the dispatch function to an `action` prop, or dispatch manually inside `startTransition`',
{withoutStack: true},
],
]);
@@ -1919,11 +1922,16 @@ describe('ReactDOMForm', () => {
expect(inputRef.current.value).toBe(' Updated ');
// This triggers a synchronous requestFormReset, and a warning
- await expect(async () => {
- await act(() => resolveText('Wait 1'));
- }).toErrorDev(['requestFormReset was called outside a transition'], {
- withoutStack: true,
- });
+ await act(() => resolveText('Wait 1'));
+ assertConsoleErrorDev(
+ [
+ 'requestFormReset was called outside a transition or action. ' +
+ 'To fix, move to an action, or wrap with startTransition.',
+ ],
+ {
+ withoutStack: true,
+ },
+ );
assertLog(['Request form reset']);
// The form was reset even though the action didn't finish.
@@ -1957,7 +1965,14 @@ describe('ReactDOMForm', () => {
// Symbols are coerced to null, so this should fire the form action
await act(() => root.render( ));
- assertConsoleErrorDev(['Invalid value for prop `formAction`']);
+ assertConsoleErrorDev([
+ 'Invalid value for prop `formAction` 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 button (at **)\n' +
+ (gate('enableOwnerStacks') ? '' : ' in form (at **)\n') +
+ ' in App (at **)',
+ ]);
await submit(buttonRef.current);
assertLog(['Form action']);
@@ -2201,7 +2216,13 @@ describe('ReactDOMForm', () => {
// Symbols are coerced to null
await act(() => root.render( ));
- assertConsoleErrorDev(['Invalid value for prop `action`']);
+ assertConsoleErrorDev([
+ 'Invalid value for prop `action` 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 form (at **)\n' +
+ ' in Form (at **)',
+ ]);
await submit(formRef.current);
assertLog([null]);
diff --git a/packages/react-dom/src/__tests__/ReactDOMInput-test.js b/packages/react-dom/src/__tests__/ReactDOMInput-test.js
index 80ded083d085e..5b47095d7c755 100644
--- a/packages/react-dom/src/__tests__/ReactDOMInput-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMInput-test.js
@@ -26,6 +26,7 @@ describe('ReactDOMInput', () => {
let setUntrackedChecked;
let container;
let root;
+ let assertConsoleErrorDev;
function dispatchEventOnNode(node, type) {
node.dispatchEvent(new Event(type, {bubbles: true, cancelable: true}));
@@ -96,6 +97,8 @@ describe('ReactDOMInput', () => {
ReactDOMServer = require('react-dom/server');
Scheduler = require('scheduler');
act = require('internal-test-utils').act;
+ assertConsoleErrorDev =
+ require('internal-test-utils').assertConsoleErrorDev;
assertLog = require('internal-test-utils').assertLog;
container = document.createElement('div');
@@ -109,52 +112,54 @@ describe('ReactDOMInput', () => {
});
it('should warn for controlled value of 0 with missing onChange', async () => {
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ await act(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
'You provided a `value` prop to a form ' +
'field without an `onChange` handler. This will render a read-only ' +
'field. If the field should be mutable use `defaultValue`. ' +
- 'Otherwise, set either `onChange` or `readOnly`.',
- );
+ 'Otherwise, set either `onChange` or `readOnly`.\n' +
+ ' in input (at **)',
+ ]);
});
it('should warn for controlled value of "" with missing onChange', async () => {
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ await act(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
'You provided a `value` prop to a form ' +
'field without an `onChange` handler. This will render a read-only ' +
'field. If the field should be mutable use `defaultValue`. ' +
- 'Otherwise, set either `onChange` or `readOnly`.',
- );
+ 'Otherwise, set either `onChange` or `readOnly`.\n' +
+ ' in input (at **)',
+ ]);
});
it('should warn for controlled value of "0" with missing onChange', async () => {
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ await act(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
'You provided a `value` prop to a form ' +
'field without an `onChange` handler. This will render a read-only ' +
'field. If the field should be mutable use `defaultValue`. ' +
- 'Otherwise, set either `onChange` or `readOnly`.',
- );
+ 'Otherwise, set either `onChange` or `readOnly`.\n' +
+ ' in input (at **)',
+ ]);
});
it('should warn for controlled value of false with missing onChange', async () => {
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
- 'You provided a `checked` prop to a form field without an `onChange` handler.',
- );
+ await act(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
+ 'You provided a `checked` prop to a form field without an `onChange` handler. ' +
+ 'This will render a read-only field. If the field should be mutable use `defaultChecked`. ' +
+ 'Otherwise, set either `onChange` or `readOnly`.\n' +
+ ' in input (at **)',
+ ]);
});
it('should warn with checked and no onChange handler with readOnly specified', async () => {
@@ -164,15 +169,15 @@ describe('ReactDOMInput', () => {
root.unmount();
root = ReactDOMClient.createRoot(container);
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ await act(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
'You provided a `checked` prop to a form field without an `onChange` handler. ' +
'This will render a read-only field. If the field should be mutable use `defaultChecked`. ' +
- 'Otherwise, set either `onChange` or `readOnly`.',
- );
+ 'Otherwise, set either `onChange` or `readOnly`.\n' +
+ ' in input (at **)',
+ ]);
});
it('should not warn about missing onChange in uncontrolled inputs', async () => {
@@ -213,13 +218,15 @@ describe('ReactDOMInput', () => {
});
it('should properly control a value even if no event listener exists', async () => {
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
- 'You provided a `value` prop to a form field without an `onChange` handler.',
- );
+ await act(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
+ 'You provided a `value` prop to a form field without an `onChange` handler. ' +
+ 'This will render a read-only field. If the field should be mutable use `defaultValue`. ' +
+ 'Otherwise, set either `onChange` or `readOnly`.\n' +
+ ' in input (at **)',
+ ]);
const node = container.firstChild;
expect(isValueDirty(node)).toBe(true);
@@ -426,14 +433,16 @@ describe('ReactDOMInput', () => {
}
const ref = React.createRef();
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
- 'You provided a `value` prop to a form field ' +
- 'without an `onChange` handler.',
- );
+ await act(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
+ 'You provided a `value` prop to a form field without an `onChange` handler. ' +
+ 'This will render a read-only field. If the field should be mutable use `defaultValue`. ' +
+ 'Otherwise, set either `onChange` or `readOnly`.\n' +
+ ' in input (at **)\n' +
+ ' in Stub (at **)',
+ ]);
const node = container.firstChild;
await act(() => {
ref.current.setState({value: '0.98'});
@@ -499,14 +508,16 @@ describe('ReactDOMInput', () => {
}
const ref = React.createRef();
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
- 'You provided a `value` prop to a form field ' +
- 'without an `onChange` handler.',
- );
+ await act(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
+ 'You provided a `value` prop to a form field without an `onChange` handler. ' +
+ 'This will render a read-only field. If the field should be mutable use `defaultValue`. ' +
+ 'Otherwise, set either `onChange` or `readOnly`.\n' +
+ ' in input (at **)\n' +
+ ' in Stub (at **)',
+ ]);
const node = container.firstChild;
await act(() => {
ref.current.setState({value: '3'});
@@ -636,13 +647,16 @@ describe('ReactDOMInput', () => {
const node = container.firstChild;
expect(node.value).toBe('0');
expect(isValueDirty(node)).toBe(true);
- 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 **)',
+ ]);
expect(node.value).toBe('0');
expect(isValueDirty(node)).toBe(true);
});
@@ -745,15 +759,18 @@ describe('ReactDOMInput', () => {
}
}
await expect(async () => {
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
- 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
- 'strings, not TemporalLike. This value must be coerced to a string before using it here.',
- );
+ await act(() => {
+ root.render( );
+ });
}).rejects.toThrowError(new TypeError('prod message'));
+ assertConsoleErrorDev([
+ 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
+ 'strings, not TemporalLike. This value must be coerced to a string before using it here.\n' +
+ ' in input (at **)',
+ 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
+ 'strings, not TemporalLike. This value must be coerced to a string before using it here.\n' +
+ ' in input (at **)',
+ ]);
});
it('should throw for text inputs if `defaultValue` is an object where valueOf() throws', async () => {
@@ -768,15 +785,18 @@ describe('ReactDOMInput', () => {
}
}
await expect(async () => {
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
- 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
- 'strings, not TemporalLike. This value must be coerced to a string before using it here.',
- );
+ await act(() => {
+ root.render( );
+ });
}).rejects.toThrowError(new TypeError('prod message'));
+ assertConsoleErrorDev([
+ 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
+ 'strings, not TemporalLike. This value must be coerced to a string before using it here.\n' +
+ ' in input (at **)',
+ 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
+ 'strings, not TemporalLike. This value must be coerced to a string before using it here.\n' +
+ ' in input (at **)',
+ ]);
});
it('should throw for date inputs if `value` is an object where valueOf() throws', async () => {
@@ -791,21 +811,20 @@ describe('ReactDOMInput', () => {
}
}
await expect(async () => {
- await expect(async () => {
- await act(() => {
- root.render(
- {}}
- />,
- );
- });
- }).toErrorDev(
- 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
- 'strings, not TemporalLike. This value must be coerced to a string before using it here.',
- );
+ await act(() => {
+ root.render(
+ {}} />,
+ );
+ });
}).rejects.toThrowError(new TypeError('prod message'));
+ assertConsoleErrorDev([
+ 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
+ 'strings, not TemporalLike. This value must be coerced to a string before using it here.\n' +
+ ' in input (at **)',
+ 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
+ 'strings, not TemporalLike. This value must be coerced to a string before using it here.\n' +
+ ' in input (at **)',
+ ]);
});
it('should throw for text inputs if `value` is an object where valueOf() throws', async () => {
@@ -820,21 +839,20 @@ describe('ReactDOMInput', () => {
}
}
await expect(async () => {
- await expect(async () => {
- await act(() => {
- root.render(
- {}}
- />,
- );
- });
- }).toErrorDev(
- 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
- 'strings, not TemporalLike. This value must be coerced to a string before using it here.',
- );
+ await act(() => {
+ root.render(
+ {}} />,
+ );
+ });
}).rejects.toThrowError(new TypeError('prod message'));
+ assertConsoleErrorDev([
+ 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
+ 'strings, not TemporalLike. This value must be coerced to a string before using it here.\n' +
+ ' in input (at **)',
+ 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
+ 'strings, not TemporalLike. This value must be coerced to a string before using it here.\n' +
+ ' in input (at **)',
+ ]);
});
it('should display `value` of number 0', async () => {
@@ -1199,15 +1217,18 @@ describe('ReactDOMInput', () => {
// Not really relevant to this particular test, but changing to undefined
// should nonetheless trigger a warning
- 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 **)',
+ ]);
const node = container.firstChild;
expect(node.getAttribute('value')).toBe(null);
@@ -1221,15 +1242,18 @@ describe('ReactDOMInput', () => {
// Not really relevant to this particular test, but changing to undefined
// should nonetheless trigger a warning
- 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 **)',
+ ]);
const node = container.firstChild;
expect(node.getAttribute('value')).toBe(null);
@@ -1281,11 +1305,14 @@ describe('ReactDOMInput', () => {
it('should not set a null value on a submit input', async () => {
const stub = ;
- await expect(async () => {
- await act(() => {
- root.render(stub);
- });
- }).toErrorDev('`value` prop on `input` should not be null');
+ await act(() => {
+ root.render(stub);
+ });
+ assertConsoleErrorDev([
+ '`value` prop on `input` should not be null. ' +
+ 'Consider using an empty string to clear the component or `undefined` for uncontrolled components.\n' +
+ ' in input (at **)',
+ ]);
const node = container.firstChild;
// Note: it shouldn't be an empty string
@@ -1300,11 +1327,14 @@ describe('ReactDOMInput', () => {
it('should not set a null value on a reset input', async () => {
const stub = ;
- await expect(async () => {
- await act(() => {
- root.render(stub);
- });
- }).toErrorDev('`value` prop on `input` should not be null');
+ await act(() => {
+ root.render(stub);
+ });
+ assertConsoleErrorDev([
+ '`value` prop on `input` should not be null. ' +
+ 'Consider using an empty string to clear the component or `undefined` for uncontrolled components.\n' +
+ ' in input (at **)',
+ ]);
const node = container.firstChild;
// Note: it shouldn't be an empty string
@@ -1906,17 +1936,16 @@ describe('ReactDOMInput', () => {
root.unmount();
root = ReactDOMClient.createRoot(container);
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ await act(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
'You provided a `value` prop to a form ' +
'field without an `onChange` handler. This will render a read-only ' +
'field. If the field should be mutable use `defaultValue`. ' +
'Otherwise, set either `onChange` or `readOnly`.\n' +
' in input (at **)',
- );
+ ]);
});
it('should have a this value of undefined if bind is not used', async () => {
@@ -1958,15 +1987,15 @@ describe('ReactDOMInput', () => {
});
it('should warn if value is null', async () => {
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ await act(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
'`value` prop on `input` should not be null. ' +
'Consider using an empty string to clear the component or `undefined` ' +
- 'for uncontrolled components.',
- );
+ 'for uncontrolled components.\n' +
+ ' in input (at **)',
+ ]);
root.unmount();
root = ReactDOMClient.createRoot(container);
@@ -1976,25 +2005,25 @@ describe('ReactDOMInput', () => {
});
it('should warn if checked and defaultChecked props are specified', async () => {
- await expect(async () => {
- await act(() => {
- root.render(
- ,
- );
- });
- }).toErrorDev(
+ await act(() => {
+ root.render(
+ ,
+ );
+ });
+ assertConsoleErrorDev([
'A component contains an input of type radio with both checked and defaultChecked props. ' +
'Input elements must be either controlled or uncontrolled ' +
'(specify either the checked prop, or the defaultChecked prop, but not ' +
'both). Decide between using a controlled or uncontrolled input ' +
'element and remove one of these props. More info: ' +
- 'https://react.dev/link/controlled-components',
- );
+ 'https://react.dev/link/controlled-components\n' +
+ ' in input (at **)',
+ ]);
root.unmount();
root = ReactDOMClient.createRoot(container);
@@ -2011,20 +2040,20 @@ describe('ReactDOMInput', () => {
});
it('should warn if value and defaultValue props are specified', async () => {
- await expect(async () => {
- await act(() => {
- root.render(
- ,
- );
- });
- }).toErrorDev(
+ await act(() => {
+ root.render(
+ ,
+ );
+ });
+ assertConsoleErrorDev([
'A component contains an input of type text with both value and defaultValue props. ' +
'Input elements must be either controlled or uncontrolled ' +
'(specify either the value prop, or the defaultValue prop, but not ' +
'both). Decide between using a controlled or uncontrolled input ' +
'element and remove one of these props. More info: ' +
- 'https://react.dev/link/controlled-components',
- );
+ 'https://react.dev/link/controlled-components\n' +
+ ' in input (at **)',
+ ]);
await (() => {
root.unmount();
});
@@ -2043,18 +2072,17 @@ describe('ReactDOMInput', () => {
await act(() => {
root.render(stub);
});
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ 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 **)',
- );
+ ]);
});
it('should warn if controlled input switches to uncontrolled (value is null)', async () => {
@@ -2064,13 +2092,13 @@ describe('ReactDOMInput', () => {
await act(() => {
root.render(stub);
});
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev([
+ await act(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
'`value` prop on `input` should not be null. ' +
- 'Consider using an empty string to clear the component or `undefined` for uncontrolled components',
+ 'Consider using an empty string to clear the component or `undefined` for uncontrolled components.\n' +
+ ' in input (at **)',
'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. ' +
@@ -2087,18 +2115,17 @@ describe('ReactDOMInput', () => {
await act(() => {
root.render(stub);
});
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ 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 **)',
- );
+ ]);
});
it('should warn if uncontrolled input (value is undefined) switches to controlled', async () => {
@@ -2106,42 +2133,40 @@ describe('ReactDOMInput', () => {
await act(() => {
root.render(stub);
});
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ await act(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
'A component is changing an uncontrolled input to be controlled. ' +
'This is likely caused by the value changing from undefined to ' +
'a defined value, 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 **)',
- );
+ ]);
});
it('should warn if uncontrolled input (value is null) switches to controlled', async () => {
const stub = ;
- await expect(async () => {
- await act(() => {
- root.render(stub);
- });
- }).toErrorDev(
+ await act(() => {
+ root.render(stub);
+ });
+ assertConsoleErrorDev([
'`value` prop on `input` should not be null. ' +
- 'Consider using an empty string to clear the component or `undefined` for uncontrolled components.',
- );
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ 'Consider using an empty string to clear the component or `undefined` for uncontrolled components.\n' +
+ ' in input (at **)',
+ ]);
+ await act(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
'A component is changing an uncontrolled input to be controlled. ' +
'This is likely caused by the value changing from undefined to ' +
'a defined value, 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 **)',
- );
+ ]);
});
it('should warn if controlled checkbox switches to uncontrolled (checked is undefined)', async () => {
@@ -2151,18 +2176,17 @@ describe('ReactDOMInput', () => {
await act(() => {
root.render(stub);
});
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ 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 **)',
- );
+ ]);
});
it('should warn if controlled checkbox switches to uncontrolled (checked is null)', async () => {
@@ -2172,18 +2196,17 @@ describe('ReactDOMInput', () => {
await act(() => {
root.render(stub);
});
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ 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 **)',
- );
+ ]);
});
it('should warn if controlled checkbox switches to uncontrolled with defaultChecked', async () => {
@@ -2193,18 +2216,17 @@ describe('ReactDOMInput', () => {
await act(() => {
root.render(stub);
});
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ 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 **)',
- );
+ ]);
});
it('should warn if uncontrolled checkbox (checked is undefined) switches to controlled', async () => {
@@ -2212,18 +2234,17 @@ describe('ReactDOMInput', () => {
await act(() => {
root.render(stub);
});
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ await act(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
'A component is changing an uncontrolled input to be controlled. ' +
'This is likely caused by the value changing from undefined to ' +
'a defined value, 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 **)',
- );
+ ]);
});
it('should warn if uncontrolled checkbox (checked is null) switches to controlled', async () => {
@@ -2231,18 +2252,17 @@ describe('ReactDOMInput', () => {
await act(() => {
root.render(stub);
});
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ await act(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
'A component is changing an uncontrolled input to be controlled. ' +
'This is likely caused by the value changing from undefined to ' +
'a defined value, 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 **)',
- );
+ ]);
});
it('should warn if controlled radio switches to uncontrolled (checked is undefined)', async () => {
@@ -2250,18 +2270,17 @@ describe('ReactDOMInput', () => {
await act(() => {
root.render(stub);
});
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ 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 **)',
- );
+ ]);
});
it('should warn if controlled radio switches to uncontrolled (checked is null)', async () => {
@@ -2269,18 +2288,17 @@ describe('ReactDOMInput', () => {
await act(() => {
root.render(stub);
});
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ 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 **)',
- );
+ ]);
});
it('should warn if controlled radio switches to uncontrolled with defaultChecked', async () => {
@@ -2288,18 +2306,17 @@ describe('ReactDOMInput', () => {
await act(() => {
root.render(stub);
});
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ 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 **)',
- );
+ ]);
});
it('should warn if uncontrolled radio (checked is undefined) switches to controlled', async () => {
@@ -2307,18 +2324,17 @@ describe('ReactDOMInput', () => {
await act(() => {
root.render(stub);
});
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ await act(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
'A component is changing an uncontrolled input to be controlled. ' +
'This is likely caused by the value changing from undefined to ' +
'a defined value, 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 **)',
- );
+ ]);
});
it('should warn if uncontrolled radio (checked is null) switches to controlled', async () => {
@@ -2326,18 +2342,17 @@ describe('ReactDOMInput', () => {
await act(() => {
root.render(stub);
});
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ await act(() => {
+ root.render( );
+ });
+ assertConsoleErrorDev([
'A component is changing an uncontrolled input to be controlled. ' +
'This is likely caused by the value changing from undefined to ' +
'a defined value, 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 **)',
- );
+ ]);
});
it('should not warn if radio value changes but never becomes controlled', async () => {
@@ -2390,18 +2405,17 @@ describe('ReactDOMInput', () => {
/>,
);
});
- await expect(async () => {
- await act(() => {
- root.render( );
- });
- }).toErrorDev(
+ 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 **)',
- );
+ ]);
});
it('sets type, step, min, max before value always', async () => {
@@ -2747,9 +2761,15 @@ describe('ReactDOMInput', () => {
}
it('reverts the value attribute to the initial value', async () => {
- await expect(renderInputWithStringThenWithUndefined).toErrorDev(
- 'A component is changing a controlled input to be uncontrolled.',
- );
+ await renderInputWithStringThenWithUndefined();
+ 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 **)\n' +
+ ' in Input (at **)',
+ ]);
if (disableInputAttributeSyncing) {
expect(input.getAttribute('value')).toBe(null);
} else {
@@ -2758,9 +2778,15 @@ describe('ReactDOMInput', () => {
});
it('preserves the value property', async () => {
- await expect(renderInputWithStringThenWithUndefined).toErrorDev(
- 'A component is changing a controlled input to be uncontrolled.',
- );
+ await renderInputWithStringThenWithUndefined();
+ 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 **)\n' +
+ ' in Input (at **)',
+ ]);
expect(input.value).toBe('latest');
});
});
@@ -2798,11 +2824,19 @@ describe('ReactDOMInput', () => {
}
it('reverts the value attribute to the initial value', async () => {
- await expect(renderInputWithStringThenWithNull).toErrorDev([
+ await renderInputWithStringThenWithNull();
+ assertConsoleErrorDev([
'`value` prop on `input` should not be null. ' +
'Consider using an empty string to clear the component ' +
- 'or `undefined` for uncontrolled components.',
- 'A component is changing a controlled input to be uncontrolled.',
+ 'or `undefined` for uncontrolled components.\n' +
+ ' in input (at **)\n' +
+ ' in Input (at **)',
+ '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 **)\n' +
+ ' in Input (at **)',
]);
if (disableInputAttributeSyncing) {
expect(input.getAttribute('value')).toBe(null);
@@ -2812,11 +2846,19 @@ describe('ReactDOMInput', () => {
});
it('preserves the value property', async () => {
- await expect(renderInputWithStringThenWithNull).toErrorDev([
+ await renderInputWithStringThenWithNull();
+ assertConsoleErrorDev([
'`value` prop on `input` should not be null. ' +
'Consider using an empty string to clear the component ' +
- 'or `undefined` for uncontrolled components.',
- 'A component is changing a controlled input to be uncontrolled.',
+ 'or `undefined` for uncontrolled components.\n' +
+ ' in input (at **)\n' +
+ ' in Input (at **)',
+ '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 **)\n' +
+ ' in Input (at **)',
]);
expect(input.value).toBe('latest');
});
@@ -2824,11 +2866,15 @@ describe('ReactDOMInput', () => {
describe('When given a Symbol value', function () {
it('treats initial Symbol value as an empty string', async () => {
- await expect(async () => {
- await act(() => {
- root.render( {}} />);
- });
- }).toErrorDev('Invalid value for prop `value`');
+ await act(() => {
+ root.render( {}} />);
+ });
+ assertConsoleErrorDev([
+ 'Invalid value for prop `value` 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 input (at **)',
+ ]);
const node = container.firstChild;
expect(node.value).toBe('');
@@ -2843,11 +2889,15 @@ describe('ReactDOMInput', () => {
await act(() => {
root.render( {}} />);
});
- await expect(async () => {
- await act(() => {
- root.render( {}} />);
- });
- }).toErrorDev('Invalid value for prop `value`');
+ await act(() => {
+ root.render( {}} />);
+ });
+ assertConsoleErrorDev([
+ 'Invalid value for prop `value` 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 input (at **)',
+ ]);
const node = container.firstChild;
expect(node.value).toBe('');
@@ -2890,11 +2940,15 @@ describe('ReactDOMInput', () => {
describe('When given a function value', function () {
it('treats initial function value as an empty string', async () => {
- await expect(async () => {
- await act(() => {
- root.render( {}} onChange={() => {}} />);
- });
- }).toErrorDev('Invalid value for prop `value`');
+ await act(() => {
+ root.render( {}} onChange={() => {}} />);
+ });
+ assertConsoleErrorDev([
+ 'Invalid value for prop `value` 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 input (at **)',
+ ]);
const node = container.firstChild;
expect(node.value).toBe('');
@@ -2909,11 +2963,15 @@ describe('ReactDOMInput', () => {
await act(() => {
root.render( {}} />);
});
- await expect(async () => {
- await act(() => {
- root.render( {}} onChange={() => {}} />);
- });
- }).toErrorDev('Invalid value for prop `value`');
+ await act(() => {
+ root.render( {}} onChange={() => {}} />);
+ });
+ assertConsoleErrorDev([
+ 'Invalid value for prop `value` 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 input (at **)',
+ ]);
const node = container.firstChild;
expect(node.value).toBe('');