Skip to content

Commit

Permalink
[assert helpers] react-dom (pt2)
Browse files Browse the repository at this point in the history
  • Loading branch information
rickhanlonii committed Jan 2, 2025
1 parent 94867f3 commit 19ae1ea
Show file tree
Hide file tree
Showing 7 changed files with 628 additions and 448 deletions.
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

0 comments on commit 19ae1ea

Please sign in to comment.