@samitouri / QOS-React-1 / commits / e4314a0a0f

[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 });