Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
655 changes: 12 additions & 643 deletions packages/internal-test-utils/__tests__/ReactInternalTestUtils-test.js

Large diffs are not rendered by default.

128 changes: 3 additions & 125 deletions packages/internal-test-utils/consoleMock.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,26 +290,11 @@ export function createLogAssertion(
}
}

const withoutStack = options.withoutStack;

if (consoleMethod === 'log' && withoutStack !== undefined) {
throwFormattedError(
`Do not pass withoutStack to assertConsoleLogDev, console.log does not have component stacks.`,
);
} else if (withoutStack !== undefined && withoutStack !== true) {
throwFormattedError(
`The second argument must be {withoutStack: true}.` +
`\n\nInstead received ${JSON.stringify(options)}.`,
);
}

const observedLogs = clearObservedErrors();
const receivedLogs = [];
const missingExpectedLogs = Array.from(expectedMessages);

const unexpectedLogs = [];
const unexpectedMissingComponentStack = [];
const unexpectedIncludingComponentStack = [];
const unexpectedMissingErrorStack = [];
const unexpectedIncludingErrorStack = [];
const logsMismatchingFormat = [];
Expand All @@ -334,72 +319,12 @@ export function createLogAssertion(
}

let expectedMessage;
let expectedWithoutStack;
const expectedMessageOrArray = expectedMessages[index];
if (
expectedMessageOrArray != null &&
Array.isArray(expectedMessageOrArray)
) {
// Should be in the local form assert([['log', {withoutStack: true}]])

// Some validations for common mistakes.
if (expectedMessageOrArray.length === 1) {
throwFormattedError(
`Did you forget to remove the array around the log?` +
`\n\nThe expected message for ${matcherName}() must be a string or an array of length 2, but there's only one item in the array. If this is intentional, remove the extra array.`,
);
} else if (expectedMessageOrArray.length !== 2) {
throwFormattedError(
`The expected message for ${matcherName}() must be a string or an array of length 2. ` +
`Instead received ${expectedMessageOrArray}.`,
);
} else if (consoleMethod === 'log') {
// We don't expect any console.log calls to have a stack.
throwFormattedError(
`Do not pass withoutStack to assertConsoleLogDev logs, console.log does not have component stacks.`,
);
}

// Format is correct, check the values.
const currentExpectedMessage = expectedMessageOrArray[0];
const currentExpectedOptions = expectedMessageOrArray[1];
if (
typeof currentExpectedMessage !== 'string' ||
typeof currentExpectedOptions !== 'object' ||
currentExpectedOptions.withoutStack !== true
) {
throwFormattedError(
`Log entries that are arrays must be of the form [string, {withoutStack: true}]` +
`\n\nInstead received [${typeof currentExpectedMessage}, ${JSON.stringify(
currentExpectedOptions,
)}].`,
);
}

expectedMessage = normalizeExpectedMessage(currentExpectedMessage);
expectedWithoutStack = expectedMessageOrArray[1].withoutStack;
} else if (typeof expectedMessageOrArray === 'string') {
if (typeof expectedMessageOrArray === 'string') {
expectedMessage = normalizeExpectedMessage(expectedMessageOrArray);
// withoutStack: inherit from global option - simplify when withoutStack is removed.
if (consoleMethod === 'log') {
expectedWithoutStack = true;
} else {
expectedWithoutStack = withoutStack;
}
} else if (
typeof expectedMessageOrArray === 'object' &&
expectedMessageOrArray != null &&
expectedMessageOrArray.withoutStack != null
) {
// Special case for common case of a wrong withoutStack value.
throwFormattedError(
`Did you forget to wrap a log with withoutStack in an array?` +
`\n\nThe expected message for ${matcherName}() must be a string or an array of length 2.` +
`\n\nInstead received ${JSON.stringify(expectedMessageOrArray)}.`,
);
} else if (expectedMessageOrArray != null) {
throwFormattedError(
`The expected message for ${matcherName}() must be a string or an array of length 2. ` +
`The expected message for ${matcherName}() must be a string. ` +
`Instead received ${JSON.stringify(expectedMessageOrArray)}.`,
);
}
Expand Down Expand Up @@ -499,18 +424,6 @@ export function createLogAssertion(
}

if (matchesExpectedMessage) {
// withoutStack: Check for unexpected/missing component stacks.
// These checks can be simplified when withoutStack is removed.
if (isLikelyAComponentStack(normalizedMessage)) {
if (expectedWithoutStack === true && !hasErrorStack) {
// Only report unexpected component stack if it's not an error stack
// (error stacks look like component stacks after normalization)
unexpectedIncludingComponentStack.push(normalizedMessage);
}
} else if (expectedWithoutStack !== true && !expectsErrorStack) {
unexpectedMissingComponentStack.push(normalizedMessage);
}

// Check for unexpected/missing error stacks
if (hasErrorStack && !expectsErrorStack) {
// Error stack is present but \n in <stack> was not in the expected message
Expand Down Expand Up @@ -538,12 +451,7 @@ export function createLogAssertion(
function printDiff() {
return `${diff(
expectedMessages
.map(messageOrTuple => {
const message = Array.isArray(messageOrTuple)
? messageOrTuple[0]
: messageOrTuple;
return message.replace('\n', ' ');
})
.map(message => message.replace('\n', ' '))
.join('\n'),
receivedLogs.map(message => message.replace('\n', ' ')).join('\n'),
{
Expand Down Expand Up @@ -582,36 +490,6 @@ export function createLogAssertion(
);
}

// Any logs that include a component stack but shouldn't.
if (unexpectedIncludingComponentStack.length > 0) {
throwFormattedError(
`${unexpectedIncludingComponentStack
.map(
stack =>
`Unexpected component stack for:\n ${printReceived(stack)}`,
)
.join(
'\n\n',
)}\n\nIf this ${logName()} should include a component stack, remove {withoutStack: true} from this ${logName()}.` +
`\nIf all ${logName()}s should include the component stack, you may need to remove {withoutStack: true} from the ${matcherName} call.`,
);
}

// Any logs that are missing a component stack without withoutStack.
if (unexpectedMissingComponentStack.length > 0) {
throwFormattedError(
`${unexpectedMissingComponentStack
.map(
stack =>
`Missing component stack for:\n ${printReceived(stack)}`,
)
.join(
'\n\n',
)}\n\nIf this ${logName()} should omit a component stack, pass [log, {withoutStack: true}].` +
`\nIf all ${logName()}s should omit the component stack, add {withoutStack: true} to the ${matcherName} call.`,
);
}

// Any logs that include an error stack trace but \n in <stack> wasn't expected.
if (unexpectedIncludingErrorStack.length > 0) {
throwFormattedError(
Expand Down
146 changes: 58 additions & 88 deletions packages/react-client/src/__tests__/ReactFlight-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1514,16 +1514,13 @@ describe('ReactFlight', () => {
},
};
const transport = ReactNoopFlightServer.render(<input value={obj} />);
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' +
' <input value={{toJSON: ...}}>\n' +
' ^^^^^^^^^^^^^^^',
],
{withoutStack: true},
);
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' +
' <input value={{toJSON: ...}}>\n' +
' ^^^^^^^^^^^^^^^',
]);

ReactNoopFlightClient.read(transport);
assertConsoleErrorDev([
Expand All @@ -1545,14 +1542,11 @@ describe('ReactFlight', () => {
const transport = ReactNoopFlightServer.render(
<div>Womp womp: {new MyError('spaghetti')}</div>,
);
assertConsoleErrorDev(
[
'Error objects cannot be rendered as text children. Try formatting it using toString().\n' +
' <div>Womp womp: {Error}</div>\n' +
' ^^^^^^^',
],
{withoutStack: true},
);
assertConsoleErrorDev([
'Error objects cannot be rendered as text children. Try formatting it using toString().\n' +
' <div>Womp womp: {Error}</div>\n' +
' ^^^^^^^',
]);

ReactNoopFlightClient.read(transport);
assertConsoleErrorDev([
Expand All @@ -1565,15 +1559,12 @@ describe('ReactFlight', () => {

it('should warn in DEV if a special object is passed to a host component', () => {
const transport = ReactNoopFlightServer.render(<input value={Math} />);
assertConsoleErrorDev(
[
'Only plain objects can be passed to Client Components from Server Components. ' +
'Math objects are not supported.\n' +
' <input value={Math}>\n' +
' ^^^^^^',
],
{withoutStack: true},
);
assertConsoleErrorDev([
'Only plain objects can be passed to Client Components from Server Components. ' +
'Math objects are not supported.\n' +
' <input value={Math}>\n' +
' ^^^^^^',
]);

ReactNoopFlightClient.read(transport);
assertConsoleErrorDev([
Expand All @@ -1589,15 +1580,12 @@ describe('ReactFlight', () => {
const transport = ReactNoopFlightServer.render(
<input value={{[Symbol.iterator]: {}}} />,
);
assertConsoleErrorDev(
[
'Only plain objects can be passed to Client Components from Server Components. ' +
'Objects with symbol properties like Symbol.iterator are not supported.\n' +
' <input value={{}}>\n' +
' ^^^^',
],
{withoutStack: true},
);
assertConsoleErrorDev([
'Only plain objects can be passed to Client Components from Server Components. ' +
'Objects with symbol properties like Symbol.iterator are not supported.\n' +
' <input value={{}}>\n' +
' ^^^^',
]);

ReactNoopFlightClient.read(transport);
assertConsoleErrorDev([
Expand All @@ -1620,16 +1608,13 @@ describe('ReactFlight', () => {
}
const Client = clientReference(ClientImpl);
const transport = ReactNoopFlightServer.render(<Client value={obj} />);
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},
);
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' +
' ^^^^^^^^^^^^^^^',
]);

ReactNoopFlightClient.read(transport);
assertConsoleErrorDev([
Expand All @@ -1655,16 +1640,13 @@ describe('ReactFlight', () => {
const transport = ReactNoopFlightServer.render(
<Client>Current date: {obj}</Client>,
);
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},
);
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' +
' ^^^^^^^^^^^^^^^',
]);

ReactNoopFlightClient.read(transport);
assertConsoleErrorDev([
Expand All @@ -1683,15 +1665,12 @@ describe('ReactFlight', () => {
}
const Client = clientReference(ClientImpl);
const transport = ReactNoopFlightServer.render(<Client value={Math} />);
assertConsoleErrorDev(
[
'Only plain objects can be passed to Client Components from Server Components. ' +
'Math objects are not supported.\n' +
' <... value={Math}>\n' +
' ^^^^^^',
],
{withoutStack: true},
);
assertConsoleErrorDev([
'Only plain objects can be passed to Client Components from Server Components. ' +
'Math objects are not supported.\n' +
' <... value={Math}>\n' +
' ^^^^^^',
]);

ReactNoopFlightClient.read(transport);
assertConsoleErrorDev([
Expand All @@ -1712,15 +1691,12 @@ describe('ReactFlight', () => {
const transport = ReactNoopFlightServer.render(
<Client value={{[Symbol.iterator]: {}}} />,
);
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},
);
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' +
' ^^^^',
]);

ReactNoopFlightClient.read(transport);

Expand All @@ -1744,13 +1720,10 @@ describe('ReactFlight', () => {
ReactNoopFlightClient.read(transport);

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' +
' ^^^^',
'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' +
Expand All @@ -1769,13 +1742,10 @@ describe('ReactFlight', () => {
);
ReactNoopFlightClient.read(transport);
assertConsoleErrorDev([
[
'Only plain objects can be passed to Client Components from Server Components. ' +
'Math objects are not supported.\n' +
' [..., Math, <h1/>]\n' +
' ^^^^',
{withoutStack: true},
],
'Only plain objects can be passed to Client Components from Server Components. ' +
'Math objects are not supported.\n' +
' [..., Math, <h1/>]\n' +
' ^^^^',
'Only plain objects can be passed to Client Components from Server Components. ' +
'Math objects are not supported.\n' +
' [..., Math, <h1/>]\n' +
Expand Down
Loading
Loading