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

Convert ReactIdentity-test.js to createRoot #28106

Merged
merged 4 commits into from
Jan 26, 2024
Merged
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
119 changes: 70 additions & 49 deletions packages/react-dom/src/__tests__/ReactIdentity-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,20 @@
'use strict';

let React;
let ReactDOM;
let ReactDOMClient;
let ReactTestUtils;
let act;

describe('ReactIdentity', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
ReactTestUtils = require('react-dom/test-utils');
act = require('internal-test-utils').act;
});

it('should allow key property to express identity', () => {
it('should allow key property to express identity', async () => {
let node;
const Component = props => (
<div ref={c => (node = c)}>
Expand All @@ -31,41 +33,48 @@ describe('ReactIdentity', () => {
);

const container = document.createElement('div');
ReactDOM.render(<Component />, container);
const root = ReactDOMClient.createRoot(container);
await act(async () => {
root.render(<Component />);
});
const origChildren = Array.from(node.childNodes);
ReactDOM.render(<Component swap={true} />, container);
await act(async () => {
root.render(<Component swap={true} />);
});
const newChildren = Array.from(node.childNodes);
expect(origChildren[0]).toBe(newChildren[1]);
expect(origChildren[1]).toBe(newChildren[0]);
});

it('should use composite identity', () => {
it('should use composite identity', async () => {
class Wrapper extends React.Component {
render() {
return <a>{this.props.children}</a>;
}
}

const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
let node1;
let node2;
ReactDOM.render(
<Wrapper key="wrap1">
<span ref={c => (node1 = c)} />
</Wrapper>,
container,
);
ReactDOM.render(
<Wrapper key="wrap2">
<span ref={c => (node2 = c)} />
</Wrapper>,
container,
);

await act(async () => {
root.render(
<Wrapper key="wrap1">
<span ref={c => (node1 = c)} />
</Wrapper>,
);
});
await act(async () => {
root.render(
<Wrapper key="wrap2">
<span ref={c => (node2 = c)} />
</Wrapper>,
);
});
expect(node1).not.toBe(node2);
});

function renderAComponentWithKeyIntoContainer(key, container) {
async function renderAComponentWithKeyIntoContainer(key, root) {
class Wrapper extends React.Component {
spanRef = React.createRef();
render() {
Expand All @@ -76,36 +85,41 @@ describe('ReactIdentity', () => {
);
}
}

const instance = ReactDOM.render(<Wrapper />, container);
const span = instance.spanRef.current;
const wrapperRef = React.createRef();
await act(async () => {
root.render(<Wrapper ref={wrapperRef} />);
});
const span = wrapperRef.current.spanRef.current;
expect(span).not.toBe(null);
}

it('should allow any character as a key, in a detached parent', () => {
it('should allow any character as a key, in a detached parent', async () => {
const detachedContainer = document.createElement('div');
renderAComponentWithKeyIntoContainer("<'WEIRD/&\\key'>", detachedContainer);
const root = ReactDOMClient.createRoot(detachedContainer);
await renderAComponentWithKeyIntoContainer("<'WEIRD/&\\key'>", root);
});

it('should allow any character as a key, in an attached parent', () => {
it('should allow any character as a key, in an attached parent', async () => {
// This test exists to protect against implementation details that
// incorrectly query escaped IDs using DOM tools like getElementById.
const attachedContainer = document.createElement('div');
const root = ReactDOMClient.createRoot(attachedContainer);
document.body.appendChild(attachedContainer);

renderAComponentWithKeyIntoContainer("<'WEIRD/&\\key'>", attachedContainer);
await renderAComponentWithKeyIntoContainer("<'WEIRD/&\\key'>", root);

document.body.removeChild(attachedContainer);
});

it('should not allow scripts in keys to execute', () => {
it('should not allow scripts in keys to execute', async () => {
const h4x0rKey =
'"><script>window[\'YOUVEBEENH4X0RED\']=true;</script><div id="';

const attachedContainer = document.createElement('div');
const root = ReactDOMClient.createRoot(attachedContainer);
document.body.appendChild(attachedContainer);

renderAComponentWithKeyIntoContainer(h4x0rKey, attachedContainer);
await renderAComponentWithKeyIntoContainer(h4x0rKey, root);

document.body.removeChild(attachedContainer);

Expand Down Expand Up @@ -209,7 +223,7 @@ describe('ReactIdentity', () => {
}).not.toThrow();
});

it('should retain key during updates in composite components', () => {
it('should retain key during updates in composite components', async () => {
class TestComponent extends React.Component {
render() {
return <div>{this.props.children}</div>;
Expand All @@ -236,16 +250,23 @@ describe('ReactIdentity', () => {
const instance0 = <span key="A" />;
const instance1 = <span key="B" />;

let wrapped = <TestContainer first={instance0} second={instance1} />;

wrapped = ReactDOM.render(wrapped, document.createElement('div'));
const div = ReactDOM.findDOMNode(wrapped);
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
const wrappedRef = React.createRef();
await act(async () => {
root.render(
<TestContainer first={instance0} second={instance1} ref={wrappedRef} />,
);
});
const div = container.firstChild;

const beforeA = div.childNodes[0];
const beforeB = div.childNodes[1];
wrapped.swap();
const afterA = div.childNodes[1];
const afterB = div.childNodes[0];
const beforeA = div.firstChild;
const beforeB = div.lastChild;
await act(async () => {
wrappedRef.current.swap();
});
const afterA = div.lastChild;
const afterB = div.firstChild;

expect(beforeA).toBe(afterA);
expect(beforeB).toBe(afterB);
Expand All @@ -264,7 +285,7 @@ describe('ReactIdentity', () => {
}).not.toThrow();
});

it('should throw if key is a Temporal-like object', () => {
it('should throw if key is a Temporal-like object', async () => {
class TemporalLike {
valueOf() {
// Throwing here is the behavior of ECMAScript "Temporal" date/time API.
Expand All @@ -277,15 +298,15 @@ describe('ReactIdentity', () => {
}

const el = document.createElement('div');
const test = () =>
ReactDOM.render(
<div>
<span key={new TemporalLike()} />
</div>,
el,
);
expect(() =>
expect(test).toThrowError(new TypeError('prod message')),
const root = ReactDOMClient.createRoot(el);
await expect(() =>
expect(() => {
root.render(
<div>
<span key={new TemporalLike()} />
</div>,
);
}).toThrowError(new TypeError('prod message')),
).toErrorDev(
'The provided key is an unsupported type TemporalLike.' +
' This value must be coerced to a string before using it here.',
Expand Down