[tests] Assert on component stack for Maximum Update error (#33686)
Good to assert these include the component stack
Ricky committed
Jul 7, 2025 at 13:58 UTC
e4314a0a0f6ea1ab8f739c9119583d148c1be657
1 file changed
+72
-7
packages/react-dom/src/__tests__/ReactUpdates-test.js
+72
-7
@@ -20,6 +20,19 @@ let waitFor;
20
let assertLog;
21
let assertConsoleErrorDev;
22
23
+function normalizeCodeLocInfo(str) {
24
+ return (
25
+ str &&
26
+ str.replace(/^ +(?:at|in) ([\S]+)[^\n]*/gm, function (m, name) {
27
+ const dot = name.lastIndexOf('.');
28
+ if (dot !== -1) {
29
+ name = name.slice(dot + 1);
30
+ }
31
+ return ' in ' + name + (/\d/.test(m) ? ' (at **)' : '');
32
+ })
33
+ );
34
+}
35
+
36
describe('ReactUpdates', () => {
37
beforeEach(() => {
38
jest.resetModules();
@@ -1972,13 +1985,65 @@ describe('ReactUpdates', () => {
1985
}
1986
1987
const container = document.createElement('div');
1975
- const root = ReactDOMClient.createRoot(container);
1976
- await expect(async () => {
1977
- await act(() => {
1978
- ReactDOM.flushSync(() => {
1979
- root.render(<NonTerminating />);
1980
- });
1988
+ const errors = [];
1989
+ const root = ReactDOMClient.createRoot(container, {
1990
+ onUncaughtError: (error, errorInfo) => {
1991
+ errors.push(
1992
+ `${error.message}${normalizeCodeLocInfo(errorInfo.componentStack)}`,
1993
+ );
1994
+ },
1995
+ });
1996
+ await act(() => {
1997
+ ReactDOM.flushSync(() => {
1998
+ root.render(<NonTerminating />);
1999
});
1982
- }).rejects.toThrow('Maximum update depth exceeded');
2000
+ });
2001
+
2002
+ expect(errors).toEqual([
2003
+ 'Maximum update depth exceeded. ' +
2004
+ 'This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. ' +
2005
+ 'React limits the number of nested updates to prevent infinite loops.' +
2006
+ '\n in NonTerminating (at **)',
2007
+ ]);
2008
+ });
2009
+
2010
+ it('prevents infinite update loop triggered by too many updates in ref callbacks', async () => {
2011
+ let scheduleUpdate;
2012
+ function TooManyRefUpdates() {
2013
+ const [count, _scheduleUpdate] = React.useReducer(c => c + 1, 0);
2014
+ scheduleUpdate = _scheduleUpdate;
2015
+
2016
+ return (
2017
+ <div
2018
+ ref={() => {
2019
+ for (let i = 0; i < 50; i++) {
2020
+ scheduleUpdate(1);
2021
+ }
2022
+ }}>
2023
+ {count}
2024
+ </div>
2025
+ );
2026
+ }
2027
+
2028
+ const container = document.createElement('div');
2029
+ const errors = [];
2030
+ const root = ReactDOMClient.createRoot(container, {
2031
+ onUncaughtError: (error, errorInfo) => {
2032
+ errors.push(
2033
+ `${error.message}${normalizeCodeLocInfo(errorInfo.componentStack)}`,
2034
+ );
2035
+ },
2036
+ });
2037
+ await act(() => {
2038
+ root.render(<TooManyRefUpdates />);
2039
+ });
2040
+
2041
+ expect(errors).toEqual([
2042
+ 'Maximum update depth exceeded. ' +
2043
+ 'This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. ' +
2044
+ 'React limits the number of nested updates to prevent infinite loops.' +
2045
+ '\n in div' +
2046
+ '\n in TooManyRefUpdates (at **)',
2047
+ ]);
2048
});
2049
});