[assert helpers] react-dom (pt2) (#31902)
Converts more react-dom tests
Ricky committed
Jan 2, 2025 at 15:53 UTC
7c11aad3746d7aa89435d12322e79a72896d5a9b
7 files changed
+628
-448
packages/react-dom/src/__tests__/ReactDOMComponentTree-test.js
+11
-8
@@ -14,11 +14,14 @@ describe('ReactDOMComponentTree', () => {
14
let ReactDOMClient;
15
let act;
16
let container;
17
+ let assertConsoleErrorDev;
18
19
beforeEach(() => {
20
React = require('react');
21
ReactDOMClient = require('react-dom/client');
22
act = require('internal-test-utils').act;
23
+ assertConsoleErrorDev =
24
+ require('internal-test-utils').assertConsoleErrorDev;
25
26
container = document.createElement('div');
27
document.body.appendChild(container);
@@ -190,18 +193,18 @@ describe('ReactDOMComponentTree', () => {
193
root.render(<Controlled />);
194
});
195
193
- await expect(
194
- async () =>
195
- await act(() => {
196
- simulateInput(inputRef.current, finishValue);
197
- }),
198
- ).toErrorDev(
196
+ await act(() => {
197
+ simulateInput(inputRef.current, finishValue);
198
+ });
199
+ assertConsoleErrorDev([
200
'A component is changing an uncontrolled input to be controlled. ' +
201
'This is likely caused by the value changing from undefined to ' +
202
'a defined value, which should not happen. ' +
203
'Decide between using a controlled or uncontrolled input ' +
204
'element for the lifetime of the component. More info: ' +
204
- 'https://react.dev/link/controlled-components',
205
- );
205
+ 'https://react.dev/link/controlled-components\n' +
206
+ ' in input (at **)\n' +
207
+ ' in Controlled (at **)',
208
+ ]);
209
});
210
});
packages/react-dom/src/__tests__/ReactDOMFiber-test.js
+17
-14
@@ -746,8 +746,13 @@ describe('ReactDOMFiber', () => {
746
root.render(<Parent />);
747
});
748
assertConsoleErrorDev([
749
- 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
750
- 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
749
+ 'Parent uses the legacy childContextTypes API which will soon be removed. ' +
750
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
751
+ ' in Parent (at **)',
752
+ 'Component uses the legacy contextTypes API which will soon be removed. ' +
753
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
754
+ (gate('enableOwnerStacks') ? '' : ' in Component (at **)\n') +
755
+ ' in Parent (at **)',
756
]);
757
expect(container.innerHTML).toBe('');
758
expect(portalContainer.innerHTML).toBe('<div>bar</div>');
@@ -957,15 +962,14 @@ describe('ReactDOMFiber', () => {
962
return <div onClick="woops" />;
963
}
964
}
960
- expect(() => {
961
- ReactDOM.flushSync(() => {
962
- root.render(<Example />);
963
- });
964
- }).toErrorDev(
965
+ ReactDOM.flushSync(() => {
966
+ root.render(<Example />);
967
+ });
968
+ assertConsoleErrorDev([
969
'Expected `onClick` listener to be a function, instead got a value of `string` type.\n' +
970
' in div (at **)\n' +
971
' in Example (at **)',
968
- );
972
+ ]);
973
});
974
975
it('should warn with a special message for `false` event listeners', () => {
@@ -974,17 +978,16 @@ describe('ReactDOMFiber', () => {
978
return <div onClick={false} />;
979
}
980
}
977
- expect(() => {
978
- ReactDOM.flushSync(() => {
979
- root.render(<Example />);
980
- });
981
- }).toErrorDev(
981
+ ReactDOM.flushSync(() => {
982
+ root.render(<Example />);
983
+ });
984
+ assertConsoleErrorDev([
985
'Expected `onClick` listener to be a function, instead got `false`.\n\n' +
986
'If you used to conditionally omit it with onClick={condition && value}, ' +
987
'pass onClick={condition ? value : undefined} instead.\n' +
988
' in div (at **)\n' +
989
' in Example (at **)',
987
- );
990
+ ]);
991
});
992
993
it('should not update event handlers until commit', async () => {
packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
+12
-5
@@ -19,6 +19,7 @@ let waitForAll;
19
let waitFor;
20
let waitForMicrotasks;
21
let assertLog;
22
+let assertConsoleErrorDev;
23
24
const setUntrackedInputValue = Object.getOwnPropertyDescriptor(
25
HTMLInputElement.prototype,
@@ -34,6 +35,8 @@ describe('ReactDOMFiberAsync', () => {
35
ReactDOM = require('react-dom');
36
ReactDOMClient = require('react-dom/client');
37
act = require('internal-test-utils').act;
38
+ assertConsoleErrorDev =
39
+ require('internal-test-utils').assertConsoleErrorDev;
40
Scheduler = require('scheduler');
41
42
const InternalTestUtils = require('internal-test-utils');
@@ -176,11 +179,15 @@ describe('ReactDOMFiberAsync', () => {
179
root.render(<Component />);
180
});
181
// Update
179
- expect(() => {
180
- ReactDOM.flushSync(() => {
181
- root.render(<Component />);
182
- });
183
- }).toErrorDev('flushSync was called from inside a lifecycle method');
182
+ ReactDOM.flushSync(() => {
183
+ root.render(<Component />);
184
+ });
185
+ assertConsoleErrorDev([
186
+ 'flushSync was called from inside a lifecycle method. ' +
187
+ 'React cannot flush when React is already rendering. ' +
188
+ 'Consider moving this call to a scheduler task or micro task.\n' +
189
+ ' in Component (at **)',
190
+ ]);
191
});
192
193
describe('concurrent mode', () => {
packages/react-dom/src/__tests__/ReactDOMFizzForm-test.js
+70
-20
@@ -26,6 +26,7 @@ let useFormStatus;
26
let useOptimistic;
27
let useActionState;
28
let Scheduler;
29
+let assertConsoleErrorDev;
30
31
describe('ReactDOMFizzForm', () => {
32
beforeEach(() => {
@@ -38,6 +39,8 @@ describe('ReactDOMFizzForm', () => {
39
useFormStatus = require('react-dom').useFormStatus;
40
useOptimistic = require('react').useOptimistic;
41
act = require('internal-test-utils').act;
42
+ assertConsoleErrorDev =
43
+ require('internal-test-utils').assertConsoleErrorDev;
44
container = document.createElement('div');
45
document.body.appendChild(container);
46
// TODO: Test the old api but it warns so needs warnings to be asserted.
@@ -195,12 +198,26 @@ describe('ReactDOMFizzForm', () => {
198
ReactDOMServer.renderToReadableStream(<App />),
199
);
200
await readIntoContainer(stream);
198
- await expect(async () => {
199
- await act(async () => {
200
- ReactDOMClient.hydrateRoot(container, <App isClient={true} />);
201
- });
202
- }).toErrorDev(
203
- "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties.",
201
+ await act(async () => {
202
+ ReactDOMClient.hydrateRoot(container, <App isClient={true} />);
203
+ });
204
+ assertConsoleErrorDev(
205
+ [
206
+ "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
207
+ "This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n" +
208
+ "- A server/client branch `if (typeof window !== 'undefined')`.\n" +
209
+ "- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
210
+ "- Date formatting in a user's locale which doesn't match the server.\n" +
211
+ '- External changing data without sending a snapshot of it along with the HTML.\n' +
212
+ '- Invalid HTML tag nesting.\n\n' +
213
+ 'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n\n' +
214
+ 'https://react.dev/link/hydration-mismatch\n\n' +
215
+ ' <App isClient={true}>\n' +
216
+ ' <form\n' +
217
+ '+ action="action"\n' +
218
+ '- action="function"\n' +
219
+ ' >\n',
220
+ ],
221
{withoutStack: true},
222
);
223
});
@@ -357,23 +374,56 @@ describe('ReactDOMFizzForm', () => {
374
375
// Specifying the extra form fields are a DEV error, but we expect it
376
// to eventually still be patched up after an update.
360
- await expect(async () => {
361
- const stream = await serverAct(() =>
362
- ReactDOMServer.renderToReadableStream(<App />),
363
- );
364
- await readIntoContainer(stream);
365
- }).toErrorDev([
366
- 'Cannot specify a encType or method for a form that specifies a function as the action.',
367
- 'Cannot specify a formTarget for a button that specifies a function as a formAction.',
377
+ const stream = await serverAct(() =>
378
+ ReactDOMServer.renderToReadableStream(<App />),
379
+ );
380
+ await readIntoContainer(stream);
381
+ assertConsoleErrorDev([
382
+ 'Cannot specify a encType or method for a form that specifies a function as the action. ' +
383
+ 'React provides those automatically. They will get overridden.\n' +
384
+ ' in form (at **)\n' +
385
+ ' in App (at **)',
386
+ 'Cannot specify a formTarget for a button that specifies a function as a formAction. ' +
387
+ 'The function will always be executed in the same window.\n' +
388
+ ' in input (at **)\n' +
389
+ (gate('enableOwnerStacks') ? '' : ' in form (at **)\n') +
390
+ ' in App (at **)',
391
]);
392
let root;
370
- await expect(async () => {
371
- await act(async () => {
372
- root = ReactDOMClient.hydrateRoot(container, <App />);
373
- });
374
- }).toErrorDev(
393
+ await act(async () => {
394
+ root = ReactDOMClient.hydrateRoot(container, <App />);
395
+ });
396
+ assertConsoleErrorDev(
397
[
376
- "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties.",
398
+ "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
399
+ "This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n" +
400
+ "- A server/client branch `if (typeof window !== 'undefined')`.\n" +
401
+ "- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
402
+ "- Date formatting in a user's locale which doesn't match the server.\n" +
403
+ '- External changing data without sending a snapshot of it along with the HTML.\n' +
404
+ '- Invalid HTML tag nesting.\n\n' +
405
+ 'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n\n' +
406
+ 'https://react.dev/link/hydration-mismatch\n\n' +
407
+ ' <App>\n' +
408
+ ' <form\n' +
409
+ ' action={function action}\n' +
410
+ ' ref={{current:null}}\n' +
411
+ '+ method="DELETE"\n' +
412
+ '- method={null}\n' +
413
+ ' >\n' +
414
+ ' <input\n' +
415
+ ' type="submit"\n' +
416
+ ' formAction={function action}\n' +
417
+ ' ref={{current:null}}\n' +
418
+ '+ formTarget="elsewhere"\n' +
419
+ '- formTarget={null}\n' +
420
+ ' >\n' +
421
+ ' <button\n' +
422
+ ' formAction={function action}\n' +
423
+ ' ref={{current:null}}\n' +
424
+ '+ formEncType="text/plain"\n' +
425
+ '- formEncType={null}\n' +
426
+ ' >\n',
427
],
428
{withoutStack: true},
429
);
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+82
-44
@@ -1925,8 +1925,18 @@ describe('ReactDOMFizzServer', () => {
1925
pipe(writable);
1926
});
1927
assertConsoleErrorDev([
1928
- 'TestProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
1929
- 'TestConsumer uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
1928
+ 'TestProvider uses the legacy childContextTypes API which will soon be removed. ' +
1929
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
1930
+ ' in TestProvider (at **)',
1931
+ 'TestConsumer uses the legacy contextTypes API which will soon be removed. ' +
1932
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
1933
+ ' in TestConsumer (at **)' +
1934
+ (gate('enableOwnerStacks')
1935
+ ? ''
1936
+ : '\n in TestProvider (at **)' +
1937
+ '\n in Suspense (at **)' +
1938
+ '\n in div (at **)' +
1939
+ '\n in TestProvider (at **)'),
1940
]);
1941
expect(getVisibleChildren(container)).toEqual(
1942
<div>
@@ -3506,13 +3516,14 @@ describe('ReactDOMFizzServer', () => {
3516
</div>,
3517
{
3518
onRecoverableError(error, errorInfo) {
3509
- expect(() => {
3510
- expect(error.digest).toBe('a digest');
3511
- expect(errorInfo.digest).toBe(undefined);
3512
- }).toErrorDev(
3513
- 'You are accessing "digest" from the errorInfo object passed to onRecoverableError.' +
3514
- ' This property is no longer provided as part of errorInfo but can be accessed as a property' +
3515
- ' of the Error instance itself.',
3519
+ expect(error.digest).toBe('a digest');
3520
+ expect(errorInfo.digest).toBe(undefined);
3521
+ assertConsoleErrorDev(
3522
+ [
3523
+ 'You are accessing "digest" from the errorInfo object passed to onRecoverableError.' +
3524
+ ' This property is no longer provided as part of errorInfo but can be accessed as a property' +
3525
+ ' of the Error instance itself.',
3526
+ ],
3527
{withoutStack: true},
3528
);
3529
},
@@ -5777,13 +5788,24 @@ describe('ReactDOMFizzServer', () => {
5788
);
5789
}
5790
5780
- await expect(async () => {
5781
- await act(() => {
5782
- const {pipe} = renderToPipeableStream(<App />);
5783
- pipe(writable);
5784
- });
5785
- }).toErrorDev([
5786
- 'React expects the `children` prop of <title> tags to be a string, number, bigint, or object with a novel `toString` method but found an Array with length 2 instead. Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert `children` of <title> tags to a single string value which is why Arrays of length greater than 1 are not supported. When using JSX it can be common to combine text nodes and value nodes. For example: <title>hello {nameOfUser}</title>. While not immediately apparent, `children` in this case is an Array with length 2. If your `children` prop is using this form try rewriting it using a template string: <title>{`hello ${nameOfUser}`}</title>.',
5791
+ await act(() => {
5792
+ const {pipe} = renderToPipeableStream(<App />);
5793
+ pipe(writable);
5794
+ });
5795
+ assertConsoleErrorDev([
5796
+ 'React expects the `children` prop of <title> tags to be a string, number, bigint, ' +
5797
+ 'or object with a novel `toString` method but found an Array with length 2 instead. ' +
5798
+ 'Browsers treat all child Nodes of <title> tags as Text content and React expects ' +
5799
+ 'to be able to convert `children` of <title> tags to a single string value which is why ' +
5800
+ 'Arrays of length greater than 1 are not supported. ' +
5801
+ 'When using JSX it can be common to combine text nodes and value nodes. ' +
5802
+ 'For example: <title>hello {nameOfUser}</title>. ' +
5803
+ 'While not immediately apparent, `children` in this case is an Array with length 2. ' +
5804
+ 'If your `children` prop is using this form try rewriting it using a template string: ' +
5805
+ '<title>{`hello ${nameOfUser}`}</title>.\n' +
5806
+ ' in title (at **)\n' +
5807
+ (gate('enableOwnerStacks') ? '' : ' in head (at **)\n') +
5808
+ ' in App (at **)',
5809
]);
5810
5811
expect(getVisibleChildren(document.head)).toEqual(<title />);
@@ -5814,13 +5836,22 @@ describe('ReactDOMFizzServer', () => {
5836
);
5837
}
5838
5817
- await expect(async () => {
5818
- await act(() => {
5819
- const {pipe} = renderToPipeableStream(<App />);
5820
- pipe(writable);
5821
- });
5822
- }).toErrorDev([
5823
- 'React expects the `children` prop of <title> tags to be a string, number, bigint, or object with a novel `toString` method but found an object that appears to be a React element which never implements a suitable `toString` method. Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert children of <title> tags to a single string value which is why rendering React elements is not supported. If the `children` of <title> is a React Component try moving the <title> tag into that component. If the `children` of <title> is some HTML markup change it to be Text only to be valid HTML.',
5839
+ await act(() => {
5840
+ const {pipe} = renderToPipeableStream(<App />);
5841
+ pipe(writable);
5842
+ });
5843
+ assertConsoleErrorDev([
5844
+ 'React expects the `children` prop of <title> tags to be a string, number, bigint, ' +
5845
+ 'or object with a novel `toString` method but found an object that appears to be a ' +
5846
+ 'React element which never implements a suitable `toString` method. ' +
5847
+ 'Browsers treat all child Nodes of <title> tags as Text content and React expects ' +
5848
+ 'to be able to convert children of <title> tags to a single string value which is ' +
5849
+ 'why rendering React elements is not supported. If the `children` of <title> is a ' +
5850
+ 'React Component try moving the <title> tag into that component. ' +
5851
+ 'If the `children` of <title> is some HTML markup change it to be Text only to be valid HTML.\n' +
5852
+ ' in title (at **)\n' +
5853
+ (gate('enableOwnerStacks') ? '' : ' in head (at **)\n') +
5854
+ ' in App (at **)',
5855
]);
5856
// object titles are toStringed when float is on
5857
expect(getVisibleChildren(document.head)).toEqual(
@@ -5849,13 +5880,22 @@ describe('ReactDOMFizzServer', () => {
5880
);
5881
}
5882
5852
- await expect(async () => {
5853
- await act(() => {
5854
- const {pipe} = renderToPipeableStream(<App />);
5855
- pipe(writable);
5856
- });
5857
- }).toErrorDev([
5858
- 'React expects the `children` prop of <title> tags to be a string, number, bigint, or object with a novel `toString` method but found an object that does not implement a suitable `toString` method. Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert children of <title> tags to a single string value. Using the default `toString` method available on every object is almost certainly an error. Consider whether the `children` of this <title> is an object in error and change it to a string or number value if so. Otherwise implement a `toString` method that React can use to produce a valid <title>.',
5883
+ await act(() => {
5884
+ const {pipe} = renderToPipeableStream(<App />);
5885
+ pipe(writable);
5886
+ });
5887
+ assertConsoleErrorDev([
5888
+ 'React expects the `children` prop of <title> tags to be a string, number, bigint, ' +
5889
+ 'or object with a novel `toString` method but found an object that does not implement a ' +
5890
+ 'suitable `toString` method. Browsers treat all child Nodes of <title> tags as Text ' +
5891
+ 'content and React expects to be able to convert children of <title> tags to a single string value. ' +
5892
+ 'Using the default `toString` method available on every object is almost certainly an error. ' +
5893
+ 'Consider whether the `children` of this <title> is an object in error and change it to a ' +
5894
+ 'string or number value if so. Otherwise implement a `toString` method that React can ' +
5895
+ 'use to produce a valid <title>.\n' +
5896
+ ' in title (at **)\n' +
5897
+ (gate('enableOwnerStacks') ? '' : ' in head (at **)\n') +
5898
+ ' in App (at **)',
5899
]);
5900
// object titles are toStringed when float is on
5901
expect(getVisibleChildren(document.head)).toEqual(
@@ -8381,12 +8421,11 @@ describe('ReactDOMFizzServer', () => {
8421
return <div>{children}</div>;
8422
}
8423
8384
- await expect(async () => {
8385
- await act(() => {
8386
- const {pipe} = renderToPipeableStream(<Foo />);
8387
- pipe(writable);
8388
- });
8389
- }).toErrorDev(
8424
+ await act(() => {
8425
+ const {pipe} = renderToPipeableStream(<Foo />);
8426
+ pipe(writable);
8427
+ });
8428
+ assertConsoleErrorDev([
8429
'Using Iterators as children is unsupported and will likely yield ' +
8430
'unexpected results because enumerating a generator mutates it. ' +
8431
'You may convert it to an array with `Array.from()` or the ' +
@@ -8394,7 +8433,7 @@ describe('ReactDOMFizzServer', () => {
8433
'Iterable that can iterate multiple times over the same items.\n' +
8434
' in div (at **)\n' +
8435
' in Foo (at **)',
8397
- );
8436
+ ]);
8437
8438
expect(document.body.textContent).toBe('HelloWorld');
8439
});
@@ -8420,19 +8459,18 @@ describe('ReactDOMFizzServer', () => {
8459
return iterator;
8460
}
8461
8423
- await expect(async () => {
8424
- await act(() => {
8425
- const {pipe} = renderToPipeableStream(<Foo />);
8426
- pipe(writable);
8427
- });
8428
- }).toErrorDev(
8462
+ await act(() => {
8463
+ const {pipe} = renderToPipeableStream(<Foo />);
8464
+ pipe(writable);
8465
+ });
8466
+ assertConsoleErrorDev([
8467
'Using Iterators as children is unsupported and will likely yield ' +
8468
'unexpected results because enumerating a generator mutates it. ' +
8469
'You may convert it to an array with `Array.from()` or the ' +
8470
'`[...spread]` operator before rendering. You can also use an ' +
8471
'Iterable that can iterate multiple times over the same items.\n' +
8472
' in Foo (at **)',
8435
- );
8473
+ ]);
8474
8475
expect(document.body.textContent).toBe('HelloWorld');
8476
});
packages/react-dom/src/__tests__/ReactDOMForm-test.js
+70
-49
@@ -361,7 +361,7 @@ describe('ReactDOMForm', () => {
361
expect(actionCalled).toBe(false);
362
});
363
364
- it('should only submit the inner of nested forms', async () => {
364
+ it('should submit the inner of nested forms', async () => {
365
const ref = React.createRef();
366
let data;
367
@@ -373,19 +373,18 @@ describe('ReactDOMForm', () => {
373
}
374
375
const root = ReactDOMClient.createRoot(container);
376
- await expect(async () => {
377
- await act(async () => {
378
- // This isn't valid HTML but just in case.
379
- root.render(
380
- <form action={outerAction}>
381
- <input type="text" name="data" defaultValue="outer" />
382
- <form action={innerAction} ref={ref}>
383
- <input type="text" name="data" defaultValue="inner" />
384
- </form>
385
- </form>,
386
- );
387
- });
388
- }).toErrorDev(
376
+ await act(async () => {
377
+ // This isn't valid HTML but just in case.
378
+ root.render(
379
+ <form action={outerAction}>
380
+ <input type="text" name="data" defaultValue="outer" />
381
+ <form action={innerAction} ref={ref}>
382
+ <input type="text" name="data" defaultValue="inner" />
383
+ </form>
384
+ </form>,
385
+ );
386
+ });
387
+ assertConsoleErrorDev([
388
'In HTML, <form> cannot be a descendant of <form>.\n' +
389
'This will cause a hydration error.\n' +
390
'\n' +
@@ -394,14 +393,14 @@ describe('ReactDOMForm', () => {
393
'> <form action={function innerAction} ref={{current:null}}>\n' +
394
'\n in form (at **)' +
395
(gate(flags => flags.enableOwnerStacks) ? '' : '\n in form (at **)'),
397
- );
396
+ ]);
397
398
await submit(ref.current);
399
400
expect(data).toBe('innerinner');
401
});
402
404
- it('should only submit once if one root is nested inside the other', async () => {
403
+ it('should submit once if one root is nested inside the other', async () => {
404
const ref = React.createRef();
405
let outerCalled = 0;
406
let innerCalled = 0;
@@ -444,7 +443,7 @@ describe('ReactDOMForm', () => {
443
expect(innerCalled).toBe(1);
444
});
445
447
- it('should only submit once if a portal is nested inside its own root', async () => {
446
+ it('should submit once if a portal is nested inside its own root', async () => {
447
const ref = React.createRef();
448
let outerCalled = 0;
449
let innerCalled = 0;
@@ -565,29 +564,32 @@ describe('ReactDOMForm', () => {
564
}
565
566
const root = ReactDOMClient.createRoot(container);
568
- await expect(async () => {
569
- await act(async () => {
570
- root.render(
571
- <form>
572
- <input
573
- type="submit"
574
- name="button"
575
- value="delete"
576
- ref={inputRef}
577
- formAction={action}
578
- />
579
- <button
580
- name="button"
581
- value="edit"
582
- ref={buttonRef}
583
- formAction={action}>
584
- Edit
585
- </button>
586
- </form>,
587
- );
588
- });
589
- }).toErrorDev([
590
- 'Cannot specify a "name" prop for a button that specifies a function as a formAction.',
567
+ await act(async () => {
568
+ root.render(
569
+ <form>
570
+ <input
571
+ type="submit"
572
+ name="button"
573
+ value="delete"
574
+ ref={inputRef}
575
+ formAction={action}
576
+ />
577
+ <button
578
+ name="button"
579
+ value="edit"
580
+ ref={buttonRef}
581
+ formAction={action}>
582
+ Edit
583
+ </button>
584
+ </form>,
585
+ );
586
+ });
587
+ assertConsoleErrorDev([
588
+ 'Cannot specify a "name" prop for a button that specifies a function as a formAction. ' +
589
+ 'React needs it to encode which action should be invoked. ' +
590
+ 'It will get overridden.\n' +
591
+ ' in input (at **)' +
592
+ (gate('enableOwnerStacks') ? '' : '\n in form (at **)'),
593
]);
594
595
await submit(inputRef.current);
@@ -1492,8 +1494,9 @@ describe('ReactDOMForm', () => {
1494
await act(() => dispatch());
1495
assertConsoleErrorDev([
1496
[
1495
- 'An async function was passed to useActionState, but it was ' +
1496
- 'dispatched outside of an action context',
1497
+ 'An async function was passed to useActionState, but it was dispatched outside of an action context. ' +
1498
+ 'This is likely not what you intended. ' +
1499
+ 'Either pass the dispatch function to an `action` prop, or dispatch manually inside `startTransition`',
1500
{withoutStack: true},
1501
],
1502
]);
@@ -1919,11 +1922,16 @@ describe('ReactDOMForm', () => {
1922
expect(inputRef.current.value).toBe(' Updated ');
1923
1924
// This triggers a synchronous requestFormReset, and a warning
1922
- await expect(async () => {
1923
- await act(() => resolveText('Wait 1'));
1924
- }).toErrorDev(['requestFormReset was called outside a transition'], {
1925
- withoutStack: true,
1926
- });
1925
+ await act(() => resolveText('Wait 1'));
1926
+ assertConsoleErrorDev(
1927
+ [
1928
+ 'requestFormReset was called outside a transition or action. ' +
1929
+ 'To fix, move to an action, or wrap with startTransition.',
1930
+ ],
1931
+ {
1932
+ withoutStack: true,
1933
+ },
1934
+ );
1935
assertLog(['Request form reset']);
1936
1937
// The form was reset even though the action didn't finish.
@@ -1957,7 +1965,14 @@ describe('ReactDOMForm', () => {
1965
1966
// Symbols are coerced to null, so this should fire the form action
1967
await act(() => root.render(<App submitterAction={Symbol()} />));
1960
- assertConsoleErrorDev(['Invalid value for prop `formAction`']);
1968
+ assertConsoleErrorDev([
1969
+ 'Invalid value for prop `formAction` on <button> tag. ' +
1970
+ 'Either remove it from the element, or pass a string or number value to keep it in the DOM. ' +
1971
+ 'For details, see https://react.dev/link/attribute-behavior \n' +
1972
+ ' in button (at **)\n' +
1973
+ (gate('enableOwnerStacks') ? '' : ' in form (at **)\n') +
1974
+ ' in App (at **)',
1975
+ ]);
1976
await submit(buttonRef.current);
1977
assertLog(['Form action']);
1978
@@ -2201,7 +2216,13 @@ describe('ReactDOMForm', () => {
2216
2217
// Symbols are coerced to null
2218
await act(() => root.render(<Form action={Symbol()} />));
2204
- assertConsoleErrorDev(['Invalid value for prop `action`']);
2219
+ assertConsoleErrorDev([
2220
+ 'Invalid value for prop `action` on <form> tag. ' +
2221
+ 'Either remove it from the element, or pass a string or number value to keep it in the DOM. ' +
2222
+ 'For details, see https://react.dev/link/attribute-behavior \n' +
2223
+ ' in form (at **)\n' +
2224
+ ' in Form (at **)',
2225
+ ]);
2226
await submit(formRef.current);
2227
assertLog([null]);
2228
packages/react-dom/src/__tests__/ReactDOMInput-test.js
+366
-308
@@ -26,6 +26,7 @@ describe('ReactDOMInput', () => {
26
let setUntrackedChecked;
27
let container;
28
let root;
29
+ let assertConsoleErrorDev;
30
31
function dispatchEventOnNode(node, type) {
32
node.dispatchEvent(new Event(type, {bubbles: true, cancelable: true}));
@@ -96,6 +97,8 @@ describe('ReactDOMInput', () => {
97
ReactDOMServer = require('react-dom/server');
98
Scheduler = require('scheduler');
99
act = require('internal-test-utils').act;
100
+ assertConsoleErrorDev =
101
+ require('internal-test-utils').assertConsoleErrorDev;
102
assertLog = require('internal-test-utils').assertLog;
103
104
container = document.createElement('div');
@@ -109,52 +112,54 @@ describe('ReactDOMInput', () => {
112
});
113
114
it('should warn for controlled value of 0 with missing onChange', async () => {
112
- await expect(async () => {
113
- await act(() => {
114
- root.render(<input type="text" value={0} />);
115
- });
116
- }).toErrorDev(
115
+ await act(() => {
116
+ root.render(<input type="text" value={0} />);
117
+ });
118
+ assertConsoleErrorDev([
119
'You provided a `value` prop to a form ' +
120
'field without an `onChange` handler. This will render a read-only ' +
121
'field. If the field should be mutable use `defaultValue`. ' +
120
- 'Otherwise, set either `onChange` or `readOnly`.',
121
- );
122
+ 'Otherwise, set either `onChange` or `readOnly`.\n' +
123
+ ' in input (at **)',
124
+ ]);
125
});
126
127
it('should warn for controlled value of "" with missing onChange', async () => {
125
- await expect(async () => {
126
- await act(() => {
127
- root.render(<input type="text" value="" />);
128
- });
129
- }).toErrorDev(
128
+ await act(() => {
129
+ root.render(<input type="text" value="" />);
130
+ });
131
+ assertConsoleErrorDev([
132
'You provided a `value` prop to a form ' +
133
'field without an `onChange` handler. This will render a read-only ' +
134
'field. If the field should be mutable use `defaultValue`. ' +
133
- 'Otherwise, set either `onChange` or `readOnly`.',
134
- );
135
+ 'Otherwise, set either `onChange` or `readOnly`.\n' +
136
+ ' in input (at **)',
137
+ ]);
138
});
139
140
it('should warn for controlled value of "0" with missing onChange', async () => {
138
- await expect(async () => {
139
- await act(() => {
140
- root.render(<input type="text" value="0" />);
141
- });
142
- }).toErrorDev(
141
+ await act(() => {
142
+ root.render(<input type="text" value="0" />);
143
+ });
144
+ assertConsoleErrorDev([
145
'You provided a `value` prop to a form ' +
146
'field without an `onChange` handler. This will render a read-only ' +
147
'field. If the field should be mutable use `defaultValue`. ' +
146
- 'Otherwise, set either `onChange` or `readOnly`.',
147
- );
148
+ 'Otherwise, set either `onChange` or `readOnly`.\n' +
149
+ ' in input (at **)',
150
+ ]);
151
});
152
153
it('should warn for controlled value of false with missing onChange', async () => {
151
- await expect(async () => {
152
- await act(() => {
153
- root.render(<input type="checkbox" checked={false} />);
154
- });
155
- }).toErrorDev(
156
- 'You provided a `checked` prop to a form field without an `onChange` handler.',
157
- );
154
+ await act(() => {
155
+ root.render(<input type="checkbox" checked={false} />);
156
+ });
157
+ assertConsoleErrorDev([
158
+ 'You provided a `checked` prop to a form field without an `onChange` handler. ' +
159
+ 'This will render a read-only field. If the field should be mutable use `defaultChecked`. ' +
160
+ 'Otherwise, set either `onChange` or `readOnly`.\n' +
161
+ ' in input (at **)',
162
+ ]);
163
});
164
165
it('should warn with checked and no onChange handler with readOnly specified', async () => {
@@ -164,15 +169,15 @@ describe('ReactDOMInput', () => {
169
root.unmount();
170
root = ReactDOMClient.createRoot(container);
171
167
- await expect(async () => {
168
- await act(() => {
169
- root.render(<input type="checkbox" checked={false} readOnly={false} />);
170
- });
171
- }).toErrorDev(
172
+ await act(() => {
173
+ root.render(<input type="checkbox" checked={false} readOnly={false} />);
174
+ });
175
+ assertConsoleErrorDev([
176
'You provided a `checked` prop to a form field without an `onChange` handler. ' +
177
'This will render a read-only field. If the field should be mutable use `defaultChecked`. ' +
174
- 'Otherwise, set either `onChange` or `readOnly`.',
175
- );
178
+ 'Otherwise, set either `onChange` or `readOnly`.\n' +
179
+ ' in input (at **)',
180
+ ]);
181
});
182
183
it('should not warn about missing onChange in uncontrolled inputs', async () => {
@@ -213,13 +218,15 @@ describe('ReactDOMInput', () => {
218
});
219
220
it('should properly control a value even if no event listener exists', async () => {
216
- await expect(async () => {
217
- await act(() => {
218
- root.render(<input type="text" value="lion" />);
219
- });
220
- }).toErrorDev(
221
- 'You provided a `value` prop to a form field without an `onChange` handler.',
222
- );
221
+ await act(() => {
222
+ root.render(<input type="text" value="lion" />);
223
+ });
224
+ assertConsoleErrorDev([
225
+ 'You provided a `value` prop to a form field without an `onChange` handler. ' +
226
+ 'This will render a read-only field. If the field should be mutable use `defaultValue`. ' +
227
+ 'Otherwise, set either `onChange` or `readOnly`.\n' +
228
+ ' in input (at **)',
229
+ ]);
230
const node = container.firstChild;
231
expect(isValueDirty(node)).toBe(true);
232
@@ -426,14 +433,16 @@ describe('ReactDOMInput', () => {
433
}
434
435
const ref = React.createRef();
429
- await expect(async () => {
430
- await act(() => {
431
- root.render(<Stub ref={ref} />);
432
- });
433
- }).toErrorDev(
434
- 'You provided a `value` prop to a form field ' +
435
- 'without an `onChange` handler.',
436
- );
436
+ await act(() => {
437
+ root.render(<Stub ref={ref} />);
438
+ });
439
+ assertConsoleErrorDev([
440
+ 'You provided a `value` prop to a form field without an `onChange` handler. ' +
441
+ 'This will render a read-only field. If the field should be mutable use `defaultValue`. ' +
442
+ 'Otherwise, set either `onChange` or `readOnly`.\n' +
443
+ ' in input (at **)\n' +
444
+ ' in Stub (at **)',
445
+ ]);
446
const node = container.firstChild;
447
await act(() => {
448
ref.current.setState({value: '0.98'});
@@ -499,14 +508,16 @@ describe('ReactDOMInput', () => {
508
}
509
510
const ref = React.createRef();
502
- await expect(async () => {
503
- await act(() => {
504
- root.render(<Stub ref={ref} />);
505
- });
506
- }).toErrorDev(
507
- 'You provided a `value` prop to a form field ' +
508
- 'without an `onChange` handler.',
509
- );
511
+ await act(() => {
512
+ root.render(<Stub ref={ref} />);
513
+ });
514
+ assertConsoleErrorDev([
515
+ 'You provided a `value` prop to a form field without an `onChange` handler. ' +
516
+ 'This will render a read-only field. If the field should be mutable use `defaultValue`. ' +
517
+ 'Otherwise, set either `onChange` or `readOnly`.\n' +
518
+ ' in input (at **)\n' +
519
+ ' in Stub (at **)',
520
+ ]);
521
const node = container.firstChild;
522
await act(() => {
523
ref.current.setState({value: '3'});
@@ -636,13 +647,16 @@ describe('ReactDOMInput', () => {
647
const node = container.firstChild;
648
expect(node.value).toBe('0');
649
expect(isValueDirty(node)).toBe(true);
639
- await expect(async () => {
640
- await act(() => {
641
- root.render(<input type="text" defaultValue="1" />);
642
- });
643
- }).toErrorDev(
644
- 'A component is changing a controlled input to be uncontrolled.',
645
- );
650
+ await act(() => {
651
+ root.render(<input type="text" defaultValue="1" />);
652
+ });
653
+ assertConsoleErrorDev([
654
+ 'A component is changing a controlled input to be uncontrolled. ' +
655
+ 'This is likely caused by the value changing from a defined to undefined, which should not happen. ' +
656
+ 'Decide between using a controlled or uncontrolled input element for the lifetime of the component. ' +
657
+ 'More info: https://react.dev/link/controlled-components\n' +
658
+ ' in input (at **)',
659
+ ]);
660
expect(node.value).toBe('0');
661
expect(isValueDirty(node)).toBe(true);
662
});
@@ -745,15 +759,18 @@ describe('ReactDOMInput', () => {
759
}
760
}
761
await expect(async () => {
748
- await expect(async () => {
749
- await act(() => {
750
- root.render(<input defaultValue={new TemporalLike()} type="date" />);
751
- });
752
- }).toErrorDev(
753
- 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
754
- 'strings, not TemporalLike. This value must be coerced to a string before using it here.',
755
- );
762
+ await act(() => {
763
+ root.render(<input defaultValue={new TemporalLike()} type="date" />);
764
+ });
765
}).rejects.toThrowError(new TypeError('prod message'));
766
+ assertConsoleErrorDev([
767
+ 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
768
+ 'strings, not TemporalLike. This value must be coerced to a string before using it here.\n' +
769
+ ' in input (at **)',
770
+ 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
771
+ 'strings, not TemporalLike. This value must be coerced to a string before using it here.\n' +
772
+ ' in input (at **)',
773
+ ]);
774
});
775
776
it('should throw for text inputs if `defaultValue` is an object where valueOf() throws', async () => {
@@ -768,15 +785,18 @@ describe('ReactDOMInput', () => {
785
}
786
}
787
await expect(async () => {
771
- await expect(async () => {
772
- await act(() => {
773
- root.render(<input defaultValue={new TemporalLike()} type="text" />);
774
- });
775
- }).toErrorDev(
776
- 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
777
- 'strings, not TemporalLike. This value must be coerced to a string before using it here.',
778
- );
788
+ await act(() => {
789
+ root.render(<input defaultValue={new TemporalLike()} type="text" />);
790
+ });
791
}).rejects.toThrowError(new TypeError('prod message'));
792
+ assertConsoleErrorDev([
793
+ 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
794
+ 'strings, not TemporalLike. This value must be coerced to a string before using it here.\n' +
795
+ ' in input (at **)',
796
+ 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
797
+ 'strings, not TemporalLike. This value must be coerced to a string before using it here.\n' +
798
+ ' in input (at **)',
799
+ ]);
800
});
801
802
it('should throw for date inputs if `value` is an object where valueOf() throws', async () => {
@@ -791,21 +811,20 @@ describe('ReactDOMInput', () => {
811
}
812
}
813
await expect(async () => {
794
- await expect(async () => {
795
- await act(() => {
796
- root.render(
797
- <input
798
- value={new TemporalLike()}
799
- type="date"
800
- onChange={() => {}}
801
- />,
802
- );
803
- });
804
- }).toErrorDev(
805
- 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
806
- 'strings, not TemporalLike. This value must be coerced to a string before using it here.',
807
- );
814
+ await act(() => {
815
+ root.render(
816
+ <input value={new TemporalLike()} type="date" onChange={() => {}} />,
817
+ );
818
+ });
819
}).rejects.toThrowError(new TypeError('prod message'));
820
+ assertConsoleErrorDev([
821
+ 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
822
+ 'strings, not TemporalLike. This value must be coerced to a string before using it here.\n' +
823
+ ' in input (at **)',
824
+ 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
825
+ 'strings, not TemporalLike. This value must be coerced to a string before using it here.\n' +
826
+ ' in input (at **)',
827
+ ]);
828
});
829
830
it('should throw for text inputs if `value` is an object where valueOf() throws', async () => {
@@ -820,21 +839,20 @@ describe('ReactDOMInput', () => {
839
}
840
}
841
await expect(async () => {
823
- await expect(async () => {
824
- await act(() => {
825
- root.render(
826
- <input
827
- value={new TemporalLike()}
828
- type="text"
829
- onChange={() => {}}
830
- />,
831
- );
832
- });
833
- }).toErrorDev(
834
- 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
835
- 'strings, not TemporalLike. This value must be coerced to a string before using it here.',
836
- );
842
+ await act(() => {
843
+ root.render(
844
+ <input value={new TemporalLike()} type="text" onChange={() => {}} />,
845
+ );
846
+ });
847
}).rejects.toThrowError(new TypeError('prod message'));
848
+ assertConsoleErrorDev([
849
+ 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
850
+ 'strings, not TemporalLike. This value must be coerced to a string before using it here.\n' +
851
+ ' in input (at **)',
852
+ 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
853
+ 'strings, not TemporalLike. This value must be coerced to a string before using it here.\n' +
854
+ ' in input (at **)',
855
+ ]);
856
});
857
858
it('should display `value` of number 0', async () => {
@@ -1199,15 +1217,18 @@ describe('ReactDOMInput', () => {
1217
1218
// Not really relevant to this particular test, but changing to undefined
1219
// should nonetheless trigger a warning
1202
- await expect(async () => {
1203
- await act(() => {
1204
- root.render(
1205
- <input type="submit" value={undefined} onChange={emptyFunction} />,
1206
- );
1207
- });
1208
- }).toErrorDev(
1209
- 'A component is changing a controlled input to be uncontrolled.',
1210
- );
1220
+ await act(() => {
1221
+ root.render(
1222
+ <input type="submit" value={undefined} onChange={emptyFunction} />,
1223
+ );
1224
+ });
1225
+ assertConsoleErrorDev([
1226
+ 'A component is changing a controlled input to be uncontrolled. ' +
1227
+ 'This is likely caused by the value changing from a defined to undefined, which should not happen. ' +
1228
+ 'Decide between using a controlled or uncontrolled input element for the lifetime of the component. ' +
1229
+ 'More info: https://react.dev/link/controlled-components\n' +
1230
+ ' in input (at **)',
1231
+ ]);
1232
1233
const node = container.firstChild;
1234
expect(node.getAttribute('value')).toBe(null);
@@ -1221,15 +1242,18 @@ describe('ReactDOMInput', () => {
1242
1243
// Not really relevant to this particular test, but changing to undefined
1244
// should nonetheless trigger a warning
1224
- await expect(async () => {
1225
- await act(() => {
1226
- root.render(
1227
- <input type="reset" value={undefined} onChange={emptyFunction} />,
1228
- );
1229
- });
1230
- }).toErrorDev(
1231
- 'A component is changing a controlled input to be uncontrolled.',
1232
- );
1245
+ await act(() => {
1246
+ root.render(
1247
+ <input type="reset" value={undefined} onChange={emptyFunction} />,
1248
+ );
1249
+ });
1250
+ assertConsoleErrorDev([
1251
+ 'A component is changing a controlled input to be uncontrolled. ' +
1252
+ 'This is likely caused by the value changing from a defined to undefined, which should not happen. ' +
1253
+ 'Decide between using a controlled or uncontrolled input element for the lifetime of the component. ' +
1254
+ 'More info: https://react.dev/link/controlled-components\n' +
1255
+ ' in input (at **)',
1256
+ ]);
1257
1258
const node = container.firstChild;
1259
expect(node.getAttribute('value')).toBe(null);
@@ -1281,11 +1305,14 @@ describe('ReactDOMInput', () => {
1305
1306
it('should not set a null value on a submit input', async () => {
1307
const stub = <input type="submit" value={null} />;
1284
- await expect(async () => {
1285
- await act(() => {
1286
- root.render(stub);
1287
- });
1288
- }).toErrorDev('`value` prop on `input` should not be null');
1308
+ await act(() => {
1309
+ root.render(stub);
1310
+ });
1311
+ assertConsoleErrorDev([
1312
+ '`value` prop on `input` should not be null. ' +
1313
+ 'Consider using an empty string to clear the component or `undefined` for uncontrolled components.\n' +
1314
+ ' in input (at **)',
1315
+ ]);
1316
const node = container.firstChild;
1317
1318
// Note: it shouldn't be an empty string
@@ -1300,11 +1327,14 @@ describe('ReactDOMInput', () => {
1327
1328
it('should not set a null value on a reset input', async () => {
1329
const stub = <input type="reset" value={null} />;
1303
- await expect(async () => {
1304
- await act(() => {
1305
- root.render(stub);
1306
- });
1307
- }).toErrorDev('`value` prop on `input` should not be null');
1330
+ await act(() => {
1331
+ root.render(stub);
1332
+ });
1333
+ assertConsoleErrorDev([
1334
+ '`value` prop on `input` should not be null. ' +
1335
+ 'Consider using an empty string to clear the component or `undefined` for uncontrolled components.\n' +
1336
+ ' in input (at **)',
1337
+ ]);
1338
const node = container.firstChild;
1339
1340
// Note: it shouldn't be an empty string
@@ -1906,17 +1936,16 @@ describe('ReactDOMInput', () => {
1936
root.unmount();
1937
root = ReactDOMClient.createRoot(container);
1938
1909
- await expect(async () => {
1910
- await act(() => {
1911
- root.render(<input type="text" value="zoink" readOnly={false} />);
1912
- });
1913
- }).toErrorDev(
1939
+ await act(() => {
1940
+ root.render(<input type="text" value="zoink" readOnly={false} />);
1941
+ });
1942
+ assertConsoleErrorDev([
1943
'You provided a `value` prop to a form ' +
1944
'field without an `onChange` handler. This will render a read-only ' +
1945
'field. If the field should be mutable use `defaultValue`. ' +
1946
'Otherwise, set either `onChange` or `readOnly`.\n' +
1947
' in input (at **)',
1919
- );
1948
+ ]);
1949
});
1950
1951
it('should have a this value of undefined if bind is not used', async () => {
@@ -1958,15 +1987,15 @@ describe('ReactDOMInput', () => {
1987
});
1988
1989
it('should warn if value is null', async () => {
1961
- await expect(async () => {
1962
- await act(() => {
1963
- root.render(<input type="text" value={null} />);
1964
- });
1965
- }).toErrorDev(
1990
+ await act(() => {
1991
+ root.render(<input type="text" value={null} />);
1992
+ });
1993
+ assertConsoleErrorDev([
1994
'`value` prop on `input` should not be null. ' +
1995
'Consider using an empty string to clear the component or `undefined` ' +
1968
- 'for uncontrolled components.',
1969
- );
1996
+ 'for uncontrolled components.\n' +
1997
+ ' in input (at **)',
1998
+ ]);
1999
root.unmount();
2000
2001
root = ReactDOMClient.createRoot(container);
@@ -1976,25 +2005,25 @@ describe('ReactDOMInput', () => {
2005
});
2006
2007
it('should warn if checked and defaultChecked props are specified', async () => {
1979
- await expect(async () => {
1980
- await act(() => {
1981
- root.render(
1982
- <input
1983
- type="radio"
1984
- checked={true}
1985
- defaultChecked={true}
1986
- readOnly={true}
1987
- />,
1988
- );
1989
- });
1990
- }).toErrorDev(
2008
+ await act(() => {
2009
+ root.render(
2010
+ <input
2011
+ type="radio"
2012
+ checked={true}
2013
+ defaultChecked={true}
2014
+ readOnly={true}
2015
+ />,
2016
+ );
2017
+ });
2018
+ assertConsoleErrorDev([
2019
'A component contains an input of type radio with both checked and defaultChecked props. ' +
2020
'Input elements must be either controlled or uncontrolled ' +
2021
'(specify either the checked prop, or the defaultChecked prop, but not ' +
2022
'both). Decide between using a controlled or uncontrolled input ' +
2023
'element and remove one of these props. More info: ' +
1996
- 'https://react.dev/link/controlled-components',
1997
- );
2024
+ 'https://react.dev/link/controlled-components\n' +
2025
+ ' in input (at **)',
2026
+ ]);
2027
root.unmount();
2028
2029
root = ReactDOMClient.createRoot(container);
@@ -2011,20 +2040,20 @@ describe('ReactDOMInput', () => {
2040
});
2041
2042
it('should warn if value and defaultValue props are specified', async () => {
2014
- await expect(async () => {
2015
- await act(() => {
2016
- root.render(
2017
- <input type="text" value="foo" defaultValue="bar" readOnly={true} />,
2018
- );
2019
- });
2020
- }).toErrorDev(
2043
+ await act(() => {
2044
+ root.render(
2045
+ <input type="text" value="foo" defaultValue="bar" readOnly={true} />,
2046
+ );
2047
+ });
2048
+ assertConsoleErrorDev([
2049
'A component contains an input of type text with both value and defaultValue props. ' +
2050
'Input elements must be either controlled or uncontrolled ' +
2051
'(specify either the value prop, or the defaultValue prop, but not ' +
2052
'both). Decide between using a controlled or uncontrolled input ' +
2053
'element and remove one of these props. More info: ' +
2026
- 'https://react.dev/link/controlled-components',
2027
- );
2054
+ 'https://react.dev/link/controlled-components\n' +
2055
+ ' in input (at **)',
2056
+ ]);
2057
await (() => {
2058
root.unmount();
2059
});
@@ -2043,18 +2072,17 @@ describe('ReactDOMInput', () => {
2072
await act(() => {
2073
root.render(stub);
2074
});
2046
- await expect(async () => {
2047
- await act(() => {
2048
- root.render(<input type="text" />);
2049
- });
2050
- }).toErrorDev(
2075
+ await act(() => {
2076
+ root.render(<input type="text" />);
2077
+ });
2078
+ assertConsoleErrorDev([
2079
'A component is changing a controlled input to be uncontrolled. ' +
2080
'This is likely caused by the value changing from a defined to ' +
2081
'undefined, which should not happen. ' +
2082
'Decide between using a controlled or uncontrolled input ' +
2083
'element for the lifetime of the component. More info: https://react.dev/link/controlled-components\n' +
2084
' in input (at **)',
2057
- );
2085
+ ]);
2086
});
2087
2088
it('should warn if controlled input switches to uncontrolled (value is null)', async () => {
@@ -2064,13 +2092,13 @@ describe('ReactDOMInput', () => {
2092
await act(() => {
2093
root.render(stub);
2094
});
2067
- await expect(async () => {
2068
- await act(() => {
2069
- root.render(<input type="text" value={null} />);
2070
- });
2071
- }).toErrorDev([
2095
+ await act(() => {
2096
+ root.render(<input type="text" value={null} />);
2097
+ });
2098
+ assertConsoleErrorDev([
2099
'`value` prop on `input` should not be null. ' +
2073
- 'Consider using an empty string to clear the component or `undefined` for uncontrolled components',
2100
+ 'Consider using an empty string to clear the component or `undefined` for uncontrolled components.\n' +
2101
+ ' in input (at **)',
2102
'A component is changing a controlled input to be uncontrolled. ' +
2103
'This is likely caused by the value changing from a defined to ' +
2104
'undefined, which should not happen. ' +
@@ -2087,18 +2115,17 @@ describe('ReactDOMInput', () => {
2115
await act(() => {
2116
root.render(stub);
2117
});
2090
- await expect(async () => {
2091
- await act(() => {
2092
- root.render(<input type="text" defaultValue="uncontrolled" />);
2093
- });
2094
- }).toErrorDev(
2118
+ await act(() => {
2119
+ root.render(<input type="text" defaultValue="uncontrolled" />);
2120
+ });
2121
+ assertConsoleErrorDev([
2122
'A component is changing a controlled input to be uncontrolled. ' +
2123
'This is likely caused by the value changing from a defined to ' +
2124
'undefined, which should not happen. ' +
2125
'Decide between using a controlled or uncontrolled input ' +
2126
'element for the lifetime of the component. More info: https://react.dev/link/controlled-components\n' +
2127
' in input (at **)',
2101
- );
2128
+ ]);
2129
});
2130
2131
it('should warn if uncontrolled input (value is undefined) switches to controlled', async () => {
@@ -2106,42 +2133,40 @@ describe('ReactDOMInput', () => {
2133
await act(() => {
2134
root.render(stub);
2135
});
2109
- await expect(async () => {
2110
- await act(() => {
2111
- root.render(<input type="text" value="controlled" />);
2112
- });
2113
- }).toErrorDev(
2136
+ await act(() => {
2137
+ root.render(<input type="text" value="controlled" />);
2138
+ });
2139
+ assertConsoleErrorDev([
2140
'A component is changing an uncontrolled input to be controlled. ' +
2141
'This is likely caused by the value changing from undefined to ' +
2142
'a defined value, which should not happen. ' +
2143
'Decide between using a controlled or uncontrolled input ' +
2144
'element for the lifetime of the component. More info: https://react.dev/link/controlled-components\n' +
2145
' in input (at **)',
2120
- );
2146
+ ]);
2147
});
2148
2149
it('should warn if uncontrolled input (value is null) switches to controlled', async () => {
2150
const stub = <input type="text" value={null} />;
2125
- await expect(async () => {
2126
- await act(() => {
2127
- root.render(stub);
2128
- });
2129
- }).toErrorDev(
2151
+ await act(() => {
2152
+ root.render(stub);
2153
+ });
2154
+ assertConsoleErrorDev([
2155
'`value` prop on `input` should not be null. ' +
2131
- 'Consider using an empty string to clear the component or `undefined` for uncontrolled components.',
2132
- );
2133
- await expect(async () => {
2134
- await act(() => {
2135
- root.render(<input type="text" value="controlled" />);
2136
- });
2137
- }).toErrorDev(
2156
+ 'Consider using an empty string to clear the component or `undefined` for uncontrolled components.\n' +
2157
+ ' in input (at **)',
2158
+ ]);
2159
+ await act(() => {
2160
+ root.render(<input type="text" value="controlled" />);
2161
+ });
2162
+ assertConsoleErrorDev([
2163
'A component is changing an uncontrolled input to be controlled. ' +
2164
'This is likely caused by the value changing from undefined to ' +
2165
'a defined value, which should not happen. ' +
2166
'Decide between using a controlled or uncontrolled input ' +
2167
'element for the lifetime of the component. More info: https://react.dev/link/controlled-components\n' +
2168
' in input (at **)',
2144
- );
2169
+ ]);
2170
});
2171
2172
it('should warn if controlled checkbox switches to uncontrolled (checked is undefined)', async () => {
@@ -2151,18 +2176,17 @@ describe('ReactDOMInput', () => {
2176
await act(() => {
2177
root.render(stub);
2178
});
2154
- await expect(async () => {
2155
- await act(() => {
2156
- root.render(<input type="checkbox" />);
2157
- });
2158
- }).toErrorDev(
2179
+ await act(() => {
2180
+ root.render(<input type="checkbox" />);
2181
+ });
2182
+ assertConsoleErrorDev([
2183
'A component is changing a controlled input to be uncontrolled. ' +
2184
'This is likely caused by the value changing from a defined to ' +
2185
'undefined, which should not happen. ' +
2186
'Decide between using a controlled or uncontrolled input ' +
2187
'element for the lifetime of the component. More info: https://react.dev/link/controlled-components\n' +
2188
' in input (at **)',
2165
- );
2189
+ ]);
2190
});
2191
2192
it('should warn if controlled checkbox switches to uncontrolled (checked is null)', async () => {
@@ -2172,18 +2196,17 @@ describe('ReactDOMInput', () => {
2196
await act(() => {
2197
root.render(stub);
2198
});
2175
- await expect(async () => {
2176
- await act(() => {
2177
- root.render(<input type="checkbox" checked={null} />);
2178
- });
2179
- }).toErrorDev(
2199
+ await act(() => {
2200
+ root.render(<input type="checkbox" checked={null} />);
2201
+ });
2202
+ assertConsoleErrorDev([
2203
'A component is changing a controlled input to be uncontrolled. ' +
2204
'This is likely caused by the value changing from a defined to ' +
2205
'undefined, which should not happen. ' +
2206
'Decide between using a controlled or uncontrolled input ' +
2207
'element for the lifetime of the component. More info: https://react.dev/link/controlled-components\n' +
2208
' in input (at **)',
2186
- );
2209
+ ]);
2210
});
2211
2212
it('should warn if controlled checkbox switches to uncontrolled with defaultChecked', async () => {
@@ -2193,18 +2216,17 @@ describe('ReactDOMInput', () => {
2216
await act(() => {
2217
root.render(stub);
2218
});
2196
- await expect(async () => {
2197
- await act(() => {
2198
- root.render(<input type="checkbox" defaultChecked={true} />);
2199
- });
2200
- }).toErrorDev(
2219
+ await act(() => {
2220
+ root.render(<input type="checkbox" defaultChecked={true} />);
2221
+ });
2222
+ assertConsoleErrorDev([
2223
'A component is changing a controlled input to be uncontrolled. ' +
2224
'This is likely caused by the value changing from a defined to ' +
2225
'undefined, which should not happen. ' +
2226
'Decide between using a controlled or uncontrolled input ' +
2227
'element for the lifetime of the component. More info: https://react.dev/link/controlled-components\n' +
2228
' in input (at **)',
2207
- );
2229
+ ]);
2230
});
2231
2232
it('should warn if uncontrolled checkbox (checked is undefined) switches to controlled', async () => {
@@ -2212,18 +2234,17 @@ describe('ReactDOMInput', () => {
2234
await act(() => {
2235
root.render(stub);
2236
});
2215
- await expect(async () => {
2216
- await act(() => {
2217
- root.render(<input type="checkbox" checked={true} />);
2218
- });
2219
- }).toErrorDev(
2237
+ await act(() => {
2238
+ root.render(<input type="checkbox" checked={true} />);
2239
+ });
2240
+ assertConsoleErrorDev([
2241
'A component is changing an uncontrolled input to be controlled. ' +
2242
'This is likely caused by the value changing from undefined to ' +
2243
'a defined value, which should not happen. ' +
2244
'Decide between using a controlled or uncontrolled input ' +
2245
'element for the lifetime of the component. More info: https://react.dev/link/controlled-components\n' +
2246
' in input (at **)',
2226
- );
2247
+ ]);
2248
});
2249
2250
it('should warn if uncontrolled checkbox (checked is null) switches to controlled', async () => {
@@ -2231,18 +2252,17 @@ describe('ReactDOMInput', () => {
2252
await act(() => {
2253
root.render(stub);
2254
});
2234
- await expect(async () => {
2235
- await act(() => {
2236
- root.render(<input type="checkbox" checked={true} />);
2237
- });
2238
- }).toErrorDev(
2255
+ await act(() => {
2256
+ root.render(<input type="checkbox" checked={true} />);
2257
+ });
2258
+ assertConsoleErrorDev([
2259
'A component is changing an uncontrolled input to be controlled. ' +
2260
'This is likely caused by the value changing from undefined to ' +
2261
'a defined value, which should not happen. ' +
2262
'Decide between using a controlled or uncontrolled input ' +
2263
'element for the lifetime of the component. More info: https://react.dev/link/controlled-components\n' +
2264
' in input (at **)',
2245
- );
2265
+ ]);
2266
});
2267
2268
it('should warn if controlled radio switches to uncontrolled (checked is undefined)', async () => {
@@ -2250,18 +2270,17 @@ describe('ReactDOMInput', () => {
2270
await act(() => {
2271
root.render(stub);
2272
});
2253
- await expect(async () => {
2254
- await act(() => {
2255
- root.render(<input type="radio" />);
2256
- });
2257
- }).toErrorDev(
2273
+ await act(() => {
2274
+ root.render(<input type="radio" />);
2275
+ });
2276
+ assertConsoleErrorDev([
2277
'A component is changing a controlled input to be uncontrolled. ' +
2278
'This is likely caused by the value changing from a defined to ' +
2279
'undefined, which should not happen. ' +
2280
'Decide between using a controlled or uncontrolled input ' +
2281
'element for the lifetime of the component. More info: https://react.dev/link/controlled-components\n' +
2282
' in input (at **)',
2264
- );
2283
+ ]);
2284
});
2285
2286
it('should warn if controlled radio switches to uncontrolled (checked is null)', async () => {
@@ -2269,18 +2288,17 @@ describe('ReactDOMInput', () => {
2288
await act(() => {
2289
root.render(stub);
2290
});
2272
- await expect(async () => {
2273
- await act(() => {
2274
- root.render(<input type="radio" checked={null} />);
2275
- });
2276
- }).toErrorDev(
2291
+ await act(() => {
2292
+ root.render(<input type="radio" checked={null} />);
2293
+ });
2294
+ assertConsoleErrorDev([
2295
'A component is changing a controlled input to be uncontrolled. ' +
2296
'This is likely caused by the value changing from a defined to ' +
2297
'undefined, which should not happen. ' +
2298
'Decide between using a controlled or uncontrolled input ' +
2299
'element for the lifetime of the component. More info: https://react.dev/link/controlled-components\n' +
2300
' in input (at **)',
2283
- );
2301
+ ]);
2302
});
2303
2304
it('should warn if controlled radio switches to uncontrolled with defaultChecked', async () => {
@@ -2288,18 +2306,17 @@ describe('ReactDOMInput', () => {
2306
await act(() => {
2307
root.render(stub);
2308
});
2291
- await expect(async () => {
2292
- await act(() => {
2293
- root.render(<input type="radio" defaultChecked={true} />);
2294
- });
2295
- }).toErrorDev(
2309
+ await act(() => {
2310
+ root.render(<input type="radio" defaultChecked={true} />);
2311
+ });
2312
+ assertConsoleErrorDev([
2313
'A component is changing a controlled input to be uncontrolled. ' +
2314
'This is likely caused by the value changing from a defined to ' +
2315
'undefined, which should not happen. ' +
2316
'Decide between using a controlled or uncontrolled input ' +
2317
'element for the lifetime of the component. More info: https://react.dev/link/controlled-components\n' +
2318
' in input (at **)',
2302
- );
2319
+ ]);
2320
});
2321
2322
it('should warn if uncontrolled radio (checked is undefined) switches to controlled', async () => {
@@ -2307,18 +2324,17 @@ describe('ReactDOMInput', () => {
2324
await act(() => {
2325
root.render(stub);
2326
});
2310
- await expect(async () => {
2311
- await act(() => {
2312
- root.render(<input type="radio" checked={true} />);
2313
- });
2314
- }).toErrorDev(
2327
+ await act(() => {
2328
+ root.render(<input type="radio" checked={true} />);
2329
+ });
2330
+ assertConsoleErrorDev([
2331
'A component is changing an uncontrolled input to be controlled. ' +
2332
'This is likely caused by the value changing from undefined to ' +
2333
'a defined value, which should not happen. ' +
2334
'Decide between using a controlled or uncontrolled input ' +
2335
'element for the lifetime of the component. More info: https://react.dev/link/controlled-components\n' +
2336
' in input (at **)',
2321
- );
2337
+ ]);
2338
});
2339
2340
it('should warn if uncontrolled radio (checked is null) switches to controlled', async () => {
@@ -2326,18 +2342,17 @@ describe('ReactDOMInput', () => {
2342
await act(() => {
2343
root.render(stub);
2344
});
2329
- await expect(async () => {
2330
- await act(() => {
2331
- root.render(<input type="radio" checked={true} />);
2332
- });
2333
- }).toErrorDev(
2345
+ await act(() => {
2346
+ root.render(<input type="radio" checked={true} />);
2347
+ });
2348
+ assertConsoleErrorDev([
2349
'A component is changing an uncontrolled input to be controlled. ' +
2350
'This is likely caused by the value changing from undefined to ' +
2351
'a defined value, which should not happen. ' +
2352
'Decide between using a controlled or uncontrolled input ' +
2353
'element for the lifetime of the component. More info: https://react.dev/link/controlled-components\n' +
2354
' in input (at **)',
2340
- );
2355
+ ]);
2356
});
2357
2358
it('should not warn if radio value changes but never becomes controlled', async () => {
@@ -2390,18 +2405,17 @@ describe('ReactDOMInput', () => {
2405
/>,
2406
);
2407
});
2393
- await expect(async () => {
2394
- await act(() => {
2395
- root.render(<input type="radio" value="value" />);
2396
- });
2397
- }).toErrorDev(
2408
+ await act(() => {
2409
+ root.render(<input type="radio" value="value" />);
2410
+ });
2411
+ assertConsoleErrorDev([
2412
'A component is changing a controlled input to be uncontrolled. ' +
2413
'This is likely caused by the value changing from a defined to ' +
2414
'undefined, which should not happen. ' +
2415
'Decide between using a controlled or uncontrolled input ' +
2416
'element for the lifetime of the component. More info: https://react.dev/link/controlled-components\n' +
2417
' in input (at **)',
2404
- );
2418
+ ]);
2419
});
2420
2421
it('sets type, step, min, max before value always', async () => {
@@ -2747,9 +2761,15 @@ describe('ReactDOMInput', () => {
2761
}
2762
2763
it('reverts the value attribute to the initial value', async () => {
2750
- await expect(renderInputWithStringThenWithUndefined).toErrorDev(
2751
- 'A component is changing a controlled input to be uncontrolled.',
2752
- );
2764
+ await renderInputWithStringThenWithUndefined();
2765
+ assertConsoleErrorDev([
2766
+ 'A component is changing a controlled input to be uncontrolled. ' +
2767
+ 'This is likely caused by the value changing from a defined to undefined, which should not happen. ' +
2768
+ 'Decide between using a controlled or uncontrolled input element for the lifetime of the component. ' +
2769
+ 'More info: https://react.dev/link/controlled-components\n' +
2770
+ ' in input (at **)\n' +
2771
+ ' in Input (at **)',
2772
+ ]);
2773
if (disableInputAttributeSyncing) {
2774
expect(input.getAttribute('value')).toBe(null);
2775
} else {
@@ -2758,9 +2778,15 @@ describe('ReactDOMInput', () => {
2778
});
2779
2780
it('preserves the value property', async () => {
2761
- await expect(renderInputWithStringThenWithUndefined).toErrorDev(
2762
- 'A component is changing a controlled input to be uncontrolled.',
2763
- );
2781
+ await renderInputWithStringThenWithUndefined();
2782
+ assertConsoleErrorDev([
2783
+ 'A component is changing a controlled input to be uncontrolled. ' +
2784
+ 'This is likely caused by the value changing from a defined to undefined, which should not happen. ' +
2785
+ 'Decide between using a controlled or uncontrolled input element for the lifetime of the component. ' +
2786
+ 'More info: https://react.dev/link/controlled-components\n' +
2787
+ ' in input (at **)\n' +
2788
+ ' in Input (at **)',
2789
+ ]);
2790
expect(input.value).toBe('latest');
2791
});
2792
});
@@ -2798,11 +2824,19 @@ describe('ReactDOMInput', () => {
2824
}
2825
2826
it('reverts the value attribute to the initial value', async () => {
2801
- await expect(renderInputWithStringThenWithNull).toErrorDev([
2827
+ await renderInputWithStringThenWithNull();
2828
+ assertConsoleErrorDev([
2829
'`value` prop on `input` should not be null. ' +
2830
'Consider using an empty string to clear the component ' +
2804
- 'or `undefined` for uncontrolled components.',
2805
- 'A component is changing a controlled input to be uncontrolled.',
2831
+ 'or `undefined` for uncontrolled components.\n' +
2832
+ ' in input (at **)\n' +
2833
+ ' in Input (at **)',
2834
+ 'A component is changing a controlled input to be uncontrolled. ' +
2835
+ 'This is likely caused by the value changing from a defined to undefined, which should not happen. ' +
2836
+ 'Decide between using a controlled or uncontrolled input element for the lifetime of the component. ' +
2837
+ 'More info: https://react.dev/link/controlled-components\n' +
2838
+ ' in input (at **)\n' +
2839
+ ' in Input (at **)',
2840
]);
2841
if (disableInputAttributeSyncing) {
2842
expect(input.getAttribute('value')).toBe(null);
@@ -2812,11 +2846,19 @@ describe('ReactDOMInput', () => {
2846
});
2847
2848
it('preserves the value property', async () => {
2815
- await expect(renderInputWithStringThenWithNull).toErrorDev([
2849
+ await renderInputWithStringThenWithNull();
2850
+ assertConsoleErrorDev([
2851
'`value` prop on `input` should not be null. ' +
2852
'Consider using an empty string to clear the component ' +
2818
- 'or `undefined` for uncontrolled components.',
2819
- 'A component is changing a controlled input to be uncontrolled.',
2853
+ 'or `undefined` for uncontrolled components.\n' +
2854
+ ' in input (at **)\n' +
2855
+ ' in Input (at **)',
2856
+ 'A component is changing a controlled input to be uncontrolled. ' +
2857
+ 'This is likely caused by the value changing from a defined to undefined, which should not happen. ' +
2858
+ 'Decide between using a controlled or uncontrolled input element for the lifetime of the component. ' +
2859
+ 'More info: https://react.dev/link/controlled-components\n' +
2860
+ ' in input (at **)\n' +
2861
+ ' in Input (at **)',
2862
]);
2863
expect(input.value).toBe('latest');
2864
});
@@ -2824,11 +2866,15 @@ describe('ReactDOMInput', () => {
2866
2867
describe('When given a Symbol value', function () {
2868
it('treats initial Symbol value as an empty string', async () => {
2827
- await expect(async () => {
2828
- await act(() => {
2829
- root.render(<input value={Symbol('foobar')} onChange={() => {}} />);
2830
- });
2831
- }).toErrorDev('Invalid value for prop `value`');
2869
+ await act(() => {
2870
+ root.render(<input value={Symbol('foobar')} onChange={() => {}} />);
2871
+ });
2872
+ assertConsoleErrorDev([
2873
+ 'Invalid value for prop `value` on <input> tag. ' +
2874
+ 'Either remove it from the element, or pass a string or number value to keep it in the DOM. ' +
2875
+ 'For details, see https://react.dev/link/attribute-behavior \n' +
2876
+ ' in input (at **)',
2877
+ ]);
2878
const node = container.firstChild;
2879
2880
expect(node.value).toBe('');
@@ -2843,11 +2889,15 @@ describe('ReactDOMInput', () => {
2889
await act(() => {
2890
root.render(<input value="foo" onChange={() => {}} />);
2891
});
2846
- await expect(async () => {
2847
- await act(() => {
2848
- root.render(<input value={Symbol('foobar')} onChange={() => {}} />);
2849
- });
2850
- }).toErrorDev('Invalid value for prop `value`');
2892
+ await act(() => {
2893
+ root.render(<input value={Symbol('foobar')} onChange={() => {}} />);
2894
+ });
2895
+ assertConsoleErrorDev([
2896
+ 'Invalid value for prop `value` on <input> tag. ' +
2897
+ 'Either remove it from the element, or pass a string or number value to keep it in the DOM. ' +
2898
+ 'For details, see https://react.dev/link/attribute-behavior \n' +
2899
+ ' in input (at **)',
2900
+ ]);
2901
const node = container.firstChild;
2902
2903
expect(node.value).toBe('');
@@ -2890,11 +2940,15 @@ describe('ReactDOMInput', () => {
2940
2941
describe('When given a function value', function () {
2942
it('treats initial function value as an empty string', async () => {
2893
- await expect(async () => {
2894
- await act(() => {
2895
- root.render(<input value={() => {}} onChange={() => {}} />);
2896
- });
2897
- }).toErrorDev('Invalid value for prop `value`');
2943
+ await act(() => {
2944
+ root.render(<input value={() => {}} onChange={() => {}} />);
2945
+ });
2946
+ assertConsoleErrorDev([
2947
+ 'Invalid value for prop `value` on <input> tag. ' +
2948
+ 'Either remove it from the element, or pass a string or number value to keep it in the DOM. ' +
2949
+ 'For details, see https://react.dev/link/attribute-behavior \n' +
2950
+ ' in input (at **)',
2951
+ ]);
2952
const node = container.firstChild;
2953
2954
expect(node.value).toBe('');
@@ -2909,11 +2963,15 @@ describe('ReactDOMInput', () => {
2963
await act(() => {
2964
root.render(<input value="foo" onChange={() => {}} />);
2965
});
2912
- await expect(async () => {
2913
- await act(() => {
2914
- root.render(<input value={() => {}} onChange={() => {}} />);
2915
- });
2916
- }).toErrorDev('Invalid value for prop `value`');
2966
+ await act(() => {
2967
+ root.render(<input value={() => {}} onChange={() => {}} />);
2968
+ });
2969
+ assertConsoleErrorDev([
2970
+ 'Invalid value for prop `value` on <input> tag. ' +
2971
+ 'Either remove it from the element, or pass a string or number value to keep it in the DOM. ' +
2972
+ 'For details, see https://react.dev/link/attribute-behavior \n' +
2973
+ ' in input (at **)',
2974
+ ]);
2975
const node = container.firstChild;
2976
2977
expect(node.value).toBe('');