From 0032b2a3eec4723c433c16a738cf17e33ad742c3 Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Wed, 13 Aug 2025 08:47:09 +0200 Subject: [PATCH 1/2] [Flight] Log error if prod elements are rendered (#34189) --- packages/internal-test-utils/consoleMock.js | 2 +- .../react-server/src/ReactFlightServer.js | 21 ++++++++++++++++ .../src/__tests__/ReactFlightServer-test.js | 24 +++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/packages/internal-test-utils/consoleMock.js b/packages/internal-test-utils/consoleMock.js index ecb97b3a03059..989bb82f22edd 100644 --- a/packages/internal-test-utils/consoleMock.js +++ b/packages/internal-test-utils/consoleMock.js @@ -355,7 +355,7 @@ export function createLogAssertion( let argIndex = 0; // console.* could have been called with a non-string e.g. `console.error(new Error())` // eslint-disable-next-line react-internal/safe-string-coercion - String(format).replace(/%s|%c/g, () => argIndex++); + String(format).replace(/%s|%c|%o/g, () => argIndex++); if (argIndex !== args.length) { if (format.includes('%c%s')) { // We intentionally use mismatching formatting when printing badging because we don't know diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 19a49dcd32b98..bc9c259b3922f 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -3354,6 +3354,27 @@ function renderModelDestructive( task.debugOwner = element._owner; task.debugStack = element._debugStack; task.debugTask = element._debugTask; + if ( + element._owner === undefined || + element._debugStack === undefined || + element._debugTask === undefined + ) { + let key = ''; + if (element.key !== null) { + key = ' key="' + element.key + '"'; + } + + console.error( + 'Attempted to render <%s%s> without development properties. ' + + 'This is not supported. It can happen if:' + + '\n- The element is created with a production version of React but rendered in development.' + + '\n- The element was cloned with a custom function instead of `React.cloneElement`.\n' + + 'The props of this element may help locate this element: %o', + element.type, + key, + element.props, + ); + } // TODO: Pop this. Since we currently don't have a point where we can pop the stack // this debug information will be used for errors inside sibling properties that // are not elements. Leading to the wrong attribution on the server. We could fix diff --git a/packages/react-server/src/__tests__/ReactFlightServer-test.js b/packages/react-server/src/__tests__/ReactFlightServer-test.js index b81a793a7f29d..c924a52c4f417 100644 --- a/packages/react-server/src/__tests__/ReactFlightServer-test.js +++ b/packages/react-server/src/__tests__/ReactFlightServer-test.js @@ -36,6 +36,7 @@ let ReactNoopFlightServer; let Scheduler; let advanceTimersByTime; let assertLog; +let assertConsoleErrorDev; describe('ReactFlight', () => { beforeEach(() => { @@ -64,6 +65,7 @@ describe('ReactFlight', () => { Scheduler = require('scheduler'); const InternalTestUtils = require('internal-test-utils'); assertLog = InternalTestUtils.assertLog; + assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev; }); afterEach(() => { @@ -175,4 +177,26 @@ describe('ReactFlight', () => { stackTwo: '\n in OwnerStackDelayed (at **)' + '\n in App (at **)', }); }); + + it('logs an error when prod elements are rendered', async () => { + const element = ReactServer.createElement('span', { + key: 'one', + children: 'Free!', + }); + ReactNoopFlightServer.render( + // bad clone + {...element}, + ); + + assertConsoleErrorDev([ + [ + 'Attempted to render without development properties. This is not supported. It can happen if:' + + '\n- The element is created with a production version of React but rendered in development.' + + '\n- The element was cloned with a custom function instead of `React.cloneElement`.\n' + + "The props of this element may help locate this element: { children: 'Free!', [key]: [Getter] }", + {withoutStack: true}, + ], + "TypeError: Cannot read properties of undefined (reading 'stack')", + ]); + }); }); From 9433fe357aa5df277f0e509c1b8cf975afe9ec9a Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Wed, 13 Aug 2025 08:48:04 +0200 Subject: [PATCH 2/2] Fail tests if unasserted console calls contain `undefined` (#34191) --- .../__tests__/ReactInternalTestUtils-test.js | 23 +++++++++++++++++++ packages/internal-test-utils/consoleMock.js | 5 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/internal-test-utils/__tests__/ReactInternalTestUtils-test.js b/packages/internal-test-utils/__tests__/ReactInternalTestUtils-test.js index 3c10125186832..b24741477866b 100644 --- a/packages/internal-test-utils/__tests__/ReactInternalTestUtils-test.js +++ b/packages/internal-test-utils/__tests__/ReactInternalTestUtils-test.js @@ -2169,6 +2169,29 @@ describe('ReactInternalTestUtils console assertions', () => { + Bye in div (at **)" `); }); + + // @gate __DEV__ + it('fails if last received error containing "undefined" is not included', () => { + const message = expectToThrowFailure(() => { + console.error('Hi'); + console.error( + "TypeError: Cannot read properties of undefined (reading 'stack')\n" + + ' in Foo (at **)' + ); + assertConsoleErrorDev([['Hi', {withoutStack: true}]]); + }); + expect(message).toMatchInlineSnapshot(` + "assertConsoleErrorDev(expected) + + Unexpected error(s) recorded. + + - Expected errors + + Received errors + + Hi + + TypeError: Cannot read properties of undefined (reading 'stack') in Foo (at **)" + `); + }); // @gate __DEV__ it('fails if only error does not contain a stack', () => { const message = expectToThrowFailure(() => { diff --git a/packages/internal-test-utils/consoleMock.js b/packages/internal-test-utils/consoleMock.js index 989bb82f22edd..743519590e37e 100644 --- a/packages/internal-test-utils/consoleMock.js +++ b/packages/internal-test-utils/consoleMock.js @@ -382,8 +382,9 @@ export function createLogAssertion( // Main logic to check if log is expected, with the component stack. if ( - normalizedMessage === expectedMessage || - normalizedMessage.includes(expectedMessage) + typeof expectedMessage === 'string' && + (normalizedMessage === expectedMessage || + normalizedMessage.includes(expectedMessage)) ) { if (isLikelyAComponentStack(normalizedMessage)) { if (expectedWithoutStack === true) {