Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[assert helpers] react-dom (pt2) #31902

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
19 changes: 11 additions & 8 deletions packages/react-dom/src/__tests__/ReactDOMComponentTree-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ describe('ReactDOMComponentTree', () => {
let ReactDOMClient;
let act;
let container;
let assertConsoleErrorDev;

beforeEach(() => {
React = require('react');
ReactDOMClient = require('react-dom/client');
act = require('internal-test-utils').act;
assertConsoleErrorDev =
require('internal-test-utils').assertConsoleErrorDev;

container = document.createElement('div');
document.body.appendChild(container);
Expand Down Expand Up @@ -190,18 +193,18 @@ describe('ReactDOMComponentTree', () => {
root.render(<Controlled />);
});

await expect(
async () =>
await act(() => {
simulateInput(inputRef.current, finishValue);
}),
).toErrorDev(
await act(() => {
simulateInput(inputRef.current, finishValue);
});
assertConsoleErrorDev([
'A component is changing an uncontrolled input to be controlled. ' +
'This is likely caused by the value changing from undefined to ' +
'a defined value, which should not happen. ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: ' +
'https://react.dev/link/controlled-components',
);
'https://react.dev/link/controlled-components\n' +
' in input (at **)\n' +
' in Controlled (at **)',
]);
});
});
31 changes: 17 additions & 14 deletions packages/react-dom/src/__tests__/ReactDOMFiber-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -746,8 +746,13 @@ describe('ReactDOMFiber', () => {
root.render(<Parent />);
});
assertConsoleErrorDev([
'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
'Parent uses the legacy childContextTypes API which will soon be removed. ' +
'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
' in Parent (at **)',
'Component uses the legacy contextTypes API which will soon be removed. ' +
'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
(gate('enableOwnerStacks') ? '' : ' in Component (at **)\n') +
' in Parent (at **)',
]);
expect(container.innerHTML).toBe('');
expect(portalContainer.innerHTML).toBe('<div>bar</div>');
Expand Down Expand Up @@ -957,15 +962,14 @@ describe('ReactDOMFiber', () => {
return <div onClick="woops" />;
}
}
expect(() => {
ReactDOM.flushSync(() => {
root.render(<Example />);
});
}).toErrorDev(
ReactDOM.flushSync(() => {
root.render(<Example />);
});
assertConsoleErrorDev([
'Expected `onClick` listener to be a function, instead got a value of `string` type.\n' +
' in div (at **)\n' +
' in Example (at **)',
);
]);
});

it('should warn with a special message for `false` event listeners', () => {
Expand All @@ -974,17 +978,16 @@ describe('ReactDOMFiber', () => {
return <div onClick={false} />;
}
}
expect(() => {
ReactDOM.flushSync(() => {
root.render(<Example />);
});
}).toErrorDev(
ReactDOM.flushSync(() => {
root.render(<Example />);
});
assertConsoleErrorDev([
'Expected `onClick` listener to be a function, instead got `false`.\n\n' +
'If you used to conditionally omit it with onClick={condition && value}, ' +
'pass onClick={condition ? value : undefined} instead.\n' +
' in div (at **)\n' +
' in Example (at **)',
);
]);
});

it('should not update event handlers until commit', async () => {
Expand Down
17 changes: 12 additions & 5 deletions packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ let waitForAll;
let waitFor;
let waitForMicrotasks;
let assertLog;
let assertConsoleErrorDev;

const setUntrackedInputValue = Object.getOwnPropertyDescriptor(
HTMLInputElement.prototype,
Expand All @@ -34,6 +35,8 @@ describe('ReactDOMFiberAsync', () => {
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
act = require('internal-test-utils').act;
assertConsoleErrorDev =
require('internal-test-utils').assertConsoleErrorDev;
Scheduler = require('scheduler');

const InternalTestUtils = require('internal-test-utils');
Expand Down Expand Up @@ -176,11 +179,15 @@ describe('ReactDOMFiberAsync', () => {
root.render(<Component />);
});
// Update
expect(() => {
ReactDOM.flushSync(() => {
root.render(<Component />);
});
}).toErrorDev('flushSync was called from inside a lifecycle method');
ReactDOM.flushSync(() => {
root.render(<Component />);
});
assertConsoleErrorDev([
'flushSync was called from inside a lifecycle method. ' +
'React cannot flush when React is already rendering. ' +
'Consider moving this call to a scheduler task or micro task.\n' +
' in Component (at **)',
]);
});

describe('concurrent mode', () => {
Expand Down
90 changes: 70 additions & 20 deletions packages/react-dom/src/__tests__/ReactDOMFizzForm-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ let useFormStatus;
let useOptimistic;
let useActionState;
let Scheduler;
let assertConsoleErrorDev;

describe('ReactDOMFizzForm', () => {
beforeEach(() => {
Expand All @@ -38,6 +39,8 @@ describe('ReactDOMFizzForm', () => {
useFormStatus = require('react-dom').useFormStatus;
useOptimistic = require('react').useOptimistic;
act = require('internal-test-utils').act;
assertConsoleErrorDev =
require('internal-test-utils').assertConsoleErrorDev;
container = document.createElement('div');
document.body.appendChild(container);
// TODO: Test the old api but it warns so needs warnings to be asserted.
Expand Down Expand Up @@ -195,12 +198,26 @@ describe('ReactDOMFizzForm', () => {
ReactDOMServer.renderToReadableStream(<App />),
);
await readIntoContainer(stream);
await expect(async () => {
await act(async () => {
ReactDOMClient.hydrateRoot(container, <App isClient={true} />);
});
}).toErrorDev(
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties.",
await act(async () => {
ReactDOMClient.hydrateRoot(container, <App isClient={true} />);
});
assertConsoleErrorDev(
[
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n" +
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
"- Date formatting in a user's locale which doesn't match the server.\n" +
'- External changing data without sending a snapshot of it along with the HTML.\n' +
'- Invalid HTML tag nesting.\n\n' +
'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n\n' +
'https://react.dev/link/hydration-mismatch\n\n' +
' <App isClient={true}>\n' +
' <form\n' +
'+ action="action"\n' +
'- action="function"\n' +
' >\n',
],
{withoutStack: true},
);
});
Expand Down Expand Up @@ -357,23 +374,56 @@ describe('ReactDOMFizzForm', () => {

// Specifying the extra form fields are a DEV error, but we expect it
// to eventually still be patched up after an update.
await expect(async () => {
const stream = await serverAct(() =>
ReactDOMServer.renderToReadableStream(<App />),
);
await readIntoContainer(stream);
}).toErrorDev([
'Cannot specify a encType or method for a form that specifies a function as the action.',
'Cannot specify a formTarget for a button that specifies a function as a formAction.',
const stream = await serverAct(() =>
ReactDOMServer.renderToReadableStream(<App />),
);
await readIntoContainer(stream);
assertConsoleErrorDev([
'Cannot specify a encType or method for a form that specifies a function as the action. ' +
'React provides those automatically. They will get overridden.\n' +
' in form (at **)\n' +
' in App (at **)',
'Cannot specify a formTarget for a button that specifies a function as a formAction. ' +
'The function will always be executed in the same window.\n' +
' in input (at **)\n' +
(gate('enableOwnerStacks') ? '' : ' in form (at **)\n') +
' in App (at **)',
]);
let root;
await expect(async () => {
await act(async () => {
root = ReactDOMClient.hydrateRoot(container, <App />);
});
}).toErrorDev(
await act(async () => {
root = ReactDOMClient.hydrateRoot(container, <App />);
});
assertConsoleErrorDev(
[
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties.",
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n" +
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
"- Date formatting in a user's locale which doesn't match the server.\n" +
'- External changing data without sending a snapshot of it along with the HTML.\n' +
'- Invalid HTML tag nesting.\n\n' +
'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n\n' +
'https://react.dev/link/hydration-mismatch\n\n' +
' <App>\n' +
' <form\n' +
' action={function action}\n' +
' ref={{current:null}}\n' +
'+ method="DELETE"\n' +
'- method={null}\n' +
' >\n' +
' <input\n' +
' type="submit"\n' +
' formAction={function action}\n' +
' ref={{current:null}}\n' +
'+ formTarget="elsewhere"\n' +
'- formTarget={null}\n' +
' >\n' +
' <button\n' +
' formAction={function action}\n' +
' ref={{current:null}}\n' +
'+ formEncType="text/plain"\n' +
'- formEncType={null}\n' +
' >\n',
],
{withoutStack: true},
);
Expand Down
Loading
Loading