diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js index b2489705394a1..980dbbf0e1247 100644 --- a/packages/react-client/src/__tests__/ReactFlight-test.js +++ b/packages/react-client/src/__tests__/ReactFlight-test.js @@ -1467,13 +1467,12 @@ describe('ReactFlight', () => { const transport = ReactNoopFlightServer.render(); - await expect(async () => { - await act(() => { - startTransition(() => { - ReactNoop.render(ReactNoopFlightClient.read(transport)); - }); + await act(() => { + startTransition(() => { + ReactNoop.render(ReactNoopFlightClient.read(transport)); }); - }).toErrorDev( + }); + assertConsoleErrorDev([ 'Each child in a list should have a unique "key" prop.\n' + '\n' + 'Check the render method of `Component`. See https://react.dev/link/warning-keys for more information.\n' + @@ -1483,7 +1482,7 @@ describe('ReactFlight', () => { ? '' : ' in Indirection (at **)\n') + ' in App (at **)', - ); + ]); }); it('should trigger the inner most error boundary inside a Client Component', async () => { @@ -1541,17 +1540,47 @@ describe('ReactFlight', () => { return 123; }, }; - expect(() => { - const transport = ReactNoopFlightServer.render(); - ReactNoopFlightClient.read(transport); - }).toErrorDev( - 'Only plain objects can be passed to Client Components from Server Components. ' + - 'Objects with toJSON methods are not supported. ' + - 'Convert it manually to a simple value before passing it to props.\n' + - ' \n' + - ' ^^^^^^^^^^^^^^^', - {withoutStack: true}, - ); + const transport = ReactNoopFlightServer.render(); + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev( + [ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with toJSON methods are not supported. ' + + 'Convert it manually to a simple value before passing it to props.\n' + + ' \n' + + ' ^^^^^^^^^^^^^^^', + ], + {withoutStack: true}, + ); + } + + ReactNoopFlightClient.read(transport); + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev([ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with toJSON methods are not supported. ' + + 'Convert it manually to a simple value before passing it to props.\n' + + ' \n' + + ' ^^^^^^^^^^^^^^^\n' + + ' at ()', + ]); + } else { + assertConsoleErrorDev( + [ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with toJSON methods are not supported. ' + + 'Convert it manually to a simple value before passing it to props.\n' + + ' \n' + + ' ^^^^^^^^^^^^^^^', + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with toJSON methods are not supported. ' + + 'Convert it manually to a simple value before passing it to props.\n' + + ' \n' + + ' ^^^^^^^^^^^^^^^', + ], + {withoutStack: true}, + ); + } }); it('should warn in DEV if a toJSON instance is passed to a host component child', () => { @@ -1560,43 +1589,123 @@ describe('ReactFlight', () => { return 123; } } - expect(() => { - const transport = ReactNoopFlightServer.render( -
Womp womp: {new MyError('spaghetti')}
, + const transport = ReactNoopFlightServer.render( +
Womp womp: {new MyError('spaghetti')}
, + ); + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev( + [ + 'Error objects cannot be rendered as text children. Try formatting it using toString().\n' + + '
Womp womp: {Error}
\n' + + ' ^^^^^^^', + ], + {withoutStack: true}, ); - ReactNoopFlightClient.read(transport); - }).toErrorDev( - 'Error objects cannot be rendered as text children. Try formatting it using toString().\n' + - '
Womp womp: {Error}
\n' + - ' ^^^^^^^', - {withoutStack: true}, - ); + } + + ReactNoopFlightClient.read(transport); + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev([ + 'Error objects cannot be rendered as text children. Try formatting it using toString().\n' + + '
Womp womp: {Error}
\n' + + ' ^^^^^^^\n' + + ' at ()', + ]); + } else { + assertConsoleErrorDev( + [ + 'Error objects cannot be rendered as text children. Try formatting it using toString().\n' + + '
Womp womp: {Error}
\n' + + ' ^^^^^^^', + 'Error objects cannot be rendered as text children. Try formatting it using toString().\n' + + '
Womp womp: {Error}
\n' + + ' ^^^^^^^', + ], + {withoutStack: true}, + ); + } }); it('should warn in DEV if a special object is passed to a host component', () => { - expect(() => { - const transport = ReactNoopFlightServer.render(); - ReactNoopFlightClient.read(transport); - }).toErrorDev( - 'Only plain objects can be passed to Client Components from Server Components. ' + - 'Math objects are not supported.\n' + - ' \n' + - ' ^^^^^^', - {withoutStack: true}, - ); + const transport = ReactNoopFlightServer.render(); + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev( + [ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Math objects are not supported.\n' + + ' \n' + + ' ^^^^^^', + ], + {withoutStack: true}, + ); + } + + ReactNoopFlightClient.read(transport); + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev([ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Math objects are not supported.\n' + + ' \n' + + ' ^^^^^^\n' + + ' at ()', + ]); + } else { + assertConsoleErrorDev( + [ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Math objects are not supported.\n' + + ' \n' + + ' ^^^^^^', + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Math objects are not supported.\n' + + ' \n' + + ' ^^^^^^', + ], + {withoutStack: true}, + ); + } }); it('should warn in DEV if an object with symbols is passed to a host component', () => { - expect(() => { - const transport = ReactNoopFlightServer.render( - , + const transport = ReactNoopFlightServer.render( + , + ); + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev( + [ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with symbol properties like Symbol.iterator are not supported.\n' + + ' \n' + + ' ^^^^', + ], + {withoutStack: true}, ); - ReactNoopFlightClient.read(transport); - }).toErrorDev( - 'Only plain objects can be passed to Client Components from Server Components. ' + - 'Objects with symbol properties like Symbol.iterator are not supported.', - {withoutStack: true}, - ); + } + + ReactNoopFlightClient.read(transport); + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev([ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with symbol properties like Symbol.iterator are not supported.\n' + + ' \n' + + ' ^^^^\n' + + ' at ()', + ]); + } else { + assertConsoleErrorDev( + [ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with symbol properties like Symbol.iterator are not supported.\n' + + ' \n' + + ' ^^^^', + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with symbol properties like Symbol.iterator are not supported.\n' + + ' \n' + + ' ^^^^', + ], + {withoutStack: true}, + ); + } }); it('should warn in DEV if a toJSON instance is passed to a Client Component', () => { @@ -1609,14 +1718,47 @@ describe('ReactFlight', () => { return
{value}
; } const Client = clientReference(ClientImpl); - expect(() => { - const transport = ReactNoopFlightServer.render(); - ReactNoopFlightClient.read(transport); - }).toErrorDev( - 'Only plain objects can be passed to Client Components from Server Components. ' + - 'Objects with toJSON methods are not supported.', - {withoutStack: true}, - ); + const transport = ReactNoopFlightServer.render(); + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev( + [ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with toJSON methods are not supported. ' + + 'Convert it manually to a simple value before passing it to props.\n' + + ' <... value={{toJSON: ...}}>\n' + + ' ^^^^^^^^^^^^^^^', + ], + {withoutStack: true}, + ); + } + + ReactNoopFlightClient.read(transport); + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev([ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with toJSON methods are not supported. ' + + 'Convert it manually to a simple value before passing it to props.\n' + + ' <... value={{toJSON: ...}}>\n' + + ' ^^^^^^^^^^^^^^^\n' + + ' at ()', + ]); + } else { + assertConsoleErrorDev( + [ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with toJSON methods are not supported. ' + + 'Convert it manually to a simple value before passing it to props.\n' + + ' <... value={{toJSON: ...}}>\n' + + ' ^^^^^^^^^^^^^^^', + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with toJSON methods are not supported. ' + + 'Convert it manually to a simple value before passing it to props.\n' + + ' <... value={{toJSON: ...}}>\n' + + ' ^^^^^^^^^^^^^^^', + ], + {withoutStack: true}, + ); + } }); it('should warn in DEV if a toJSON instance is passed to a Client Component child', () => { @@ -1629,19 +1771,49 @@ describe('ReactFlight', () => { return
{children}
; } const Client = clientReference(ClientImpl); - expect(() => { - const transport = ReactNoopFlightServer.render( - Current date: {obj}, + const transport = ReactNoopFlightServer.render( + Current date: {obj}, + ); + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev( + [ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with toJSON methods are not supported. ' + + 'Convert it manually to a simple value before passing it to props.\n' + + ' <>Current date: {{toJSON: ...}}\n' + + ' ^^^^^^^^^^^^^^^', + ], + {withoutStack: true}, ); - ReactNoopFlightClient.read(transport); - }).toErrorDev( - 'Only plain objects can be passed to Client Components from Server Components. ' + - 'Objects with toJSON methods are not supported. ' + - 'Convert it manually to a simple value before passing it to props.\n' + - ' <>Current date: {{toJSON: ...}}\n' + - ' ^^^^^^^^^^^^^^^', - {withoutStack: true}, - ); + } + + ReactNoopFlightClient.read(transport); + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev([ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with toJSON methods are not supported. ' + + 'Convert it manually to a simple value before passing it to props.\n' + + ' <>Current date: {{toJSON: ...}}\n' + + ' ^^^^^^^^^^^^^^^\n' + + ' at ()', + ]); + } else { + assertConsoleErrorDev( + [ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with toJSON methods are not supported. ' + + 'Convert it manually to a simple value before passing it to props.\n' + + ' <>Current date: {{toJSON: ...}}\n' + + ' ^^^^^^^^^^^^^^^', + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with toJSON methods are not supported. ' + + 'Convert it manually to a simple value before passing it to props.\n' + + ' <>Current date: {{toJSON: ...}}\n' + + ' ^^^^^^^^^^^^^^^', + ], + {withoutStack: true}, + ); + } }); it('should warn in DEV if a special object is passed to a Client Component', () => { @@ -1649,16 +1821,44 @@ describe('ReactFlight', () => { return
{value}
; } const Client = clientReference(ClientImpl); - expect(() => { - const transport = ReactNoopFlightServer.render(); - ReactNoopFlightClient.read(transport); - }).toErrorDev( - 'Only plain objects can be passed to Client Components from Server Components. ' + - 'Math objects are not supported.\n' + - ' <... value={Math}>\n' + - ' ^^^^^^', - {withoutStack: true}, - ); + const transport = ReactNoopFlightServer.render(); + + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev( + [ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Math objects are not supported.\n' + + ' <... value={Math}>\n' + + ' ^^^^^^', + ], + {withoutStack: true}, + ); + } + + ReactNoopFlightClient.read(transport); + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev([ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Math objects are not supported.\n' + + ' <... value={Math}>\n' + + ' ^^^^^^\n' + + ' at ()', + ]); + } else { + assertConsoleErrorDev( + [ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Math objects are not supported.\n' + + ' <... value={Math}>\n' + + ' ^^^^^^', + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Math objects are not supported.\n' + + ' <... value={Math}>\n' + + ' ^^^^^^', + ], + {withoutStack: true}, + ); + } }); it('should warn in DEV if an object with symbols is passed to a Client Component', () => { @@ -1666,16 +1866,46 @@ describe('ReactFlight', () => { return
{value}
; } const Client = clientReference(ClientImpl); - expect(() => { - const transport = ReactNoopFlightServer.render( - , + assertConsoleErrorDev([]); + const transport = ReactNoopFlightServer.render( + , + ); + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev( + [ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with symbol properties like Symbol.iterator are not supported.\n' + + ' <... value={{}}>\n' + + ' ^^^^', + ], + {withoutStack: true}, ); - ReactNoopFlightClient.read(transport); - }).toErrorDev( - 'Only plain objects can be passed to Client Components from Server Components. ' + - 'Objects with symbol properties like Symbol.iterator are not supported.', - {withoutStack: true}, - ); + } + + ReactNoopFlightClient.read(transport); + + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev([ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with symbol properties like Symbol.iterator are not supported.\n' + + ' <... value={{}}>\n' + + ' ^^^^\n', + ]); + } else { + assertConsoleErrorDev( + [ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with symbol properties like Symbol.iterator are not supported.\n' + + ' <... value={{}}>\n' + + ' ^^^^', + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with symbol properties like Symbol.iterator are not supported.\n' + + ' <... value={{}}>\n' + + ' ^^^^', + ], + {withoutStack: true}, + ); + } }); it('should warn in DEV if a special object is passed to a nested object in Client Component', () => { @@ -1683,18 +1913,41 @@ describe('ReactFlight', () => { return
{value}
; } const Client = clientReference(ClientImpl); - expect(() => { - const transport = ReactNoopFlightServer.render( - hi}} />, - ); - ReactNoopFlightClient.read(transport); - }).toErrorDev( - 'Only plain objects can be passed to Client Components from Server Components. ' + - 'Math objects are not supported.\n' + - ' {hello: Math, title:

}\n' + - ' ^^^^', - {withoutStack: true}, + const transport = ReactNoopFlightServer.render( + , ); + ReactNoopFlightClient.read(transport); + + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev([ + [ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with symbol properties like Symbol.iterator are not supported.\n' + + ' <... value={{}}>\n' + + ' ^^^^', + {withoutStack: true}, + ], + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with symbol properties like Symbol.iterator are not supported.\n' + + ' <... value={{}}>\n' + + ' ^^^^\n' + + ' at ()', + ]); + } else { + assertConsoleErrorDev( + [ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with symbol properties like Symbol.iterator are not supported.\n' + + ' <... value={{}}>\n' + + ' ^^^^', + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Objects with symbol properties like Symbol.iterator are not supported.\n' + + ' <... value={{}}>\n' + + ' ^^^^', + ], + {withoutStack: true}, + ); + } }); it('should warn in DEV if a special object is passed to a nested array in Client Component', () => { @@ -1702,20 +1955,40 @@ describe('ReactFlight', () => { return
{value}
; } const Client = clientReference(ClientImpl); - expect(() => { - const transport = ReactNoopFlightServer.render( - hi

]} - />, - ); - ReactNoopFlightClient.read(transport); - }).toErrorDev( - 'Only plain objects can be passed to Client Components from Server Components. ' + - 'Math objects are not supported.\n' + - ' [..., Math,

]\n' + - ' ^^^^', - {withoutStack: true}, + const transport = ReactNoopFlightServer.render( + hi

]} />, ); + ReactNoopFlightClient.read(transport); + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev([ + [ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Math objects are not supported.\n' + + ' [..., Math,

]\n' + + ' ^^^^', + {withoutStack: true}, + ], + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Math objects are not supported.\n' + + ' [..., Math,

]\n' + + ' ^^^^\n' + + ' at ()', + ]); + } else { + assertConsoleErrorDev( + [ + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Math objects are not supported.\n' + + ' [..., Math,

]\n' + + ' ^^^^', + 'Only plain objects can be passed to Client Components from Server Components. ' + + 'Math objects are not supported.\n' + + ' [..., Math,

]\n' + + ' ^^^^', + ], + {withoutStack: true}, + ); + } }); it('should NOT warn in DEV for key getters', () => { @@ -1729,63 +2002,100 @@ describe('ReactFlight', () => { key: "this has a key but parent doesn't", }); } - expect(() => { - // While we're on the server we need to have the Server version active to track component stacks. - jest.resetModules(); - jest.mock('react', () => ReactServer); - const transport = ReactNoopFlightServer.render( - ReactServer.createElement( - 'div', - null, - Array(6).fill(ReactServer.createElement(NoKey)), - ), - ); - jest.resetModules(); - jest.mock('react', () => React); - ReactNoopFlightClient.read(transport); - }).toErrorDev('Each child in a list should have a unique "key" prop.'); + // While we're on the server we need to have the Server version active to track component stacks. + jest.resetModules(); + jest.mock('react', () => ReactServer); + const transport = ReactNoopFlightServer.render( + ReactServer.createElement( + 'div', + null, + Array(6).fill(ReactServer.createElement(NoKey)), + ), + ); + jest.resetModules(); + jest.mock('react', () => React); + ReactNoopFlightClient.read(transport); + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev([ + 'Each child in a list should have a unique "key" prop. ' + + 'See https://react.dev/link/warning-keys for more information.\n' + + ' in NoKey (at **)', + 'Each child in a list should have a unique "key" prop. ' + + 'See https://react.dev/link/warning-keys for more information.\n' + + ' in NoKey (at **)', + ]); + } else { + assertConsoleErrorDev([ + 'Each child in a list should have a unique "key" prop.\n\n' + + 'Check the top-level render call using
. ' + + 'See https://react.dev/link/warning-keys for more information.\n' + + ' in NoKey (at **)', + ]); + } }); // @gate !__DEV__ || enableOwnerStacks it('should warn in DEV a child is missing keys on a fragment', () => { - expect(() => { - // While we're on the server we need to have the Server version active to track component stacks. - jest.resetModules(); - jest.mock('react', () => ReactServer); - const transport = ReactNoopFlightServer.render( - ReactServer.createElement( - 'div', - null, - Array(6).fill(ReactServer.createElement(ReactServer.Fragment)), - ), - ); - jest.resetModules(); - jest.mock('react', () => React); - ReactNoopFlightClient.read(transport); - }).toErrorDev('Each child in a list should have a unique "key" prop.'); + // While we're on the server we need to have the Server version active to track component stacks. + jest.resetModules(); + jest.mock('react', () => ReactServer); + const transport = ReactNoopFlightServer.render( + ReactServer.createElement( + 'div', + null, + Array(6).fill(ReactServer.createElement(ReactServer.Fragment)), + ), + ); + jest.resetModules(); + jest.mock('react', () => React); + ReactNoopFlightClient.read(transport); + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev([ + 'Each child in a list should have a unique "key" prop. ' + + 'See https://react.dev/link/warning-keys for more information.\n' + + ' in Fragment (at **)', + 'Each child in a list should have a unique "key" prop. ' + + 'See https://react.dev/link/warning-keys for more information.\n' + + ' in Fragment (at **)', + ]); + } else { + assertConsoleErrorDev([ + 'Each child in a list should have a unique "key" prop.\n\n' + + 'Check the top-level render call using
. ' + + 'See https://react.dev/link/warning-keys for more information.\n' + + ' in Fragment (at **)', + ]); + } }); it('should warn in DEV a child is missing keys in client component', async () => { function ParentClient({children}) { return children; } - const Parent = clientReference(ParentClient); - await expect(async () => { + + await act(async () => { + const Parent = clientReference(ParentClient); const transport = ReactNoopFlightServer.render( {Array(6).fill(
no key
)}
, ); ReactNoopFlightClient.read(transport); - await act(async () => { - ReactNoop.render(await ReactNoopFlightClient.read(transport)); - }); - }).toErrorDev( - gate(flags => flags.enableOwnerStacks) - ? 'Each child in a list should have a unique "key" prop.' + - '\n\nCheck the top-level render call using . ' + - 'See https://react.dev/link/warning-keys for more information.' - : 'Each child in a list should have a unique "key" prop. ' + - 'See https://react.dev/link/warning-keys for more information.', - ); + + ReactNoop.render(await ReactNoopFlightClient.read(transport)); + }); + if (gate(flags => flags.enableOwnerStacks)) { + assertConsoleErrorDev([ + 'Each child in a list should have a unique "key" prop.\n\n' + + 'Check the top-level render call using . ' + + 'See https://react.dev/link/warning-keys for more information.\n' + + ' in div (at **)', + ]); + } else { + assertConsoleErrorDev([ + 'Each child in a list should have a unique "key" prop. ' + + 'See https://react.dev/link/warning-keys for more information.\n' + + ' in div (at **)', + ]); + } }); it('should error if a class instance is passed to a host component', () => { @@ -3135,19 +3445,17 @@ describe('ReactFlight', () => { }, ); - let transport; - expect(() => { - // Reset the modules so that we get a new overridden console on top of the - // one installed by expect. This ensures that we still emit console.error - // calls. - jest.resetModules(); - jest.mock('react', () => require('react/react.react-server')); - ReactServer = require('react'); - ReactNoopFlightServer = require('react-noop-renderer/flight-server'); - transport = ReactNoopFlightServer.render({ - root: ReactServer.createElement(App), - }); - }).toErrorDev('err'); + // Reset the modules so that we get a new overridden console on top of the + // one installed by expect. This ensures that we still emit console.error + // calls. + jest.resetModules(); + jest.mock('react', () => require('react/react.react-server')); + ReactServer = require('react'); + ReactNoopFlightServer = require('react-noop-renderer/flight-server'); + const transport = ReactNoopFlightServer.render({ + root: ReactServer.createElement(App), + }); + assertConsoleErrorDev(['Error: err']); expect(mockConsoleLog).toHaveBeenCalledTimes(1); expect(mockConsoleLog.mock.calls[0][0]).toBe('hi');