[assert helpers] ReactDOMComponent-test (#31898)
Splitting out ReactDOMComponent to it's own PR because it's huge.
Ricky committed
Jan 2, 2025 at 15:28 UTC
d8a08f8e39972978cd0666f277409a1657083bb5
1 file changed
+1117
-903
packages/react-dom/src/__tests__/ReactDOMComponent-test.js
+1117
-903
@@ -19,6 +19,7 @@ describe('ReactDOMComponent', () => {
19
let act;
20
let assertLog;
21
let Scheduler;
22
+ let assertConsoleErrorDev;
23
24
beforeEach(() => {
25
jest.resetModules();
@@ -28,6 +29,8 @@ describe('ReactDOMComponent', () => {
29
ReactDOMServer = require('react-dom/server');
30
Scheduler = require('scheduler');
31
act = require('internal-test-utils').act;
32
+ assertConsoleErrorDev =
33
+ require('internal-test-utils').assertConsoleErrorDev;
34
assertLog = require('internal-test-utils').assertLog;
35
});
36
@@ -189,73 +192,72 @@ describe('ReactDOMComponent', () => {
192
it('should warn for unknown prop', async () => {
193
const container = document.createElement('div');
194
const root = ReactDOMClient.createRoot(container);
192
- await expect(async () => {
193
- await act(() => {
194
- root.render(<div foo={() => {}} />);
195
- });
196
- }).toErrorDev(
195
+ await act(() => {
196
+ root.render(<div foo={() => {}} />);
197
+ });
198
+ assertConsoleErrorDev([
199
'Invalid value for prop `foo` on <div> tag. Either remove it ' +
200
'from the element, or pass a string or number value to keep ' +
201
'it in the DOM. For details, see https://react.dev/link/attribute-behavior ' +
202
'\n in div (at **)',
201
- );
203
+ ]);
204
});
205
206
it('should group multiple unknown prop warnings together', async () => {
207
const container = document.createElement('div');
208
const root = ReactDOMClient.createRoot(container);
207
- await expect(async () => {
208
- await act(() => {
209
- root.render(<div foo={() => {}} baz={() => {}} />);
210
- });
211
- }).toErrorDev(
209
+ await act(() => {
210
+ root.render(<div foo={() => {}} baz={() => {}} />);
211
+ });
212
+ assertConsoleErrorDev([
213
'Invalid values for props `foo`, `baz` on <div> tag. Either remove ' +
214
'them from the element, or pass a string or number value to keep ' +
215
'them in the DOM. For details, see https://react.dev/link/attribute-behavior ' +
216
'\n in div (at **)',
216
- );
217
+ ]);
218
});
219
220
it('should warn for onDblClick prop', async () => {
221
const container = document.createElement('div');
222
const root = ReactDOMClient.createRoot(container);
222
- await expect(async () => {
223
- await act(() => {
224
- root.render(<div onDblClick={() => {}} />);
225
- });
226
- }).toErrorDev(
227
- 'Invalid event handler property `onDblClick`. Did you mean `onDoubleClick`?\n in div (at **)',
228
- );
223
+ await act(() => {
224
+ root.render(<div onDblClick={() => {}} />);
225
+ });
226
+ assertConsoleErrorDev([
227
+ 'Invalid event handler property `onDblClick`. Did you mean `onDoubleClick`?\n' +
228
+ ' in div (at **)',
229
+ ]);
230
});
231
232
it('should warn for unknown string event handlers', async () => {
233
const container = document.createElement('div');
234
const root = ReactDOMClient.createRoot(container);
234
- await expect(async () => {
235
- await act(() => {
236
- root.render(<div onUnknown='alert("hack")' />);
237
- });
238
- }).toErrorDev(
239
- 'Unknown event handler property `onUnknown`. It will be ignored.\n in div (at **)',
240
- );
235
+ await act(() => {
236
+ root.render(<div onUnknown='alert("hack")' />);
237
+ });
238
+ assertConsoleErrorDev([
239
+ 'Unknown event handler property `onUnknown`. It will be ignored.\n' +
240
+ ' in div (at **)',
241
+ ]);
242
expect(container.firstChild.hasAttribute('onUnknown')).toBe(false);
243
expect(container.firstChild.onUnknown).toBe(undefined);
243
- await expect(async () => {
244
- await act(() => {
245
- root.render(<div onunknown='alert("hack")' />);
246
- });
247
- }).toErrorDev(
248
- 'Unknown event handler property `onunknown`. It will be ignored.\n in div (at **)',
249
- );
244
+ await act(() => {
245
+ root.render(<div onunknown='alert("hack")' />);
246
+ });
247
+ assertConsoleErrorDev([
248
+ 'Unknown event handler property `onunknown`. It will be ignored.\n' +
249
+ ' in div (at **)',
250
+ ]);
251
expect(container.firstChild.hasAttribute('onunknown')).toBe(false);
252
expect(container.firstChild.onunknown).toBe(undefined);
252
- await expect(async () => {
253
- await act(() => {
254
- root.render(<div on-unknown='alert("hack")' />);
255
- });
256
- }).toErrorDev(
257
- 'Unknown event handler property `on-unknown`. It will be ignored.\n in div (at **)',
258
- );
253
+
254
+ await act(() => {
255
+ root.render(<div on-unknown='alert("hack")' />);
256
+ });
257
+ assertConsoleErrorDev([
258
+ 'Unknown event handler property `on-unknown`. It will be ignored.\n' +
259
+ ' in div (at **)',
260
+ ]);
261
expect(container.firstChild.hasAttribute('on-unknown')).toBe(false);
262
expect(container.firstChild['on-unknown']).toBe(undefined);
263
});
@@ -263,31 +265,31 @@ describe('ReactDOMComponent', () => {
265
it('should warn for unknown function event handlers', async () => {
266
const container = document.createElement('div');
267
const root = ReactDOMClient.createRoot(container);
266
- await expect(async () => {
267
- await act(() => {
268
- root.render(<div onUnknown={function () {}} />);
269
- });
270
- }).toErrorDev(
271
- 'Unknown event handler property `onUnknown`. It will be ignored.\n in div (at **)',
272
- );
268
+ await act(() => {
269
+ root.render(<div onUnknown={function () {}} />);
270
+ });
271
+ assertConsoleErrorDev([
272
+ 'Unknown event handler property `onUnknown`. It will be ignored.\n' +
273
+ ' in div (at **)',
274
+ ]);
275
expect(container.firstChild.hasAttribute('onUnknown')).toBe(false);
276
expect(container.firstChild.onUnknown).toBe(undefined);
275
- await expect(async () => {
276
- await act(() => {
277
- root.render(<div onunknown={function () {}} />);
278
- });
279
- }).toErrorDev(
280
- 'Unknown event handler property `onunknown`. It will be ignored.\n in div (at **)',
281
- );
277
+ await act(() => {
278
+ root.render(<div onunknown={function () {}} />);
279
+ });
280
+ assertConsoleErrorDev([
281
+ 'Unknown event handler property `onunknown`. It will be ignored.\n' +
282
+ ' in div (at **)',
283
+ ]);
284
expect(container.firstChild.hasAttribute('onunknown')).toBe(false);
285
expect(container.firstChild.onunknown).toBe(undefined);
284
- await expect(async () => {
285
- await act(() => {
286
- root.render(<div on-unknown={function () {}} />);
287
- });
288
- }).toErrorDev(
289
- 'Unknown event handler property `on-unknown`. It will be ignored.\n in div (at **)',
290
- );
286
+ await act(() => {
287
+ root.render(<div on-unknown={function () {}} />);
288
+ });
289
+ assertConsoleErrorDev([
290
+ 'Unknown event handler property `on-unknown`. It will be ignored.\n' +
291
+ ' in div (at **)',
292
+ ]);
293
expect(container.firstChild.hasAttribute('on-unknown')).toBe(false);
294
expect(container.firstChild['on-unknown']).toBe(undefined);
295
});
@@ -295,13 +297,13 @@ describe('ReactDOMComponent', () => {
297
it('should warn for badly cased React attributes', async () => {
298
const container = document.createElement('div');
299
const root = ReactDOMClient.createRoot(container);
298
- await expect(async () => {
299
- await act(() => {
300
- root.render(<div CHILDREN="5" />);
301
- });
302
- }).toErrorDev(
303
- 'Invalid DOM property `CHILDREN`. Did you mean `children`?\n in div (at **)',
304
- );
300
+ await act(() => {
301
+ root.render(<div CHILDREN="5" />);
302
+ });
303
+ assertConsoleErrorDev([
304
+ 'Invalid DOM property `CHILDREN`. Did you mean `children`?\n' +
305
+ ' in div (at **)',
306
+ ]);
307
expect(container.firstChild.getAttribute('CHILDREN')).toBe('5');
308
});
309
@@ -323,14 +325,13 @@ describe('ReactDOMComponent', () => {
325
const style = {fontSize: NaN};
326
const div = document.createElement('div');
327
const root = ReactDOMClient.createRoot(div);
326
- await expect(async () => {
327
- await act(() => {
328
- root.render(<span style={style} />);
329
- });
330
- }).toErrorDev(
331
- '`NaN` is an invalid value for the `fontSize` css style property.' +
332
- '\n in span (at **)',
333
- );
328
+ await act(() => {
329
+ root.render(<span style={style} />);
330
+ });
331
+ assertConsoleErrorDev([
332
+ '`NaN` is an invalid value for the `fontSize` css style property.\n' +
333
+ ' in span (at **)',
334
+ ]);
335
await act(() => {
336
root.render(<span style={style} />);
337
});
@@ -350,15 +351,18 @@ describe('ReactDOMComponent', () => {
351
const style = {fontSize: new TemporalLike()};
352
const root = ReactDOMClient.createRoot(document.createElement('div'));
353
await expect(async () => {
353
- await expect(async () => {
354
- await act(() => {
355
- root.render(<span style={style} />);
356
- });
357
- }).toErrorDev(
358
- 'The provided `fontSize` CSS property is an unsupported type TemporalLike.' +
359
- ' This value must be coerced to a string before using it here.',
360
- );
354
+ await act(() => {
355
+ root.render(<span style={style} />);
356
+ });
357
}).rejects.toThrowError(new TypeError('prod message'));
358
+ assertConsoleErrorDev([
359
+ 'The provided `fontSize` CSS property is an unsupported type TemporalLike.' +
360
+ ' This value must be coerced to a string before using it here.\n' +
361
+ ' in span (at **)',
362
+ 'The provided `fontSize` CSS property is an unsupported type TemporalLike.' +
363
+ ' This value must be coerced to a string before using it here.\n' +
364
+ ' in span (at **)',
365
+ ]);
366
});
367
368
it('should update styles if initially null', async () => {
@@ -590,16 +594,16 @@ describe('ReactDOMComponent', () => {
594
it('should not add an empty src attribute', async () => {
595
const container = document.createElement('div');
596
const root = ReactDOMClient.createRoot(container);
593
- await expect(async () => {
594
- await act(() => {
595
- root.render(<img src="" />);
596
- });
597
- }).toErrorDev(
597
+ await act(() => {
598
+ root.render(<img src="" />);
599
+ });
600
+ assertConsoleErrorDev([
601
'An empty string ("") was passed to the src attribute. ' +
602
'This may cause the browser to download the whole page again over the network. ' +
603
'To fix this, either do not render the element at all ' +
601
- 'or pass null to src instead of an empty string.',
602
- );
604
+ 'or pass null to src instead of an empty string.\n' +
605
+ ' in img (at **)',
606
+ ]);
607
const node = container.firstChild;
608
expect(node.hasAttribute('src')).toBe(false);
609
@@ -608,31 +612,31 @@ describe('ReactDOMComponent', () => {
612
});
613
expect(node.hasAttribute('src')).toBe(true);
614
611
- await expect(async () => {
612
- await act(() => {
613
- root.render(<img src="" />);
614
- });
615
- }).toErrorDev(
615
+ await act(() => {
616
+ root.render(<img src="" />);
617
+ });
618
+ assertConsoleErrorDev([
619
'An empty string ("") was passed to the src attribute. ' +
620
'This may cause the browser to download the whole page again over the network. ' +
621
'To fix this, either do not render the element at all ' +
619
- 'or pass null to src instead of an empty string.',
620
- );
622
+ 'or pass null to src instead of an empty string.\n' +
623
+ ' in img (at **)',
624
+ ]);
625
expect(node.hasAttribute('src')).toBe(false);
626
});
627
628
it('should not add an empty href attribute', async () => {
629
const container = document.createElement('div');
630
const root = ReactDOMClient.createRoot(container);
627
- await expect(async () => {
628
- await act(() => {
629
- root.render(<link href="" />);
630
- });
631
- }).toErrorDev(
631
+ await act(() => {
632
+ root.render(<link href="" />);
633
+ });
634
+ assertConsoleErrorDev([
635
'An empty string ("") was passed to the href attribute. ' +
636
'To fix this, either do not render the element at all ' +
634
- 'or pass null to href instead of an empty string.',
635
- );
637
+ 'or pass null to href instead of an empty string.\n' +
638
+ ' in link (at **)',
639
+ ]);
640
const node = container.firstChild;
641
expect(node.hasAttribute('href')).toBe(false);
642
@@ -641,15 +645,15 @@ describe('ReactDOMComponent', () => {
645
});
646
expect(node.hasAttribute('href')).toBe(true);
647
644
- await expect(async () => {
645
- await act(() => {
646
- root.render(<link href="" />);
647
- });
648
- }).toErrorDev(
648
+ await act(() => {
649
+ root.render(<link href="" />);
650
+ });
651
+ assertConsoleErrorDev([
652
'An empty string ("") was passed to the href attribute. ' +
653
'To fix this, either do not render the element at all ' +
651
- 'or pass null to href instead of an empty string.',
652
- );
654
+ 'or pass null to href instead of an empty string.\n' +
655
+ ' in link (at **)',
656
+ ]);
657
expect(node.hasAttribute('href')).toBe(false);
658
});
659
@@ -871,204 +875,235 @@ describe('ReactDOMComponent', () => {
875
});
876
877
it('should reject attribute key injection attack on markup for regular DOM (SSR)', () => {
874
- expect(() => {
875
- for (let i = 0; i < 3; i++) {
876
- const element1 = React.createElement(
877
- 'div',
878
- {'blah" onclick="beevil" noise="hi': 'selected'},
879
- null,
880
- );
881
- const element2 = React.createElement(
882
- 'div',
883
- {'></div><script>alert("hi")</script>': 'selected'},
884
- null,
885
- );
886
- const result1 = ReactDOMServer.renderToString(element1);
887
- const result2 = ReactDOMServer.renderToString(element2);
888
- expect(result1.toLowerCase()).not.toContain('onclick');
889
- expect(result2.toLowerCase()).not.toContain('script');
890
- }
891
- }).toErrorDev([
892
- 'Invalid attribute name: `blah" onclick="beevil" noise="hi`',
893
- 'Invalid attribute name: `></div><script>alert("hi")</script>`',
878
+ for (let i = 0; i < 3; i++) {
879
+ const element1 = React.createElement(
880
+ 'div',
881
+ {'blah" onclick="beevil" noise="hi': 'selected'},
882
+ null,
883
+ );
884
+ const element2 = React.createElement(
885
+ 'div',
886
+ {'></div><script>alert("hi")</script>': 'selected'},
887
+ null,
888
+ );
889
+ const result1 = ReactDOMServer.renderToString(element1);
890
+ const result2 = ReactDOMServer.renderToString(element2);
891
+ expect(result1.toLowerCase()).not.toContain('onclick');
892
+ expect(result2.toLowerCase()).not.toContain('script');
893
+ }
894
+ assertConsoleErrorDev([
895
+ 'Invalid attribute name: `blah" onclick="beevil" noise="hi`\n' +
896
+ ' in div (at **)',
897
+ 'Invalid attribute name: `></div><script>alert("hi")</script>`\n' +
898
+ ' in div (at **)',
899
]);
900
});
901
902
it('should reject attribute key injection attack on markup for custom elements (SSR)', () => {
898
- expect(() => {
899
- for (let i = 0; i < 3; i++) {
900
- const element1 = React.createElement(
901
- 'x-foo-component',
902
- {'blah" onclick="beevil" noise="hi': 'selected'},
903
- null,
904
- );
905
- const element2 = React.createElement(
906
- 'x-foo-component',
907
- {'></x-foo-component><script>alert("hi")</script>': 'selected'},
908
- null,
909
- );
910
- const result1 = ReactDOMServer.renderToString(element1);
911
- const result2 = ReactDOMServer.renderToString(element2);
912
- expect(result1.toLowerCase()).not.toContain('onclick');
913
- expect(result2.toLowerCase()).not.toContain('script');
914
- }
915
- }).toErrorDev([
916
- 'Invalid attribute name: `blah" onclick="beevil" noise="hi`',
917
- 'Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`',
903
+ for (let i = 0; i < 3; i++) {
904
+ const element1 = React.createElement(
905
+ 'x-foo-component',
906
+ {'blah" onclick="beevil" noise="hi': 'selected'},
907
+ null,
908
+ );
909
+ const element2 = React.createElement(
910
+ 'x-foo-component',
911
+ {'></x-foo-component><script>alert("hi")</script>': 'selected'},
912
+ null,
913
+ );
914
+ const result1 = ReactDOMServer.renderToString(element1);
915
+ const result2 = ReactDOMServer.renderToString(element2);
916
+ expect(result1.toLowerCase()).not.toContain('onclick');
917
+ expect(result2.toLowerCase()).not.toContain('script');
918
+ }
919
+ assertConsoleErrorDev([
920
+ 'Invalid attribute name: `blah" onclick="beevil" noise="hi`\n' +
921
+ ' in x-foo-component (at **)',
922
+ 'Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`\n' +
923
+ ' in x-foo-component (at **)',
924
]);
925
});
926
927
it('should reject attribute key injection attack on mount for regular DOM', async () => {
922
- await expect(async () => {
923
- for (let i = 0; i < 3; i++) {
924
- const container = document.createElement('div');
925
- let root = ReactDOMClient.createRoot(container);
926
- await act(() => {
927
- root.render(
928
- React.createElement(
929
- 'div',
930
- {'blah" onclick="beevil" noise="hi': 'selected'},
931
- null,
932
- ),
933
- );
934
- });
935
-
936
- expect(container.firstChild.attributes.length).toBe(0);
937
- await act(() => {
938
- root.unmount();
939
- });
940
- root = ReactDOMClient.createRoot(container);
941
- await act(() => {
942
- root.render(
943
- React.createElement(
944
- 'div',
945
- {'></div><script>alert("hi")</script>': 'selected'},
946
- null,
947
- ),
948
- );
949
- });
950
-
951
- expect(container.firstChild.attributes.length).toBe(0);
928
+ for (let i = 0; i < 3; i++) {
929
+ const container = document.createElement('div');
930
+ let root = ReactDOMClient.createRoot(container);
931
+ await act(() => {
932
+ root.render(
933
+ React.createElement(
934
+ 'div',
935
+ {'blah" onclick="beevil" noise="hi': 'selected'},
936
+ null,
937
+ ),
938
+ );
939
+ });
940
+
941
+ expect(container.firstChild.attributes.length).toBe(0);
942
+ if (i === 0) {
943
+ assertConsoleErrorDev([
944
+ 'Invalid attribute name: `blah" onclick="beevil" noise="hi`\n' +
945
+ ' in div (at **)',
946
+ ]);
947
}
953
- }).toErrorDev([
954
- 'Invalid attribute name: `blah" onclick="beevil" noise="hi`',
955
- 'Invalid attribute name: `></div><script>alert("hi")</script>`',
956
- ]);
948
+ await act(() => {
949
+ root.unmount();
950
+ });
951
+ root = ReactDOMClient.createRoot(container);
952
+ await act(() => {
953
+ root.render(
954
+ React.createElement(
955
+ 'div',
956
+ {'></div><script>alert("hi")</script>': 'selected'},
957
+ null,
958
+ ),
959
+ );
960
+ });
961
+ if (i === 0) {
962
+ assertConsoleErrorDev([
963
+ 'Invalid attribute name: `></div><script>alert("hi")</script>`\n' +
964
+ ' in div (at **)',
965
+ ]);
966
+ }
967
+
968
+ expect(container.firstChild.attributes.length).toBe(0);
969
+ }
970
});
971
972
it('should reject attribute key injection attack on mount for custom elements', async () => {
960
- await expect(async () => {
961
- for (let i = 0; i < 3; i++) {
962
- const container = document.createElement('div');
963
- let root = ReactDOMClient.createRoot(container);
964
-
965
- await act(() => {
966
- root.render(
967
- React.createElement(
968
- 'x-foo-component',
969
- {'blah" onclick="beevil" noise="hi': 'selected'},
970
- null,
971
- ),
972
- );
973
- });
974
-
975
- expect(container.firstChild.attributes.length).toBe(0);
976
- await act(() => {
977
- root.unmount();
978
- });
979
- root = ReactDOMClient.createRoot(container);
980
- await act(() => {
981
- root.render(
982
- React.createElement(
983
- 'x-foo-component',
984
- {'></x-foo-component><script>alert("hi")</script>': 'selected'},
985
- null,
986
- ),
987
- );
988
- });
989
-
990
- expect(container.firstChild.attributes.length).toBe(0);
973
+ for (let i = 0; i < 3; i++) {
974
+ const container = document.createElement('div');
975
+ let root = ReactDOMClient.createRoot(container);
976
+
977
+ await act(() => {
978
+ root.render(
979
+ React.createElement(
980
+ 'x-foo-component',
981
+ {'blah" onclick="beevil" noise="hi': 'selected'},
982
+ null,
983
+ ),
984
+ );
985
+ });
986
+
987
+ if (i === 0) {
988
+ assertConsoleErrorDev([
989
+ 'Invalid attribute name: `blah" onclick="beevil" noise="hi`\n' +
990
+ ' in x-foo-component (at **)',
991
+ ]);
992
}
992
- }).toErrorDev([
993
- 'Invalid attribute name: `blah" onclick="beevil" noise="hi`',
994
- 'Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`',
995
- ]);
993
+ expect(container.firstChild.attributes.length).toBe(0);
994
+ await act(() => {
995
+ root.unmount();
996
+ });
997
+
998
+ root = ReactDOMClient.createRoot(container);
999
+ await act(() => {
1000
+ root.render(
1001
+ React.createElement(
1002
+ 'x-foo-component',
1003
+ {'></x-foo-component><script>alert("hi")</script>': 'selected'},
1004
+ null,
1005
+ ),
1006
+ );
1007
+ });
1008
+
1009
+ if (i === 0) {
1010
+ assertConsoleErrorDev([
1011
+ 'Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`\n' +
1012
+ ' in x-foo-component (at **)',
1013
+ ]);
1014
+ }
1015
+ expect(container.firstChild.attributes.length).toBe(0);
1016
+ }
1017
});
1018
1019
it('should reject attribute key injection attack on update for regular DOM', async () => {
999
- await expect(async () => {
1000
- for (let i = 0; i < 3; i++) {
1001
- const container = document.createElement('div');
1002
- const beforeUpdate = React.createElement('div', {}, null);
1003
- const root = ReactDOMClient.createRoot(container);
1004
- await act(() => {
1005
- root.render(beforeUpdate);
1006
- });
1007
- await act(() => {
1008
- root.render(
1009
- React.createElement(
1010
- 'div',
1011
- {'blah" onclick="beevil" noise="hi': 'selected'},
1012
- null,
1013
- ),
1014
- );
1015
- });
1016
-
1017
- expect(container.firstChild.attributes.length).toBe(0);
1018
- await act(() => {
1019
- root.render(
1020
- React.createElement(
1021
- 'div',
1022
- {'></div><script>alert("hi")</script>': 'selected'},
1023
- null,
1024
- ),
1025
- );
1026
- });
1027
-
1028
- expect(container.firstChild.attributes.length).toBe(0);
1020
+ for (let i = 0; i < 3; i++) {
1021
+ const container = document.createElement('div');
1022
+ const beforeUpdate = React.createElement('div', {}, null);
1023
+ const root = ReactDOMClient.createRoot(container);
1024
+ await act(() => {
1025
+ root.render(beforeUpdate);
1026
+ });
1027
+ await act(() => {
1028
+ root.render(
1029
+ React.createElement(
1030
+ 'div',
1031
+ {'blah" onclick="beevil" noise="hi': 'selected'},
1032
+ null,
1033
+ ),
1034
+ );
1035
+ });
1036
+
1037
+ if (i === 0) {
1038
+ assertConsoleErrorDev([
1039
+ 'Invalid attribute name: `blah" onclick="beevil" noise="hi`\n' +
1040
+ ' in div (at **)',
1041
+ ]);
1042
}
1030
- }).toErrorDev([
1031
- 'Invalid attribute name: `blah" onclick="beevil" noise="hi`',
1032
- 'Invalid attribute name: `></div><script>alert("hi")</script>`',
1033
- ]);
1043
+ expect(container.firstChild.attributes.length).toBe(0);
1044
+ await act(() => {
1045
+ root.render(
1046
+ React.createElement(
1047
+ 'div',
1048
+ {'></div><script>alert("hi")</script>': 'selected'},
1049
+ null,
1050
+ ),
1051
+ );
1052
+ });
1053
+ if (i === 0) {
1054
+ assertConsoleErrorDev([
1055
+ 'Invalid attribute name: `></div><script>alert("hi")</script>`\n' +
1056
+ ' in div (at **)',
1057
+ ]);
1058
+ }
1059
+
1060
+ expect(container.firstChild.attributes.length).toBe(0);
1061
+ }
1062
});
1063
1064
it('should reject attribute key injection attack on update for custom elements', async () => {
1037
- await expect(async () => {
1038
- for (let i = 0; i < 3; i++) {
1039
- const container = document.createElement('div');
1040
- const beforeUpdate = React.createElement('x-foo-component', {}, null);
1041
- const root = ReactDOMClient.createRoot(container);
1042
- await act(() => {
1043
- root.render(beforeUpdate);
1044
- });
1045
- await act(() => {
1046
- root.render(
1047
- React.createElement(
1048
- 'x-foo-component',
1049
- {'blah" onclick="beevil" noise="hi': 'selected'},
1050
- null,
1051
- ),
1052
- );
1053
- });
1054
-
1055
- expect(container.firstChild.attributes.length).toBe(0);
1056
- await act(() => {
1057
- root.render(
1058
- React.createElement(
1059
- 'x-foo-component',
1060
- {'></x-foo-component><script>alert("hi")</script>': 'selected'},
1061
- null,
1062
- ),
1063
- );
1064
- });
1065
-
1066
- expect(container.firstChild.attributes.length).toBe(0);
1065
+ for (let i = 0; i < 3; i++) {
1066
+ const container = document.createElement('div');
1067
+ const beforeUpdate = React.createElement('x-foo-component', {}, null);
1068
+ const root = ReactDOMClient.createRoot(container);
1069
+ await act(() => {
1070
+ root.render(beforeUpdate);
1071
+ });
1072
+ await act(() => {
1073
+ root.render(
1074
+ React.createElement(
1075
+ 'x-foo-component',
1076
+ {'blah" onclick="beevil" noise="hi': 'selected'},
1077
+ null,
1078
+ ),
1079
+ );
1080
+ });
1081
+
1082
+ if (i === 0) {
1083
+ assertConsoleErrorDev([
1084
+ 'Invalid attribute name: `blah" onclick="beevil" noise="hi`\n' +
1085
+ ' in x-foo-component (at **)',
1086
+ ]);
1087
}
1068
- }).toErrorDev([
1069
- 'Invalid attribute name: `blah" onclick="beevil" noise="hi`',
1070
- 'Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`',
1071
- ]);
1088
+ expect(container.firstChild.attributes.length).toBe(0);
1089
+ await act(() => {
1090
+ root.render(
1091
+ React.createElement(
1092
+ 'x-foo-component',
1093
+ {'></x-foo-component><script>alert("hi")</script>': 'selected'},
1094
+ null,
1095
+ ),
1096
+ );
1097
+ });
1098
+
1099
+ if (i === 0) {
1100
+ assertConsoleErrorDev([
1101
+ 'Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`\n' +
1102
+ ' in x-foo-component (at **)',
1103
+ ]);
1104
+ }
1105
+ expect(container.firstChild.attributes.length).toBe(0);
1106
+ }
1107
});
1108
1109
it('should update arbitrary attributes for tags containing dashes', async () => {
@@ -1382,36 +1417,38 @@ describe('ReactDOMComponent', () => {
1417
});
1418
expect(nodeValueSetter).toHaveBeenCalledTimes(1);
1419
1385
- await expect(async () => {
1386
- await act(() => {
1387
- root.render(<input onChange={onChange} />);
1388
- });
1389
- }).toErrorDev(
1420
+ await act(() => {
1421
+ root.render(<input onChange={onChange} />);
1422
+ });
1423
+ assertConsoleErrorDev([
1424
'A component is changing a controlled input to be uncontrolled. This is likely caused by ' +
1425
'the value changing from a defined to undefined, which should not happen. Decide between ' +
1392
- 'using a controlled or uncontrolled input element for the lifetime of the component.',
1393
- );
1426
+ 'using a controlled or uncontrolled input element for the lifetime of the component. ' +
1427
+ 'More info: https://react.dev/link/controlled-components\n' +
1428
+ ' in input (at **)',
1429
+ ]);
1430
expect(nodeValueSetter).toHaveBeenCalledTimes(1);
1431
1396
- await expect(async () => {
1397
- await act(() => {
1398
- root.render(<input value={null} onChange={onChange} />);
1399
- });
1400
- }).toErrorDev(
1401
- 'value` prop on `input` should not be null. Consider using an empty string to clear the ' +
1402
- 'component or `undefined` for uncontrolled components.',
1403
- );
1432
+ await act(() => {
1433
+ root.render(<input value={null} onChange={onChange} />);
1434
+ });
1435
+ assertConsoleErrorDev([
1436
+ '`value` prop on `input` should not be null. Consider using an empty string to clear the ' +
1437
+ 'component or `undefined` for uncontrolled components.\n' +
1438
+ ' in input (at **)',
1439
+ ]);
1440
expect(nodeValueSetter).toHaveBeenCalledTimes(1);
1441
1406
- await expect(async () => {
1407
- await act(() => {
1408
- root.render(<input value="" onChange={onChange} />);
1409
- });
1410
- }).toErrorDev(
1442
+ await act(() => {
1443
+ root.render(<input value="" onChange={onChange} />);
1444
+ });
1445
+ assertConsoleErrorDev([
1446
'A component is changing an uncontrolled input to be controlled. This is likely caused by ' +
1447
'the value changing from undefined to a defined value, which should not happen. Decide between ' +
1413
- 'using a controlled or uncontrolled input element for the lifetime of the component.',
1414
- );
1448
+ 'using a controlled or uncontrolled input element for the lifetime of the component. ' +
1449
+ 'More info: https://react.dev/link/controlled-components\n' +
1450
+ ' in input (at **)',
1451
+ ]);
1452
expect(nodeValueSetter).toHaveBeenCalledTimes(2);
1453
1454
await act(() => {
@@ -1462,14 +1499,14 @@ describe('ReactDOMComponent', () => {
1499
it('should warn about non-string "is" attribute', async () => {
1500
const container = document.createElement('div');
1501
const root = ReactDOMClient.createRoot(container);
1465
- await expect(async () => {
1466
- await act(() => {
1467
- root.render(<button is={function () {}} />);
1468
- });
1469
- }).toErrorDev(
1502
+ await act(() => {
1503
+ root.render(<button is={function () {}} />);
1504
+ });
1505
+ assertConsoleErrorDev([
1506
'Received a `function` for a string attribute `is`. If this is expected, cast ' +
1471
- 'the value to a string.',
1472
- );
1507
+ 'the value to a string.\n' +
1508
+ ' in button (at **)',
1509
+ ]);
1510
});
1511
1512
it('should not update when switching between null/undefined', async () => {
@@ -1643,15 +1680,14 @@ describe('ReactDOMComponent', () => {
1680
}
1681
}
1682
1646
- let returnedValue;
1647
-
1648
- expect(() => {
1649
- returnedValue = ReactDOMServer.renderToString(<Container />);
1650
- }).toErrorDev(
1683
+ const returnedValue = ReactDOMServer.renderToString(<Container />);
1684
+ assertConsoleErrorDev([
1685
'<BR /> is using incorrect casing. ' +
1686
'Use PascalCase for React components, ' +
1653
- 'or lowercase for HTML elements.',
1654
- );
1687
+ 'or lowercase for HTML elements.\n' +
1688
+ ' in BR (at **)\n' +
1689
+ ' in Container (at **)',
1690
+ ]);
1691
// This includes a duplicate tag because we didn't treat this as self-closing.
1692
expect(returnedValue).toContain('</BR>');
1693
});
@@ -1671,32 +1707,32 @@ describe('ReactDOMComponent', () => {
1707
root.render(React.createElement('CUSTOM-TAG'));
1708
});
1709
1674
- await expect(async () => {
1675
- container = document.createElement('div');
1676
- root = ReactDOMClient.createRoot(container);
1710
+ container = document.createElement('div');
1711
+ root = ReactDOMClient.createRoot(container);
1712
1678
- await act(() => {
1679
- root.render(React.createElement('IMG'));
1680
- });
1681
- }).toErrorDev(
1713
+ await act(() => {
1714
+ root.render(React.createElement('IMG'));
1715
+ });
1716
+ assertConsoleErrorDev([
1717
'<IMG /> is using incorrect casing. ' +
1718
'Use PascalCase for React components, ' +
1684
- 'or lowercase for HTML elements.',
1685
- );
1719
+ 'or lowercase for HTML elements.\n' +
1720
+ ' in IMG (at **)',
1721
+ ]);
1722
});
1723
1724
it('should warn on props reserved for future use', async () => {
1689
- await expect(async () => {
1690
- const container = document.createElement('div');
1691
- const root = ReactDOMClient.createRoot(container);
1725
+ const container = document.createElement('div');
1726
+ const root = ReactDOMClient.createRoot(container);
1727
1693
- await act(() => {
1694
- root.render(<div aria="hello" />);
1695
- });
1696
- }).toErrorDev(
1728
+ await act(() => {
1729
+ root.render(<div aria="hello" />);
1730
+ });
1731
+ assertConsoleErrorDev([
1732
'The `aria` attribute is reserved for future use in React. ' +
1698
- 'Pass individual `aria-` attributes instead.',
1699
- );
1733
+ 'Pass individual `aria-` attributes instead.\n' +
1734
+ ' in div (at **)',
1735
+ ]);
1736
});
1737
1738
it('should warn if the tag is unrecognized', async () => {
@@ -1712,48 +1748,43 @@ describe('ReactDOMComponent', () => {
1748
};
1749
Object.prototype.toString = wrappedToString; // eslint-disable-line no-extend-native
1750
1715
- await expect(async () => {
1716
- const container = document.createElement('div');
1717
- const root = ReactDOMClient.createRoot(container);
1751
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
1752
1719
- await act(() => {
1720
- root.render(<bar />);
1721
- });
1722
- }).toErrorDev('The tag <bar> is unrecognized in this browser');
1753
+ await act(() => {
1754
+ root.render(<bar />);
1755
+ });
1756
+ assertConsoleErrorDev([
1757
+ 'The tag <bar> is unrecognized in this browser. ' +
1758
+ 'If you meant to render a React component, start its name with an uppercase letter.\n' +
1759
+ ' in bar (at **)',
1760
+ ]);
1761
// Test deduplication
1724
- await expect(async () => {
1725
- const container = document.createElement('div');
1726
- const root = ReactDOMClient.createRoot(container);
1727
-
1728
- await act(() => {
1729
- root.render(<foo />);
1730
- });
1731
- }).toErrorDev('The tag <foo> is unrecognized in this browser');
1732
- let container = document.createElement('div');
1733
- let root = ReactDOMClient.createRoot(container);
1762
await act(() => {
1763
root.render(<foo />);
1764
});
1737
-
1738
- container = document.createElement('div');
1739
- root = ReactDOMClient.createRoot(container);
1765
+ assertConsoleErrorDev([
1766
+ 'The tag <foo> is unrecognized in this browser. ' +
1767
+ 'If you meant to render a React component, start its name with an uppercase letter.\n' +
1768
+ ' in foo (at **)',
1769
+ ]);
1770
+ await act(() => {
1771
+ root.render(<foo />);
1772
+ });
1773
await act(() => {
1774
root.render(<time />);
1775
});
1776
1777
// Corner case. Make sure out deduplication logic doesn't break with weird tag.
1745
- await expect(async () => {
1746
- container = document.createElement('div');
1747
- root = ReactDOMClient.createRoot(container);
1748
-
1749
- await act(() => {
1750
- root.render(<hasOwnProperty />);
1751
- });
1752
- }).toErrorDev([
1778
+ await act(() => {
1779
+ root.render(<hasOwnProperty />);
1780
+ });
1781
+ assertConsoleErrorDev([
1782
'<hasOwnProperty /> is using incorrect casing. ' +
1754
- 'Use PascalCase for React components, ' +
1755
- 'or lowercase for HTML elements.',
1756
- 'The tag <hasOwnProperty> is unrecognized in this browser',
1783
+ 'Use PascalCase for React components, or lowercase for HTML elements.\n' +
1784
+ ' in hasOwnProperty (at **)',
1785
+ 'The tag <hasOwnProperty> is unrecognized in this browser. ' +
1786
+ 'If you meant to render a React component, start its name with an uppercase letter.\n' +
1787
+ ' in hasOwnProperty (at **)',
1788
]);
1789
} finally {
1790
Object.prototype.toString = realToString; // eslint-disable-line no-extend-native
@@ -1800,19 +1831,23 @@ describe('ReactDOMComponent', () => {
1831
expect(returnedValue).toContain('</menuitem>');
1832
1833
await expect(async () => {
1803
- await expect(async () => {
1804
- await act(() => {
1805
- root.render(
1806
- <menu>
1807
- <menuitem>children</menuitem>
1808
- </menu>,
1809
- );
1810
- });
1811
- }).toErrorDev('The tag <menuitem> is unrecognized in this browser.');
1834
+ await act(() => {
1835
+ root.render(
1836
+ <menu>
1837
+ <menuitem>children</menuitem>
1838
+ </menu>,
1839
+ );
1840
+ });
1841
}).rejects.toThrowError(
1842
'menuitem is a void element tag and must neither have `children` nor use ' +
1843
'`dangerouslySetInnerHTML`.',
1844
);
1845
+ assertConsoleErrorDev([
1846
+ 'The tag <menuitem> is unrecognized in this browser. ' +
1847
+ 'If you meant to render a React component, start its name with an uppercase letter.\n' +
1848
+ ' in menuitem (at **)' +
1849
+ (gate('enableOwnerStacks') ? '' : '\n in menu (at **)'),
1850
+ ]);
1851
});
1852
1853
it('should validate against multiple children props', async () => {
@@ -1825,15 +1860,21 @@ describe('ReactDOMComponent', () => {
1860
});
1861
1862
it('should validate against use of innerHTML', async () => {
1828
- await expect(async () => {
1829
- await mountComponent({innerHTML: '<span>Hi Jim!</span>'});
1830
- }).toErrorDev('Directly setting property `innerHTML` is not permitted. ');
1863
+ await mountComponent({innerHTML: '<span>Hi Jim!</span>'});
1864
+ assertConsoleErrorDev([
1865
+ 'Directly setting property `innerHTML` is not permitted. ' +
1866
+ 'For more information, lookup documentation on `dangerouslySetInnerHTML`.\n' +
1867
+ ' in div (at **)',
1868
+ ]);
1869
});
1870
1871
it('should validate against use of innerHTML without case sensitivity', async () => {
1834
- await expect(async () => {
1835
- await mountComponent({innerhtml: '<span>Hi Jim!</span>'});
1836
- }).toErrorDev('Directly setting property `innerHTML` is not permitted. ');
1872
+ await mountComponent({innerhtml: '<span>Hi Jim!</span>'});
1873
+ assertConsoleErrorDev([
1874
+ 'Directly setting property `innerHTML` is not permitted. ' +
1875
+ 'For more information, lookup documentation on `dangerouslySetInnerHTML`.\n' +
1876
+ ' in div (at **)',
1877
+ ]);
1878
});
1879
1880
it('should validate use of dangerouslySetInnerHTM with JSX', async () => {
@@ -1861,14 +1902,14 @@ describe('ReactDOMComponent', () => {
1902
});
1903
1904
it('should warn about contentEditable and children', async () => {
1864
- await expect(async () => {
1865
- await mountComponent({contentEditable: true, children: ''});
1866
- }).toErrorDev(
1905
+ await mountComponent({contentEditable: true, children: ''});
1906
+ assertConsoleErrorDev([
1907
'A component is `contentEditable` and contains `children` ' +
1908
'managed by React. It is now your responsibility to guarantee that ' +
1909
'none of those nodes are unexpectedly modified or duplicated. This ' +
1870
- 'is probably not intentional.\n in div (at **)',
1871
- );
1910
+ 'is probably not intentional.\n' +
1911
+ ' in div (at **)',
1912
+ ]);
1913
});
1914
1915
it('should respect suppressContentEditableWarning', async () => {
@@ -2042,15 +2083,19 @@ describe('ReactDOMComponent', () => {
2083
});
2084
2085
it('should warn about contentEditable and children', async () => {
2045
- await expect(async () => {
2046
- await act(() => {
2047
- root.render(
2048
- <div contentEditable={true}>
2049
- <div />
2050
- </div>,
2051
- );
2052
- });
2053
- }).toErrorDev('contentEditable');
2086
+ await act(() => {
2087
+ root.render(
2088
+ <div contentEditable={true}>
2089
+ <div />
2090
+ </div>,
2091
+ );
2092
+ });
2093
+ assertConsoleErrorDev([
2094
+ 'A component is `contentEditable` and contains `children` managed by React. ' +
2095
+ 'It is now your responsibility to guarantee that none of those nodes are unexpectedly modified or duplicated. ' +
2096
+ 'This is probably not intentional.\n' +
2097
+ ' in div (at **)',
2098
+ ]);
2099
});
2100
2101
it('should validate against invalid styles', async () => {
@@ -2182,16 +2227,15 @@ describe('ReactDOMComponent', () => {
2227
it('warns on invalid nesting', async () => {
2228
const container = document.createElement('div');
2229
const root = ReactDOMClient.createRoot(container);
2185
- await expect(async () => {
2186
- await act(() => {
2187
- root.render(
2188
- <div>
2189
- <tr />
2190
- <tr />
2191
- </div>,
2192
- );
2193
- });
2194
- }).toErrorDev(
2230
+ await act(() => {
2231
+ root.render(
2232
+ <div>
2233
+ <tr />
2234
+ <tr />
2235
+ </div>,
2236
+ );
2237
+ });
2238
+ assertConsoleErrorDev([
2239
'In HTML, <tr> cannot be a child of <div>.\n' +
2240
'This will cause a hydration error.\n' +
2241
'\n' +
@@ -2202,22 +2246,21 @@ describe('ReactDOMComponent', () => {
2246
(gate(flags => flags.enableOwnerStacks)
2247
? ''
2248
: '\n in div (at **)'),
2205
- );
2249
+ ]);
2250
});
2251
2252
it('warns on invalid nesting at root', async () => {
2253
const p = document.createElement('p');
2254
const root = ReactDOMClient.createRoot(p);
2255
2212
- await expect(async () => {
2213
- await act(() => {
2214
- root.render(
2215
- <span>
2216
- <p />
2217
- </span>,
2218
- );
2219
- });
2220
- }).toErrorDev(
2256
+ await act(() => {
2257
+ root.render(
2258
+ <span>
2259
+ <p />
2260
+ </span>,
2261
+ );
2262
+ });
2263
+ assertConsoleErrorDev([
2264
'In HTML, <p> cannot be a descendant of <p>.\n' +
2265
'This will cause a hydration error.' +
2266
// There is no outer `p` here because root container is not part of the stack.
@@ -2225,7 +2268,7 @@ describe('ReactDOMComponent', () => {
2268
(gate(flags => flags.enableOwnerStacks)
2269
? ''
2270
: '\n in span (at **)'),
2228
- );
2271
+ ]);
2272
});
2273
2274
it('warns nicely for table rows', async () => {
@@ -2245,14 +2288,13 @@ describe('ReactDOMComponent', () => {
2288
}
2289
}
2290
2248
- await expect(async () => {
2249
- const container = document.createElement('div');
2250
- const root = ReactDOMClient.createRoot(container);
2291
+ const container = document.createElement('div');
2292
+ const root = ReactDOMClient.createRoot(container);
2293
2252
- await act(() => {
2253
- root.render(<Foo />);
2254
- });
2255
- }).toErrorDev(
2294
+ await act(() => {
2295
+ root.render(<Foo />);
2296
+ });
2297
+ assertConsoleErrorDev(
2298
gate(flags => flags.enableOwnerStacks)
2299
? [
2300
'In HTML, <tr> cannot be a child of ' +
@@ -2266,7 +2308,8 @@ describe('ReactDOMComponent', () => {
2308
'> <tr>\n' +
2309
' ...\n' +
2310
'\n in tr (at **)' +
2269
- '\n in Row (at **)',
2311
+ '\n in Row (at **)' +
2312
+ '\n in Foo (at **)',
2313
'<table> cannot contain a nested <tr>.\nSee this log for the ancestor stack trace.' +
2314
'\n in table (at **)' +
2315
'\n in Foo (at **)',
@@ -2281,7 +2324,8 @@ describe('ReactDOMComponent', () => {
2324
'> x\n' +
2325
' ...\n' +
2326
'\n in tr (at **)' +
2284
- '\n in Row (at **)',
2327
+ '\n in Row (at **)' +
2328
+ '\n in Foo (at **)',
2329
'In HTML, whitespace text nodes cannot ' +
2330
"be a child of <table>. Make sure you don't have any extra " +
2331
'whitespace between tags on each line of your source code.\n' +
@@ -2354,11 +2398,10 @@ describe('ReactDOMComponent', () => {
2398
root.render(<Foo />);
2399
});
2400
2357
- await expect(async () => {
2358
- await act(() => {
2359
- root.render(<Foo> </Foo>);
2360
- });
2361
- }).toErrorDev([
2401
+ await act(() => {
2402
+ root.render(<Foo> </Foo>);
2403
+ });
2404
+ assertConsoleErrorDev([
2405
'In HTML, whitespace text nodes cannot ' +
2406
"be a child of <table>. Make sure you don't have any extra " +
2407
'whitespace between tags on each line of your source code.\n' +
@@ -2381,17 +2424,16 @@ describe('ReactDOMComponent', () => {
2424
);
2425
});
2426
2384
- await expect(async () => {
2385
- await act(() => {
2386
- root.render(
2387
- <Foo>
2388
- <tbody>
2389
- <Row>text</Row>
2390
- </tbody>
2391
- </Foo>,
2392
- );
2393
- });
2394
- }).toErrorDev([
2427
+ await act(() => {
2428
+ root.render(
2429
+ <Foo>
2430
+ <tbody>
2431
+ <Row>text</Row>
2432
+ </tbody>
2433
+ </Foo>,
2434
+ );
2435
+ });
2436
+ assertConsoleErrorDev([
2437
'In HTML, text nodes cannot be a ' +
2438
'child of <tr>.\n' +
2439
'This will cause a hydration error.\n' +
@@ -2430,27 +2472,52 @@ describe('ReactDOMComponent', () => {
2472
function App1() {
2473
return <Viz1 />;
2474
}
2433
- await expect(async () => {
2434
- const container = document.createElement('div');
2435
- const root = ReactDOMClient.createRoot(container);
2436
- await act(() => {
2437
- root.render(<App1 />);
2438
- });
2439
- }).toErrorDev(
2475
+ const container = document.createElement('div');
2476
+ const root = ReactDOMClient.createRoot(container);
2477
+ await act(() => {
2478
+ root.render(<App1 />);
2479
+ });
2480
+ assertConsoleErrorDev(
2481
gate(flags => flags.enableOwnerStacks)
2482
? [
2442
- '\n in tr (at **)' +
2483
+ 'In HTML, <tr> cannot be a child of <table>. ' +
2484
+ 'Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by the browser.\n' +
2485
+ 'This will cause a hydration error.\n' +
2486
+ '\n' +
2487
+ ' <App1>\n' +
2488
+ ' <Viz1>\n' +
2489
+ '> <table>\n' +
2490
+ ' <FancyRow>\n' +
2491
+ ' <Row>\n' +
2492
+ '> <tr>\n' +
2493
+ '\n in tr (at **)' +
2494
'\n in Row (at **)' +
2495
'\n in FancyRow (at **)' +
2445
- '\n in Viz1 (at **)',
2446
- '\n in table (at **)' + '\n in Viz1 (at **)',
2496
+ '\n in Viz1 (at **)' +
2497
+ '\n in App1 (at **)',
2498
+ '<table> cannot contain a nested <tr>.\n' +
2499
+ 'See this log for the ancestor stack trace.\n' +
2500
+ ' in table (at **)\n' +
2501
+ ' in Viz1 (at **)\n' +
2502
+ ' in App1 (at **)',
2503
]
2504
: [
2449
- '\n in tr (at **)' +
2505
+ 'In HTML, <tr> cannot be a child of <table>. ' +
2506
+ 'Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by the browser.\n' +
2507
+ 'This will cause a hydration error.\n' +
2508
+ '\n' +
2509
+ ' <App1>\n' +
2510
+ ' <Viz1>\n' +
2511
+ '> <table>\n' +
2512
+ ' <FancyRow>\n' +
2513
+ ' <Row>\n' +
2514
+ '> <tr>\n' +
2515
+ '\n in tr (at **)' +
2516
'\n in Row (at **)' +
2517
'\n in FancyRow (at **)' +
2518
'\n in table (at **)' +
2453
- '\n in Viz1 (at **)',
2519
+ '\n in Viz1 (at **)' +
2520
+ '\n in App1 (at **)',
2521
],
2522
);
2523
});
@@ -2485,33 +2552,61 @@ describe('ReactDOMComponent', () => {
2552
function App2() {
2553
return <Viz2 />;
2554
}
2488
- await expect(async () => {
2489
- const container = document.createElement('div');
2490
- const root = ReactDOMClient.createRoot(container);
2555
+ const container = document.createElement('div');
2556
+ const root = ReactDOMClient.createRoot(container);
2557
2492
- await act(() => {
2493
- root.render(<App2 />);
2494
- });
2495
- }).toErrorDev(
2558
+ await act(() => {
2559
+ root.render(<App2 />);
2560
+ });
2561
+ assertConsoleErrorDev(
2562
gate(flags => flags.enableOwnerStacks)
2563
? [
2498
- '\n in tr (at **)' +
2564
+ 'In HTML, <tr> cannot be a child of <table>. ' +
2565
+ 'Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by the browser.\n' +
2566
+ 'This will cause a hydration error.\n' +
2567
+ '\n' +
2568
+ ' <App2>\n' +
2569
+ ' <Viz2>\n' +
2570
+ ' <FancyTable>\n' +
2571
+ ' <Table>\n' +
2572
+ '> <table>\n' +
2573
+ ' <FancyRow>\n' +
2574
+ ' <Row>\n' +
2575
+ '> <tr>\n' +
2576
+ '\n in tr (at **)' +
2577
'\n in Row (at **)' +
2578
'\n in FancyRow (at **)' +
2501
- '\n in Viz2 (at **)',
2502
- '\n in table (at **)' +
2503
- '\n in Table (at **)' +
2504
- '\n in FancyTable (at **)' +
2505
- '\n in Viz2 (at **)',
2579
+ '\n in Viz2 (at **)' +
2580
+ '\n in App2 (at **)',
2581
+ '<table> cannot contain a nested <tr>.\n' +
2582
+ 'See this log for the ancestor stack trace.\n' +
2583
+ ' in table (at **)\n' +
2584
+ ' in Table (at **)\n' +
2585
+ ' in FancyTable (at **)\n' +
2586
+ ' in Viz2 (at **)\n' +
2587
+ ' in App2 (at **)',
2588
]
2589
: [
2508
- '\n in tr (at **)' +
2590
+ 'In HTML, <tr> cannot be a child of <table>. ' +
2591
+ 'Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by the browser.\n' +
2592
+ 'This will cause a hydration error.\n' +
2593
+ '\n' +
2594
+ ' <App2>\n' +
2595
+ ' <Viz2>\n' +
2596
+ ' <FancyTable>\n' +
2597
+ ' <Table>\n' +
2598
+ '> <table>\n' +
2599
+ ' <FancyRow>\n' +
2600
+ ' <Row>\n' +
2601
+ '> <tr>\n' +
2602
+ '\n in tr (at **)' +
2603
'\n in Row (at **)' +
2604
'\n in FancyRow (at **)' +
2605
'\n in table (at **)' +
2606
'\n in Table (at **)' +
2607
'\n in FancyTable (at **)' +
2514
- '\n in Viz2 (at **)',
2608
+ '\n in Viz2 (at **)' +
2609
+ '\n in App2 (at **)',
2610
],
2611
);
2612
});
@@ -2535,29 +2630,50 @@ describe('ReactDOMComponent', () => {
2630
return <Table>{this.props.children}</Table>;
2631
}
2632
}
2538
- await expect(async () => {
2539
- const container = document.createElement('div');
2540
- const root = ReactDOMClient.createRoot(container);
2633
+ const container = document.createElement('div');
2634
+ const root = ReactDOMClient.createRoot(container);
2635
2542
- await act(() => {
2543
- root.render(
2544
- <FancyTable>
2545
- <FancyRow />
2546
- </FancyTable>,
2547
- );
2548
- });
2549
- }).toErrorDev(
2636
+ await act(() => {
2637
+ root.render(
2638
+ <FancyTable>
2639
+ <FancyRow />
2640
+ </FancyTable>,
2641
+ );
2642
+ });
2643
+ assertConsoleErrorDev(
2644
gate(flags => flags.enableOwnerStacks)
2645
? [
2552
- '\n in tr (at **)' +
2646
+ 'In HTML, <tr> cannot be a child of <table>. ' +
2647
+ 'Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by the browser.\n' +
2648
+ 'This will cause a hydration error.\n' +
2649
+ '\n' +
2650
+ ' <FancyTable>\n' +
2651
+ ' <Table>\n' +
2652
+ '> <table>\n' +
2653
+ ' <FancyRow>\n' +
2654
+ ' <Row>\n' +
2655
+ '> <tr>\n' +
2656
+ '\n in tr (at **)' +
2657
'\n in Row (at **)' +
2658
'\n in FancyRow (at **)',
2555
- '\n in table (at **)' +
2659
+ '<table> cannot contain a nested <tr>.\n' +
2660
+ 'See this log for the ancestor stack trace.' +
2661
+ '\n in table (at **)' +
2662
'\n in Table (at **)' +
2663
'\n in FancyTable (at **)',
2664
]
2665
: [
2560
- '\n in tr (at **)' +
2666
+ 'In HTML, <tr> cannot be a child of <table>. ' +
2667
+ 'Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by the browser.\n' +
2668
+ 'This will cause a hydration error.\n' +
2669
+ '\n' +
2670
+ ' <FancyTable>\n' +
2671
+ ' <Table>\n' +
2672
+ '> <table>\n' +
2673
+ ' <FancyRow>\n' +
2674
+ ' <Row>\n' +
2675
+ '> <tr>\n' +
2676
+ '\n in tr (at **)' +
2677
'\n in Row (at **)' +
2678
'\n in FancyRow (at **)' +
2679
'\n in table (at **)' +
@@ -2575,27 +2691,44 @@ describe('ReactDOMComponent', () => {
2691
return <Row />;
2692
}
2693
2578
- await expect(async () => {
2579
- const container = document.createElement('div');
2580
- const root = ReactDOMClient.createRoot(container);
2694
+ const container = document.createElement('div');
2695
+ const root = ReactDOMClient.createRoot(container);
2696
2582
- await act(() => {
2583
- root.render(
2584
- <table>
2585
- <FancyRow />
2586
- </table>,
2587
- );
2588
- });
2589
- }).toErrorDev(
2697
+ await act(() => {
2698
+ root.render(
2699
+ <table>
2700
+ <FancyRow />
2701
+ </table>,
2702
+ );
2703
+ });
2704
+ assertConsoleErrorDev(
2705
gate(flags => flags.enableOwnerStacks)
2706
? [
2592
- '\n in tr (at **)' +
2707
+ 'In HTML, <tr> cannot be a child of <table>. ' +
2708
+ 'Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by the browser.\n' +
2709
+ 'This will cause a hydration error.\n' +
2710
+ '\n' +
2711
+ '> <table>\n' +
2712
+ ' <FancyRow>\n' +
2713
+ ' <Row>\n' +
2714
+ '> <tr>\n' +
2715
+ '\n in tr (at **)' +
2716
'\n in Row (at **)' +
2717
'\n in FancyRow (at **)',
2595
- '\n in table (at **)',
2718
+ '<table> cannot contain a nested <tr>.\n' +
2719
+ 'See this log for the ancestor stack trace.' +
2720
+ '\n in table (at **)',
2721
]
2722
: [
2598
- '\n in tr (at **)' +
2723
+ 'In HTML, <tr> cannot be a child of <table>. ' +
2724
+ 'Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by the browser.\n' +
2725
+ 'This will cause a hydration error.\n' +
2726
+ '\n' +
2727
+ '> <table>\n' +
2728
+ ' <FancyRow>\n' +
2729
+ ' <Row>\n' +
2730
+ '> <tr>\n' +
2731
+ '\n in tr (at **)' +
2732
'\n in Row (at **)' +
2733
'\n in FancyRow (at **)' +
2734
'\n in table (at **)',
@@ -2616,26 +2749,43 @@ describe('ReactDOMComponent', () => {
2749
}
2750
}
2751
2619
- await expect(async () => {
2620
- const container = document.createElement('div');
2621
- const root = ReactDOMClient.createRoot(container);
2622
- await act(() => {
2623
- root.render(
2624
- <FancyTable>
2625
- <tr />
2626
- </FancyTable>,
2627
- );
2628
- });
2629
- }).toErrorDev(
2752
+ const container = document.createElement('div');
2753
+ const root = ReactDOMClient.createRoot(container);
2754
+ await act(() => {
2755
+ root.render(
2756
+ <FancyTable>
2757
+ <tr />
2758
+ </FancyTable>,
2759
+ );
2760
+ });
2761
+ assertConsoleErrorDev(
2762
gate(flags => flags.enableOwnerStacks)
2763
? [
2632
- '\n in tr (at **)',
2633
- '\n in table (at **)' +
2764
+ 'In HTML, <tr> cannot be a child of <table>. ' +
2765
+ 'Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by the browser.\n' +
2766
+ 'This will cause a hydration error.\n' +
2767
+ '\n' +
2768
+ ' <FancyTable>\n' +
2769
+ ' <Table>\n' +
2770
+ '> <table>\n' +
2771
+ '> <tr>\n' +
2772
+ '\n in tr (at **)',
2773
+ '<table> cannot contain a nested <tr>.\n' +
2774
+ 'See this log for the ancestor stack trace.' +
2775
+ '\n in table (at **)' +
2776
'\n in Table (at **)' +
2777
'\n in FancyTable (at **)',
2778
]
2779
: [
2638
- '\n in tr (at **)' +
2780
+ 'In HTML, <tr> cannot be a child of <table>. ' +
2781
+ 'Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by the browser.\n' +
2782
+ 'This will cause a hydration error.\n' +
2783
+ '\n' +
2784
+ ' <FancyTable>\n' +
2785
+ ' <Table>\n' +
2786
+ '> <table>\n' +
2787
+ '> <tr>\n' +
2788
+ '\n in tr (at **)' +
2789
'\n in table (at **)' +
2790
'\n in Table (at **)' +
2791
'\n in FancyTable (at **)',
@@ -2648,26 +2798,43 @@ describe('ReactDOMComponent', () => {
2798
}
2799
}
2800
2651
- await expect(async () => {
2652
- const container = document.createElement('div');
2653
- const root = ReactDOMClient.createRoot(container);
2654
- await act(() => {
2655
- root.render(
2656
- <Link>
2657
- <div>
2658
- <Link />
2659
- </div>
2660
- </Link>,
2661
- );
2662
- });
2663
- }).toErrorDev(
2801
+ await act(() => {
2802
+ root.render(
2803
+ <Link>
2804
+ <div>
2805
+ <Link />
2806
+ </div>
2807
+ </Link>,
2808
+ );
2809
+ });
2810
+ assertConsoleErrorDev(
2811
gate(flags => flags.enableOwnerStacks)
2812
? [
2666
- '\n in a (at **)' + '\n in Link (at **)',
2667
- '\n in a (at **)' + '\n in Link (at **)',
2813
+ 'In HTML, <a> cannot be a descendant of <a>.\n' +
2814
+ 'This will cause a hydration error.\n' +
2815
+ '\n' +
2816
+ ' <Link>\n' +
2817
+ '> <a>\n' +
2818
+ ' <div>\n' +
2819
+ ' <Link>\n' +
2820
+ '> <a>\n' +
2821
+ '\n in a (at **)' +
2822
+ '\n in Link (at **)',
2823
+ '<a> cannot contain a nested <a>.\n' +
2824
+ 'See this log for the ancestor stack trace.' +
2825
+ '\n in a (at **)' +
2826
+ '\n in Link (at **)',
2827
]
2828
: [
2670
- '\n in a (at **)' +
2829
+ 'In HTML, <a> cannot be a descendant of <a>.\n' +
2830
+ 'This will cause a hydration error.\n' +
2831
+ '\n' +
2832
+ ' <Link>\n' +
2833
+ '> <a>\n' +
2834
+ ' <div>\n' +
2835
+ ' <Link>\n' +
2836
+ '> <a>\n' +
2837
+ '\n in a (at **)' +
2838
'\n in Link (at **)' +
2839
'\n in div (at **)' +
2840
'\n in a (at **)' +
@@ -2677,27 +2844,29 @@ describe('ReactDOMComponent', () => {
2844
});
2845
2846
it('should warn about incorrect casing on properties (ssr)', () => {
2680
- expect(() => {
2681
- ReactDOMServer.renderToString(
2682
- React.createElement('input', {type: 'text', tabindex: '1'}),
2683
- );
2684
- }).toErrorDev('tabIndex');
2847
+ ReactDOMServer.renderToString(
2848
+ React.createElement('input', {type: 'text', tabindex: '1'}),
2849
+ );
2850
+ assertConsoleErrorDev([
2851
+ 'Invalid DOM property `tabindex`. Did you mean `tabIndex`?\n' +
2852
+ ' in input (at **)',
2853
+ ]);
2854
});
2855
2856
it('should warn about incorrect casing on event handlers (ssr)', () => {
2688
- expect(() => {
2689
- ReactDOMServer.renderToString(
2690
- React.createElement('input', {type: 'text', oninput: '1'}),
2691
- );
2692
- }).toErrorDev(
2857
+ ReactDOMServer.renderToString(
2858
+ React.createElement('input', {type: 'text', oninput: '1'}),
2859
+ );
2860
+ assertConsoleErrorDev([
2861
'Invalid event handler property `oninput`. ' +
2862
'React events use the camelCase naming convention, ' +
2863
// Note: we don't know the right event name so we
2864
// use a generic one (onClick) as a suggestion.
2865
// This is because we don't bundle the event system
2866
// on the server.
2699
- 'for example `onClick`.',
2700
- );
2867
+ 'for example `onClick`.\n' +
2868
+ ' in input (at **)',
2869
+ ]);
2870
ReactDOMServer.renderToString(
2871
React.createElement('input', {type: 'text', onKeydown: '1'}),
2872
);
@@ -2707,56 +2876,62 @@ describe('ReactDOMComponent', () => {
2876
});
2877
2878
it('should warn about incorrect casing on properties', async () => {
2710
- await expect(async () => {
2711
- const container = document.createElement('div');
2712
- const root = ReactDOMClient.createRoot(container);
2713
- await act(() => {
2714
- root.render(
2715
- React.createElement('input', {type: 'text', tabindex: '1'}),
2716
- );
2717
- });
2718
- }).toErrorDev('tabIndex');
2719
- });
2720
-
2721
- it('should warn about incorrect casing on event handlers', async () => {
2722
- await expect(async () => {
2723
- const container = document.createElement('div');
2724
- const root = ReactDOMClient.createRoot(container);
2879
+ const container = document.createElement('div');
2880
+ const root = ReactDOMClient.createRoot(container);
2881
+ await act(() => {
2882
+ root.render(
2883
+ React.createElement('input', {type: 'text', tabindex: '1'}),
2884
+ );
2885
+ });
2886
+ assertConsoleErrorDev([
2887
+ 'Invalid DOM property `tabindex`. Did you mean `tabIndex`?\n' +
2888
+ ' in input (at **)',
2889
+ ]);
2890
+ });
2891
2726
- await act(() => {
2727
- root.render(
2728
- React.createElement('input', {type: 'text', oninput: '1'}),
2729
- );
2730
- });
2731
- }).toErrorDev('onInput');
2732
- await expect(async () => {
2733
- const container = document.createElement('div');
2734
- const root = ReactDOMClient.createRoot(container);
2892
+ it('should warn about incorrect casing on event handlers', async () => {
2893
+ const container = document.createElement('div');
2894
+ const root = ReactDOMClient.createRoot(container);
2895
2736
- await act(() => {
2737
- root.render(
2738
- React.createElement('input', {type: 'text', onKeydown: '1'}),
2739
- );
2740
- });
2741
- }).toErrorDev('onKeyDown');
2896
+ await act(() => {
2897
+ root.render(React.createElement('input', {type: 'text', oninput: '1'}));
2898
+ });
2899
+ assertConsoleErrorDev([
2900
+ 'Invalid event handler property `oninput`. Did you mean `onInput`?\n' +
2901
+ ' in input (at **)',
2902
+ ]);
2903
+
2904
+ await act(() => {
2905
+ root.render(
2906
+ React.createElement('input', {type: 'text', onKeydown: '1'}),
2907
+ );
2908
+ });
2909
+ assertConsoleErrorDev([
2910
+ 'Invalid event handler property `onKeydown`. Did you mean `onKeyDown`?\n' +
2911
+ ' in input (at **)',
2912
+ ]);
2913
});
2914
2915
it('should warn about class', async () => {
2745
- await expect(async () => {
2746
- const container = document.createElement('div');
2747
- const root = ReactDOMClient.createRoot(container);
2748
- await act(() => {
2749
- root.render(React.createElement('div', {class: 'muffins'}));
2750
- });
2751
- }).toErrorDev('className');
2916
+ const container = document.createElement('div');
2917
+ const root = ReactDOMClient.createRoot(container);
2918
+ await act(() => {
2919
+ root.render(React.createElement('div', {class: 'muffins'}));
2920
+ });
2921
+ assertConsoleErrorDev([
2922
+ 'Invalid DOM property `class`. Did you mean `className`?\n' +
2923
+ ' in div (at **)',
2924
+ ]);
2925
});
2926
2927
it('should warn about class (ssr)', () => {
2755
- expect(() => {
2756
- ReactDOMServer.renderToString(
2757
- React.createElement('div', {class: 'muffins'}),
2758
- );
2759
- }).toErrorDev('className');
2928
+ ReactDOMServer.renderToString(
2929
+ React.createElement('div', {class: 'muffins'}),
2930
+ );
2931
+ assertConsoleErrorDev([
2932
+ 'Invalid DOM property `class`. Did you mean `className`?\n' +
2933
+ ' in div (at **)',
2934
+ ]);
2935
});
2936
2937
it('should warn about props that are no longer supported', async () => {
@@ -2766,25 +2941,27 @@ describe('ReactDOMComponent', () => {
2941
root.render(<div />);
2942
});
2943
2769
- await expect(async () => {
2770
- container = document.createElement('div');
2771
- root = ReactDOMClient.createRoot(container);
2944
+ container = document.createElement('div');
2945
+ root = ReactDOMClient.createRoot(container);
2946
2773
- await act(() => {
2774
- root.render(<div onFocusIn={() => {}} />);
2775
- });
2776
- }).toErrorDev(
2777
- 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut.',
2778
- );
2779
- await expect(async () => {
2780
- container = document.createElement('div');
2781
- root = ReactDOMClient.createRoot(container);
2782
- await act(() => {
2783
- root.render(<div onFocusOut={() => {}} />);
2784
- });
2785
- }).toErrorDev(
2786
- 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut.',
2787
- );
2947
+ await act(() => {
2948
+ root.render(<div onFocusIn={() => {}} />);
2949
+ });
2950
+ assertConsoleErrorDev([
2951
+ 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut. ' +
2952
+ 'All React events are normalized to bubble, so onFocusIn and onFocusOut are not needed/supported by React.\n' +
2953
+ ' in div (at **)',
2954
+ ]);
2955
+ container = document.createElement('div');
2956
+ root = ReactDOMClient.createRoot(container);
2957
+ await act(() => {
2958
+ root.render(<div onFocusOut={() => {}} />);
2959
+ });
2960
+ assertConsoleErrorDev([
2961
+ 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut. ' +
2962
+ 'All React events are normalized to bubble, so onFocusIn and onFocusOut are not needed/supported by React.\n' +
2963
+ ' in div (at **)',
2964
+ ]);
2965
});
2966
2967
it('should warn about props that are no longer supported without case sensitivity', async () => {
@@ -2794,93 +2971,98 @@ describe('ReactDOMComponent', () => {
2971
root.render(<div />);
2972
});
2973
2797
- await expect(async () => {
2798
- container = document.createElement('div');
2799
- root = ReactDOMClient.createRoot(container);
2800
- await act(() => {
2801
- root.render(<div onfocusin={() => {}} />);
2802
- });
2803
- }).toErrorDev(
2804
- 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut.',
2805
- );
2806
- await expect(async () => {
2807
- container = document.createElement('div');
2808
- root = ReactDOMClient.createRoot(container);
2809
- await act(() => {
2810
- root.render(<div onfocusout={() => {}} />);
2811
- });
2812
- }).toErrorDev(
2813
- 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut.',
2814
- );
2974
+ container = document.createElement('div');
2975
+ root = ReactDOMClient.createRoot(container);
2976
+ await act(() => {
2977
+ root.render(<div onfocusin={() => {}} />);
2978
+ });
2979
+ assertConsoleErrorDev([
2980
+ 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut. ' +
2981
+ 'All React events are normalized to bubble, so onFocusIn and onFocusOut are not needed/supported by React.\n' +
2982
+ ' in div (at **)',
2983
+ ]);
2984
+ container = document.createElement('div');
2985
+ root = ReactDOMClient.createRoot(container);
2986
+ await act(() => {
2987
+ root.render(<div onfocusout={() => {}} />);
2988
+ });
2989
+ assertConsoleErrorDev([
2990
+ 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut. ' +
2991
+ 'All React events are normalized to bubble, so onFocusIn and onFocusOut are not needed/supported by React.\n' +
2992
+ ' in div (at **)',
2993
+ ]);
2994
});
2995
2996
it('should warn about props that are no longer supported (ssr)', () => {
2997
ReactDOMServer.renderToString(<div />);
2819
- expect(() =>
2820
- ReactDOMServer.renderToString(<div onFocusIn={() => {}} />),
2821
- ).toErrorDev(
2822
- 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut.',
2823
- );
2824
- expect(() =>
2825
- ReactDOMServer.renderToString(<div onFocusOut={() => {}} />),
2826
- ).toErrorDev(
2827
- 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut.',
2828
- );
2998
+ ReactDOMServer.renderToString(<div onFocusIn={() => {}} />);
2999
+ assertConsoleErrorDev([
3000
+ 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut. ' +
3001
+ 'All React events are normalized to bubble, so onFocusIn and onFocusOut are not needed/supported by React.\n' +
3002
+ ' in div (at **)',
3003
+ ]);
3004
+ ReactDOMServer.renderToString(<div onFocusOut={() => {}} />);
3005
+ assertConsoleErrorDev([
3006
+ 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut. ' +
3007
+ 'All React events are normalized to bubble, so onFocusIn and onFocusOut are not needed/supported by React.\n' +
3008
+ ' in div (at **)',
3009
+ ]);
3010
});
3011
3012
it('should warn about props that are no longer supported without case sensitivity (ssr)', () => {
3013
ReactDOMServer.renderToString(<div />);
2833
- expect(() =>
2834
- ReactDOMServer.renderToString(<div onfocusin={() => {}} />),
2835
- ).toErrorDev(
2836
- 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut.',
2837
- );
2838
- expect(() =>
2839
- ReactDOMServer.renderToString(<div onfocusout={() => {}} />),
2840
- ).toErrorDev(
2841
- 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut.',
2842
- );
3014
+ ReactDOMServer.renderToString(<div onfocusin={() => {}} />);
3015
+ assertConsoleErrorDev([
3016
+ 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut. ' +
3017
+ 'All React events are normalized to bubble, so onFocusIn and onFocusOut are not needed/supported by React.\n' +
3018
+ ' in div (at **)',
3019
+ ]);
3020
+ ReactDOMServer.renderToString(<div onfocusout={() => {}} />);
3021
+ assertConsoleErrorDev([
3022
+ 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut. ' +
3023
+ 'All React events are normalized to bubble, so onFocusIn and onFocusOut are not needed/supported by React.\n' +
3024
+ ' in div (at **)',
3025
+ ]);
3026
});
3027
3028
it('gives source code refs for unknown prop warning', async () => {
2846
- await expect(async () => {
2847
- const container = document.createElement('div');
2848
- const root = ReactDOMClient.createRoot(container);
2849
- await act(() => {
2850
- root.render(<div class="paladin" />);
2851
- });
2852
- }).toErrorDev(
2853
- 'Invalid DOM property `class`. Did you mean `className`?\n in div (at **)',
2854
- );
2855
- await expect(async () => {
2856
- const container = document.createElement('div');
2857
- const root = ReactDOMClient.createRoot(container);
2858
- await act(() => {
2859
- root.render(<input type="text" onclick="1" />);
2860
- });
2861
- }).toErrorDev(
3029
+ let container = document.createElement('div');
3030
+ let root = ReactDOMClient.createRoot(container);
3031
+ await act(() => {
3032
+ root.render(<div class="paladin" />);
3033
+ });
3034
+ assertConsoleErrorDev([
3035
+ 'Invalid DOM property `class`. Did you mean `className`?\n' +
3036
+ ' in div (at **)',
3037
+ ]);
3038
+ container = document.createElement('div');
3039
+ root = ReactDOMClient.createRoot(container);
3040
+ await act(() => {
3041
+ root.render(<input type="text" onclick="1" />);
3042
+ });
3043
+ assertConsoleErrorDev([
3044
'Invalid event handler property `onclick`. Did you mean ' +
2863
- '`onClick`?\n in input (at **)',
2864
- );
3045
+ '`onClick`?\n' +
3046
+ ' in input (at **)',
3047
+ ]);
3048
});
3049
3050
it('gives source code refs for unknown prop warning (ssr)', () => {
2868
- expect(() =>
2869
- ReactDOMServer.renderToString(<div class="paladin" />),
2870
- ).toErrorDev(
2871
- 'Invalid DOM property `class`. Did you mean `className`?\n in div (at **)',
2872
- );
2873
- expect(() =>
2874
- ReactDOMServer.renderToString(<input type="text" oninput="1" />),
2875
- ).toErrorDev(
3051
+ ReactDOMServer.renderToString(<div class="paladin" />);
3052
+ assertConsoleErrorDev([
3053
+ 'Invalid DOM property `class`. Did you mean `className`?\n' +
3054
+ ' in div (at **)',
3055
+ ]);
3056
+ ReactDOMServer.renderToString(<input type="text" oninput="1" />);
3057
+ assertConsoleErrorDev([
3058
'Invalid event handler property `oninput`. ' +
3059
// Note: we don't know the right event name so we
3060
// use a generic one (onClick) as a suggestion.
3061
// This is because we don't bundle the event system
3062
// on the server.
2881
- 'React events use the camelCase naming convention, for example `onClick`.' +
2882
- '\n in input (at **)',
2883
- );
3063
+ 'React events use the camelCase naming convention, for example `onClick`.\n' +
3064
+ ' in input (at **)',
3065
+ ]);
3066
});
3067
3068
it('gives source code refs for unknown prop warning for update render', async () => {
@@ -2892,52 +3074,57 @@ describe('ReactDOMComponent', () => {
3074
3075
container = document.createElement('div');
3076
root = ReactDOMClient.createRoot(container);
2895
- await expect(async () => {
2896
- await act(() => {
2897
- root.render(<div class="paladin" />);
2898
- });
2899
- }).toErrorDev(
2900
- 'Invalid DOM property `class`. Did you mean `className`?\n in div (at **)',
2901
- );
2902
- });
2903
-
2904
- it('gives source code refs for unknown prop warning for exact elements', async () => {
2905
- await expect(async () => {
2906
- const container = document.createElement('div');
2907
- const root = ReactDOMClient.createRoot(container);
2908
- await act(() => {
2909
- root.render(
2910
- <div className="foo1">
2911
- <span class="foo2" />
2912
- <div onClick={() => {}} />
2913
- <strong onclick={() => {}} />
2914
- <div className="foo5" />
2915
- <div className="foo6" />
2916
- </div>,
2917
- );
2918
- });
2919
- }).toErrorDev([
2920
- 'Invalid DOM property `class`. Did you mean `className`?\n in span (at **)',
2921
- 'Invalid event handler property `onclick`. Did you mean `onClick`?\n in strong (at **)',
3077
+ await act(() => {
3078
+ root.render(<div class="paladin" />);
3079
+ });
3080
+ assertConsoleErrorDev([
3081
+ 'Invalid DOM property `class`. Did you mean `className`?\n' +
3082
+ ' in div (at **)',
3083
]);
3084
});
3085
2925
- it('gives source code refs for unknown prop warning for exact elements (ssr)', () => {
2926
- expect(() =>
2927
- ReactDOMServer.renderToString(
3086
+ it('gives source code refs for unknown prop warning for exact elements', async () => {
3087
+ const container = document.createElement('div');
3088
+ const root = ReactDOMClient.createRoot(container);
3089
+ await act(() => {
3090
+ root.render(
3091
<div className="foo1">
3092
<span class="foo2" />
2930
- <div onClick="foo3" />
2931
- <strong onclick="foo4" />
3093
+ <div onClick={() => {}} />
3094
+ <strong onclick={() => {}} />
3095
<div className="foo5" />
3096
<div className="foo6" />
3097
</div>,
2935
- ),
2936
- ).toErrorDev([
2937
- 'Invalid DOM property `class`. Did you mean `className`?\n in span (at **)',
3098
+ );
3099
+ });
3100
+ assertConsoleErrorDev([
3101
+ 'Invalid DOM property `class`. Did you mean `className`?\n' +
3102
+ ' in span (at **)' +
3103
+ (gate('enableOwnerStacks') ? '' : '\n in div (at **)'),
3104
+ 'Invalid event handler property `onclick`. Did you mean `onClick`?\n' +
3105
+ ' in strong (at **)' +
3106
+ (gate('enableOwnerStacks') ? '' : '\n in div (at **)'),
3107
+ ]);
3108
+ });
3109
+
3110
+ it('gives source code refs for unknown prop warning for exact elements (ssr)', () => {
3111
+ ReactDOMServer.renderToString(
3112
+ <div className="foo1">
3113
+ <span class="foo2" />
3114
+ <div onClick="foo3" />
3115
+ <strong onclick="foo4" />
3116
+ <div className="foo5" />
3117
+ <div className="foo6" />
3118
+ </div>,
3119
+ );
3120
+ assertConsoleErrorDev([
3121
+ 'Invalid DOM property `class`. Did you mean `className`?\n' +
3122
+ ' in span (at **)' +
3123
+ (gate('enableOwnerStacks') ? '' : '\n in div (at **)'),
3124
'Invalid event handler property `onclick`. ' +
2939
- 'React events use the camelCase naming convention, for example `onClick`.' +
2940
- '\n in strong (at **)',
3125
+ 'React events use the camelCase naming convention, for example `onClick`.\n' +
3126
+ ' in strong (at **)' +
3127
+ (gate('enableOwnerStacks') ? '' : '\n in div (at **)'),
3128
]);
3129
});
3130
@@ -2979,15 +3166,22 @@ describe('ReactDOMComponent', () => {
3166
}
3167
}
3168
2982
- await expect(async () => {
2983
- const container = document.createElement('div');
2984
- const root = ReactDOMClient.createRoot(container);
2985
- await act(() => {
2986
- root.render(<Parent />);
2987
- });
2988
- }).toErrorDev([
2989
- 'Invalid DOM property `class`. Did you mean `className`?\n in span (at **)',
2990
- 'Invalid event handler property `onclick`. Did you mean `onClick`?\n in strong (at **)',
3169
+ const container = document.createElement('div');
3170
+ const root = ReactDOMClient.createRoot(container);
3171
+ await act(() => {
3172
+ root.render(<Parent />);
3173
+ });
3174
+ assertConsoleErrorDev([
3175
+ 'Invalid DOM property `class`. Did you mean `className`?\n' +
3176
+ ' in span (at **)\n' +
3177
+ ' in Child1 (at **)\n' +
3178
+ (gate('enableOwnerStacks') ? '' : ' in div (at **)\n') +
3179
+ ' in Parent (at **)',
3180
+ 'Invalid event handler property `onclick`. Did you mean `onClick`?\n' +
3181
+ ' in strong (at **)\n' +
3182
+ ' in Child3 (at **)\n' +
3183
+ (gate('enableOwnerStacks') ? '' : ' in div (at **)\n') +
3184
+ ' in Parent (at **)',
3185
]);
3186
});
3187
@@ -3031,55 +3225,60 @@ describe('ReactDOMComponent', () => {
3225
}
3226
}
3227
3034
- expect(() =>
3035
- ReactDOMServer.renderToString(<Parent />, container),
3036
- ).toErrorDev([
3037
- 'Invalid DOM property `class`. Did you mean `className`?\n in span (at **)',
3228
+ ReactDOMServer.renderToString(<Parent />, container);
3229
+ assertConsoleErrorDev([
3230
+ 'Invalid DOM property `class`. Did you mean `className`?\n' +
3231
+ ' in span (at **)\n' +
3232
+ ' in Child1 (at **)\n' +
3233
+ (gate('enableOwnerStacks') ? '' : ' in div (at **)\n') +
3234
+ ' in Parent (at **)',
3235
'Invalid event handler property `onclick`. ' +
3039
- 'React events use the camelCase naming convention, for example `onClick`.' +
3040
- '\n in strong (at **)',
3236
+ 'React events use the camelCase naming convention, for example `onClick`.\n' +
3237
+ ' in strong (at **)\n' +
3238
+ ' in Child3 (at **)\n' +
3239
+ (gate('enableOwnerStacks') ? '' : ' in div (at **)\n') +
3240
+ ' in Parent (at **)',
3241
]);
3242
});
3243
3244
it('should suggest property name if available', async () => {
3045
- await expect(async () => {
3046
- const container = document.createElement('div');
3047
- const root = ReactDOMClient.createRoot(container);
3048
- await act(() => {
3049
- root.render(React.createElement('label', {for: 'test'}));
3050
- });
3051
- }).toErrorDev(
3052
- 'Invalid DOM property `for`. Did you mean `htmlFor`?\n in label',
3053
- );
3245
+ let container = document.createElement('div');
3246
+ let root = ReactDOMClient.createRoot(container);
3247
+ await act(() => {
3248
+ root.render(React.createElement('label', {for: 'test'}));
3249
+ });
3250
+ assertConsoleErrorDev([
3251
+ 'Invalid DOM property `for`. Did you mean `htmlFor`?\n' +
3252
+ ' in label',
3253
+ ]);
3254
3055
- await expect(async () => {
3056
- const container = document.createElement('div');
3057
- const root = ReactDOMClient.createRoot(container);
3058
- await act(() => {
3059
- root.render(
3060
- React.createElement('input', {type: 'text', autofocus: true}),
3061
- );
3062
- });
3063
- }).toErrorDev(
3255
+ container = document.createElement('div');
3256
+ root = ReactDOMClient.createRoot(container);
3257
+ await act(() => {
3258
+ root.render(
3259
+ React.createElement('input', {type: 'text', autofocus: true}),
3260
+ );
3261
+ });
3262
+ assertConsoleErrorDev([
3263
'Invalid DOM property `autofocus`. Did you mean `autoFocus`?\n in input',
3065
- );
3264
+ ]);
3265
});
3266
3267
it('should suggest property name if available (ssr)', () => {
3069
- expect(() =>
3070
- ReactDOMServer.renderToString(
3071
- React.createElement('label', {for: 'test'}),
3072
- ),
3073
- ).toErrorDev(
3074
- 'Invalid DOM property `for`. Did you mean `htmlFor`?\n in label',
3268
+ ReactDOMServer.renderToString(
3269
+ React.createElement('label', {for: 'test'}),
3270
);
3076
- expect(() =>
3077
- ReactDOMServer.renderToString(
3078
- React.createElement('input', {type: 'text', autofocus: true}),
3079
- ),
3080
- ).toErrorDev(
3081
- 'Invalid DOM property `autofocus`. Did you mean `autoFocus`?\n in input',
3271
+ assertConsoleErrorDev([
3272
+ 'Invalid DOM property `for`. Did you mean `htmlFor`?\n' +
3273
+ ' in label',
3274
+ ]);
3275
+ ReactDOMServer.renderToString(
3276
+ React.createElement('input', {type: 'text', autofocus: true}),
3277
);
3278
+ assertConsoleErrorDev([
3279
+ 'Invalid DOM property `autofocus`. Did you mean `autoFocus`?\n' +
3280
+ ' in input',
3281
+ ]);
3282
});
3283
});
3284
@@ -3119,48 +3318,53 @@ describe('ReactDOMComponent', () => {
3318
describe('Attributes with aliases', function () {
3319
it('sets aliased attributes on HTML attributes', async function () {
3320
let el;
3122
- await expect(async () => {
3123
- const container = document.createElement('div');
3124
- const root = ReactDOMClient.createRoot(container);
3321
+ const container = document.createElement('div');
3322
+ const root = ReactDOMClient.createRoot(container);
3323
3126
- await act(() => {
3127
- root.render(<div class="test" ref={current => (el = current)} />);
3128
- });
3129
- }).toErrorDev('Invalid DOM property `class`. Did you mean `className`?');
3324
+ await act(() => {
3325
+ root.render(<div class="test" ref={current => (el = current)} />);
3326
+ });
3327
+ assertConsoleErrorDev([
3328
+ 'Invalid DOM property `class`. Did you mean `className`?\n' +
3329
+ ' in div (at **)',
3330
+ ]);
3331
3332
expect(el.className).toBe('test');
3333
});
3334
3335
it('sets incorrectly cased aliased attributes on HTML attributes with a warning', async function () {
3336
let el;
3136
- await expect(async () => {
3137
- const container = document.createElement('div');
3138
- const root = ReactDOMClient.createRoot(container);
3337
+ const container = document.createElement('div');
3338
+ const root = ReactDOMClient.createRoot(container);
3339
3140
- await act(() => {
3141
- root.render(<div cLASS="test" ref={current => (el = current)} />);
3142
- });
3143
- }).toErrorDev('Invalid DOM property `cLASS`. Did you mean `className`?');
3340
+ await act(() => {
3341
+ root.render(<div cLASS="test" ref={current => (el = current)} />);
3342
+ });
3343
+ assertConsoleErrorDev([
3344
+ 'Invalid DOM property `cLASS`. Did you mean `className`?\n' +
3345
+ ' in div (at **)',
3346
+ ]);
3347
3348
expect(el.className).toBe('test');
3349
});
3350
3351
it('sets aliased attributes on SVG elements with a warning', async function () {
3352
let el;
3150
- await expect(async () => {
3151
- const container = document.createElement('div');
3152
- const root = ReactDOMClient.createRoot(container);
3353
+ const container = document.createElement('div');
3354
+ const root = ReactDOMClient.createRoot(container);
3355
3154
- await act(() => {
3155
- root.render(
3156
- <svg ref={current => (el = current)}>
3157
- <text arabic-form="initial" />
3158
- </svg>,
3159
- );
3160
- });
3161
- }).toErrorDev(
3162
- 'Invalid DOM property `arabic-form`. Did you mean `arabicForm`?',
3163
- );
3356
+ await act(() => {
3357
+ root.render(
3358
+ <svg ref={current => (el = current)}>
3359
+ <text arabic-form="initial" />
3360
+ </svg>,
3361
+ );
3362
+ });
3363
+ assertConsoleErrorDev([
3364
+ 'Invalid DOM property `arabic-form`. Did you mean `arabicForm`?\n' +
3365
+ ' in text (at **)' +
3366
+ (gate('enableOwnerStacks') ? '' : '\n in svg (at **)'),
3367
+ ]);
3368
const text = el.querySelector('text');
3369
3370
expect(text.hasAttribute('arabic-form')).toBe(true);
@@ -3236,39 +3440,39 @@ describe('ReactDOMComponent', () => {
3440
3441
it('does not assign a boolean custom attributes as a string', async function () {
3442
let el;
3239
- await expect(async () => {
3240
- const container = document.createElement('div');
3241
- const root = ReactDOMClient.createRoot(container);
3443
+ const container = document.createElement('div');
3444
+ const root = ReactDOMClient.createRoot(container);
3445
3243
- await act(() => {
3244
- root.render(<div whatever={true} ref={current => (el = current)} />);
3245
- });
3246
- }).toErrorDev(
3446
+ await act(() => {
3447
+ root.render(<div whatever={true} ref={current => (el = current)} />);
3448
+ });
3449
+ assertConsoleErrorDev([
3450
'Received `true` for a non-boolean attribute `whatever`.\n\n' +
3451
'If you want to write it to the DOM, pass a string instead: ' +
3249
- 'whatever="true" or whatever={value.toString()}.',
3250
- );
3452
+ 'whatever="true" or whatever={value.toString()}.\n' +
3453
+ ' in div (at **)',
3454
+ ]);
3455
3456
expect(el.hasAttribute('whatever')).toBe(false);
3457
});
3458
3459
it('does not assign an implicit boolean custom attributes', async function () {
3460
let el;
3257
- await expect(async () => {
3258
- const container = document.createElement('div');
3259
- const root = ReactDOMClient.createRoot(container);
3461
+ const container = document.createElement('div');
3462
+ const root = ReactDOMClient.createRoot(container);
3463
3261
- await act(() => {
3262
- root.render(
3263
- // eslint-disable-next-line react/jsx-boolean-value
3264
- <div whatever ref={current => (el = current)} />,
3265
- );
3266
- });
3267
- }).toErrorDev(
3464
+ await act(() => {
3465
+ root.render(
3466
+ // eslint-disable-next-line react/jsx-boolean-value
3467
+ <div whatever ref={current => (el = current)} />,
3468
+ );
3469
+ });
3470
+ assertConsoleErrorDev([
3471
'Received `true` for a non-boolean attribute `whatever`.\n\n' +
3472
'If you want to write it to the DOM, pass a string instead: ' +
3270
- 'whatever="true" or whatever={value.toString()}.',
3271
- );
3473
+ 'whatever="true" or whatever={value.toString()}.\n' +
3474
+ ' in div (at **)',
3475
+ ]);
3476
3477
expect(el.hasAttribute('whatever')).toBe(false);
3478
});
@@ -3288,16 +3492,20 @@ describe('ReactDOMComponent', () => {
3492
3493
it('will not assign a function custom attributes', async function () {
3494
let el;
3291
- await expect(async () => {
3292
- const container = document.createElement('div');
3293
- const root = ReactDOMClient.createRoot(container);
3495
+ const container = document.createElement('div');
3496
+ const root = ReactDOMClient.createRoot(container);
3497
3295
- await act(() => {
3296
- root.render(
3297
- <div whatever={() => {}} ref={current => (el = current)} />,
3298
- );
3299
- });
3300
- }).toErrorDev('Invalid value for prop `whatever` on <div> tag');
3498
+ await act(() => {
3499
+ root.render(
3500
+ <div whatever={() => {}} ref={current => (el = current)} />,
3501
+ );
3502
+ });
3503
+ assertConsoleErrorDev([
3504
+ 'Invalid value for prop `whatever` on <div> tag. ' +
3505
+ 'Either remove it from the element, or pass a string or number value to keep it in the DOM. ' +
3506
+ 'For details, see https://react.dev/link/attribute-behavior \n' +
3507
+ ' in div (at **)',
3508
+ ]);
3509
3510
expect(el.hasAttribute('whatever')).toBe(false);
3511
});
@@ -3340,59 +3548,55 @@ describe('ReactDOMComponent', () => {
3548
3549
it('allows cased data attributes', async () => {
3550
let el;
3343
- await expect(async () => {
3344
- const container = document.createElement('div');
3345
- const root = ReactDOMClient.createRoot(container);
3551
+ const container = document.createElement('div');
3552
+ const root = ReactDOMClient.createRoot(container);
3553
3347
- await act(() => {
3348
- root.render(
3349
- <div data-fooBar="true" ref={current => (el = current)} />,
3350
- );
3351
- });
3352
- }).toErrorDev(
3554
+ await act(() => {
3555
+ root.render(<div data-fooBar="true" ref={current => (el = current)} />);
3556
+ });
3557
+ assertConsoleErrorDev([
3558
'React does not recognize the `data-fooBar` prop on a DOM element. ' +
3559
'If you intentionally want it to appear in the DOM as a custom ' +
3560
'attribute, spell it as lowercase `data-foobar` instead. ' +
3561
'If you accidentally passed it from a parent component, remove ' +
3562
'it from the DOM element.\n' +
3563
' in div (at **)',
3359
- );
3564
+ ]);
3565
expect(el.getAttribute('data-foobar')).toBe('true');
3566
});
3567
3568
it('allows cased custom attributes', async () => {
3569
let el;
3365
- await expect(async () => {
3366
- const container = document.createElement('div');
3367
- const root = ReactDOMClient.createRoot(container);
3570
+ const container = document.createElement('div');
3571
+ const root = ReactDOMClient.createRoot(container);
3572
3369
- await act(() => {
3370
- root.render(<div fooBar="true" ref={current => (el = current)} />);
3371
- });
3372
- }).toErrorDev(
3573
+ await act(() => {
3574
+ root.render(<div fooBar="true" ref={current => (el = current)} />);
3575
+ });
3576
+ assertConsoleErrorDev([
3577
'React does not recognize the `fooBar` prop on a DOM element. ' +
3578
'If you intentionally want it to appear in the DOM as a custom ' +
3579
'attribute, spell it as lowercase `foobar` instead. ' +
3580
'If you accidentally passed it from a parent component, remove ' +
3581
'it from the DOM element.\n' +
3582
' in div (at **)',
3379
- );
3583
+ ]);
3584
expect(el.getAttribute('foobar')).toBe('true');
3585
});
3586
3587
it('warns on NaN attributes', async () => {
3588
let el;
3385
- await expect(async () => {
3386
- const container = document.createElement('div');
3387
- const root = ReactDOMClient.createRoot(container);
3589
+ const container = document.createElement('div');
3590
+ const root = ReactDOMClient.createRoot(container);
3591
3389
- await act(() => {
3390
- root.render(<div whatever={NaN} ref={current => (el = current)} />);
3391
- });
3392
- }).toErrorDev(
3592
+ await act(() => {
3593
+ root.render(<div whatever={NaN} ref={current => (el = current)} />);
3594
+ });
3595
+ assertConsoleErrorDev([
3596
'Received NaN for the `whatever` attribute. If this is ' +
3394
- 'expected, cast the value to a string.\n in div',
3395
- );
3597
+ 'expected, cast the value to a string.\n' +
3598
+ ' in div',
3599
+ ]);
3600
3601
expect(el.getAttribute('whatever')).toBe('NaN');
3602
});
@@ -3403,25 +3607,31 @@ describe('ReactDOMComponent', () => {
3607
await act(() => {
3608
root.render(<div whatever={0} />);
3609
});
3406
- await expect(async () => {
3407
- await act(() => {
3408
- root.render(<div whatever={() => {}} />);
3409
- });
3410
- }).toErrorDev('Invalid value for prop `whatever` on <div> tag.');
3610
+ await act(() => {
3611
+ root.render(<div whatever={() => {}} />);
3612
+ });
3613
+ assertConsoleErrorDev([
3614
+ 'Invalid value for prop `whatever` on <div> tag. ' +
3615
+ 'Either remove it from the element, or pass a string or number value to keep it in the DOM. ' +
3616
+ 'For details, see https://react.dev/link/attribute-behavior \n' +
3617
+ ' in div (at **)',
3618
+ ]);
3619
const el = container.firstChild;
3620
expect(el.hasAttribute('whatever')).toBe(false);
3621
});
3622
3623
it('warns on bad casing of known HTML attributes', async function () {
3624
let el;
3417
- await expect(async () => {
3418
- const container = document.createElement('div');
3419
- const root = ReactDOMClient.createRoot(container);
3625
+ const container = document.createElement('div');
3626
+ const root = ReactDOMClient.createRoot(container);
3627
3421
- await act(() => {
3422
- root.render(<div SiZe="30" ref={current => (el = current)} />);
3423
- });
3424
- }).toErrorDev('Invalid DOM property `SiZe`. Did you mean `size`?');
3628
+ await act(() => {
3629
+ root.render(<div SiZe="30" ref={current => (el = current)} />);
3630
+ });
3631
+ assertConsoleErrorDev([
3632
+ 'Invalid DOM property `SiZe`. Did you mean `size`?\n' +
3633
+ ' in div (at **)',
3634
+ ]);
3635
3636
expect(el.getAttribute('size')).toBe('30');
3637
});
@@ -3524,18 +3734,18 @@ describe('ReactDOMComponent', () => {
3734
describe('String boolean attributes', function () {
3735
it('does not assign string boolean attributes for custom attributes', async function () {
3736
let el;
3527
- await expect(async () => {
3528
- const container = document.createElement('div');
3529
- const root = ReactDOMClient.createRoot(container);
3737
+ const container = document.createElement('div');
3738
+ const root = ReactDOMClient.createRoot(container);
3739
3531
- await act(() => {
3532
- root.render(<div whatever={true} ref={current => (el = current)} />);
3533
- });
3534
- }).toErrorDev(
3740
+ await act(() => {
3741
+ root.render(<div whatever={true} ref={current => (el = current)} />);
3742
+ });
3743
+ assertConsoleErrorDev([
3744
'Received `true` for a non-boolean attribute `whatever`.\n\n' +
3745
'If you want to write it to the DOM, pass a string instead: ' +
3537
- 'whatever="true" or whatever={value.toString()}.',
3538
- );
3746
+ 'whatever="true" or whatever={value.toString()}.\n' +
3747
+ ' in div (at **)',
3748
+ ]);
3749
3750
expect(el.hasAttribute('whatever')).toBe(false);
3751
});
@@ -3584,36 +3794,36 @@ describe('ReactDOMComponent', () => {
3794
describe('Boolean attributes', function () {
3795
it('warns on the ambiguous string value "false"', async function () {
3796
let el;
3587
- await expect(async () => {
3588
- const container = document.createElement('div');
3589
- const root = ReactDOMClient.createRoot(container);
3797
+ const container = document.createElement('div');
3798
+ const root = ReactDOMClient.createRoot(container);
3799
3591
- await act(() => {
3592
- root.render(<div hidden="false" ref={current => (el = current)} />);
3593
- });
3594
- }).toErrorDev(
3800
+ await act(() => {
3801
+ root.render(<div hidden="false" ref={current => (el = current)} />);
3802
+ });
3803
+ assertConsoleErrorDev([
3804
'Received the string `false` for the boolean attribute `hidden`. ' +
3805
'The browser will interpret it as a truthy value. ' +
3597
- 'Did you mean hidden={false}?',
3598
- );
3806
+ 'Did you mean hidden={false}?\n' +
3807
+ ' in div (at **)',
3808
+ ]);
3809
3810
expect(el.getAttribute('hidden')).toBe('');
3811
});
3812
3813
it('warns on the potentially-ambiguous string value "true"', async function () {
3814
let el;
3605
- await expect(async () => {
3606
- const container = document.createElement('div');
3607
- const root = ReactDOMClient.createRoot(container);
3815
+ const container = document.createElement('div');
3816
+ const root = ReactDOMClient.createRoot(container);
3817
3609
- await act(() => {
3610
- root.render(<div hidden="true" ref={current => (el = current)} />);
3611
- });
3612
- }).toErrorDev(
3818
+ await act(() => {
3819
+ root.render(<div hidden="true" ref={current => (el = current)} />);
3820
+ });
3821
+ assertConsoleErrorDev([
3822
'Received the string `true` for the boolean attribute `hidden`. ' +
3823
'Although this works, it will not work as expected if you pass the string "false". ' +
3615
- 'Did you mean hidden={true}?',
3616
- );
3824
+ 'Did you mean hidden={true}?\n' +
3825
+ ' in div (at **)',
3826
+ ]);
3827
3828
expect(el.getAttribute('hidden')).toBe('');
3829
});
@@ -3622,18 +3832,21 @@ describe('ReactDOMComponent', () => {
3832
describe('Hyphenated SVG elements', function () {
3833
it('the font-face element is not a custom element', async function () {
3834
let el;
3625
- await expect(async () => {
3626
- const container = document.createElement('div');
3627
- const root = ReactDOMClient.createRoot(container);
3835
+ const container = document.createElement('div');
3836
+ const root = ReactDOMClient.createRoot(container);
3837
3629
- await act(() => {
3630
- root.render(
3631
- <svg ref={current => (el = current)}>
3632
- <font-face x-height={false} />
3633
- </svg>,
3634
- );
3635
- });
3636
- }).toErrorDev('Invalid DOM property `x-height`. Did you mean `xHeight`');
3838
+ await act(() => {
3839
+ root.render(
3840
+ <svg ref={current => (el = current)}>
3841
+ <font-face x-height={false} />
3842
+ </svg>,
3843
+ );
3844
+ });
3845
+ assertConsoleErrorDev([
3846
+ 'Invalid DOM property `x-height`. Did you mean `xHeight`?\n' +
3847
+ ' in font-face (at **)' +
3848
+ (gate('enableOwnerStacks') ? '' : '\n in svg (at **)'),
3849
+ ]);
3850
3851
expect(el.querySelector('font-face').hasAttribute('x-height')).toBe(
3852
false,
@@ -3642,24 +3855,25 @@ describe('ReactDOMComponent', () => {
3855
3856
it('the font-face element does not allow unknown boolean values', async function () {
3857
let el;
3645
- await expect(async () => {
3646
- const container = document.createElement('div');
3647
- const root = ReactDOMClient.createRoot(container);
3858
+ const container = document.createElement('div');
3859
+ const root = ReactDOMClient.createRoot(container);
3860
3649
- await act(() => {
3650
- root.render(
3651
- <svg ref={current => (el = current)}>
3652
- <font-face whatever={false} />
3653
- </svg>,
3654
- );
3655
- });
3656
- }).toErrorDev(
3861
+ await act(() => {
3862
+ root.render(
3863
+ <svg ref={current => (el = current)}>
3864
+ <font-face whatever={false} />
3865
+ </svg>,
3866
+ );
3867
+ });
3868
+ assertConsoleErrorDev([
3869
'Received `false` for a non-boolean attribute `whatever`.\n\n' +
3870
'If you want to write it to the DOM, pass a string instead: ' +
3871
'whatever="false" or whatever={value.toString()}.\n\n' +
3872
'If you used to conditionally omit it with whatever={condition && value}, ' +
3661
- 'pass whatever={condition ? value : undefined} instead.',
3662
- );
3873
+ 'pass whatever={condition ? value : undefined} instead.\n' +
3874
+ ' in font-face (at **)' +
3875
+ (gate('enableOwnerStacks') ? '' : '\n in svg (at **)'),
3876
+ ]);
3877
3878
expect(el.querySelector('font-face').hasAttribute('whatever')).toBe(
3879
false,