@samitouri / QOS-React / commits / d8a08f8e39

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