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

Use createRoot in ReactEmptyComponent-test #28095

Merged
merged 3 commits into from
Jan 26, 2024
Merged
Changes from 1 commit
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
Next Next commit
Use createRoot in ReactEmptyComponent-test
  • Loading branch information
Jack Pope committed Jan 25, 2024
commit d9bddd027ace29b5e36a819bc14a3a5e11a27621
201 changes: 126 additions & 75 deletions packages/react-dom/src/__tests__/ReactEmptyComponent-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,28 @@

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

let log;
let container;

describe('ReactEmptyComponent', () => {
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;

log = jest.fn();

container = document.createElement('div');

TogglingComponent = class extends React.Component {
state = {component: this.props.firstComponent};

Expand All @@ -47,40 +54,44 @@ describe('ReactEmptyComponent', () => {

describe.each([null, undefined])('when %s', nullORUndefined => {
it('should not throw when rendering', () => {
class Component extends React.Component {
render() {
return nullORUndefined;
}
function EmptyComponent() {
return nullORUndefined;
}

expect(function () {
ReactTestUtils.renderIntoDocument(<Component />);
const root = ReactDOMClient.createRoot(container);

expect(() => {
ReactDOM.flushSync(() => {
root.render(<EmptyComponent />);
});
}).not.toThrowError();
});

it('should not produce child DOM nodes for nullish and false', () => {
class Component1 extends React.Component {
render() {
return nullORUndefined;
}
it('should not produce child DOM nodes for nullish and false', async () => {
function Component1() {
return nullORUndefined;
}

class Component2 extends React.Component {
render() {
return false;
}
function Component2() {
return false;
}

const container1 = document.createElement('div');
ReactDOM.render(<Component1 />, container1);
const root1 = ReactDOMClient.createRoot(container1);
await act(() => {
root1.render(<Component1 />);
});
expect(container1.children.length).toBe(0);

const container2 = document.createElement('div');
ReactDOM.render(<Component2 />, container2);
const root2 = ReactDOMClient.createRoot(container2);
await act(() => {
root2.render(<Component2 />);
});
expect(container2.children.length).toBe(0);
});

it('should be able to switch between rendering nullish and a normal tag', () => {
it('should be able to switch between rendering nullish and a normal tag', async () => {
const instance1 = (
<TogglingComponent
firstComponent={nullORUndefined}
Expand All @@ -94,8 +105,16 @@ describe('ReactEmptyComponent', () => {
/>
);

ReactTestUtils.renderIntoDocument(instance1);
ReactTestUtils.renderIntoDocument(instance2);
const container2 = document.createElement('div');
const root1 = ReactDOMClient.createRoot(container);
await act(() => {
root1.render(instance1);
});

const root2 = ReactDOMClient.createRoot(container2);
await act(() => {
root2.render(instance2);
});

expect(log).toHaveBeenCalledTimes(4);
expect(log).toHaveBeenNthCalledWith(1, null);
Expand All @@ -110,21 +129,24 @@ describe('ReactEmptyComponent', () => {
expect(log).toHaveBeenNthCalledWith(4, null);
});

it('should be able to switch in a list of children', () => {
it('should be able to switch in a list of children', async () => {
const instance1 = (
<TogglingComponent
firstComponent={nullORUndefined}
secondComponent={'div'}
/>
);

ReactTestUtils.renderIntoDocument(
<div>
{instance1}
{instance1}
{instance1}
</div>,
);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<div>
{instance1}
{instance1}
{instance1}
</div>,
);
});

expect(log).toHaveBeenCalledTimes(6);
expect(log).toHaveBeenNthCalledWith(1, null);
Expand Down Expand Up @@ -158,11 +180,19 @@ describe('ReactEmptyComponent', () => {
/>
);

expect(function () {
ReactTestUtils.renderIntoDocument(instance1);
const root1 = ReactDOMClient.createRoot(container);
expect(() => {
ReactDOM.flushSync(() => {
root1.render(instance1);
});
}).not.toThrow();
expect(function () {
ReactTestUtils.renderIntoDocument(instance2);

const container2 = document.createElement('div');
const root2 = ReactDOMClient.createRoot(container2);
expect(() => {
ReactDOM.flushSync(() => {
root2.render(instance2);
});
}).not.toThrow();

expect(log).toHaveBeenCalledTimes(4);
Expand All @@ -182,16 +212,12 @@ describe('ReactEmptyComponent', () => {
'should have findDOMNode return null when multiple layers of composite ' +
'components render to the same nullish placeholder',
() => {
class GrandChild extends React.Component {
render() {
return nullORUndefined;
}
function GrandChild() {
return nullORUndefined;
}

class Child extends React.Component {
render() {
return <GrandChild />;
}
function Child() {
return <GrandChild />;
}

const instance1 = (
Expand All @@ -201,11 +227,19 @@ describe('ReactEmptyComponent', () => {
<TogglingComponent firstComponent={Child} secondComponent={'div'} />
);

expect(function () {
ReactTestUtils.renderIntoDocument(instance1);
const root1 = ReactDOMClient.createRoot(container);
expect(() => {
ReactDOM.flushSync(() => {
root1.render(instance1);
});
}).not.toThrow();
expect(function () {
ReactTestUtils.renderIntoDocument(instance2);

const container2 = document.createElement('div');
const root2 = ReactDOMClient.createRoot(container2);
expect(() => {
ReactDOM.flushSync(() => {
root2.render(instance2);
});
}).not.toThrow();

expect(log).toHaveBeenCalledTimes(4);
Expand All @@ -222,8 +256,8 @@ describe('ReactEmptyComponent', () => {
},
);

it('works when switching components', () => {
let assertions = 0;
it('works when switching components', async () => {
let innerRef;

class Inner extends React.Component {
render() {
Expand All @@ -234,44 +268,51 @@ describe('ReactEmptyComponent', () => {
// Make sure the DOM node resolves properly even if we're replacing a
// `null` component
expect(ReactDOM.findDOMNode(this)).not.toBe(null);
assertions++;
}

componentWillUnmount() {
// Even though we're getting replaced by `null`, we haven't been
// replaced yet!
expect(ReactDOM.findDOMNode(this)).not.toBe(null);
assertions++;
}
}

class Wrapper extends React.Component {
render() {
return this.props.showInner ? <Inner /> : nullORUndefined;
}
function Wrapper({showInner}) {
innerRef = React.createRef(null);
return showInner ? <Inner ref={innerRef} /> : nullORUndefined;
}

const el = document.createElement('div');
let component;

// Render the <Inner /> component...
component = ReactDOM.render(<Wrapper showInner={true} />, el);
expect(ReactDOM.findDOMNode(component)).not.toBe(null);
const root = ReactDOMClient.createRoot(el);
await act(() => {
root.render(<Wrapper showInner={true} />);
});
expect(innerRef.current).not.toBe(null);

// Switch to null...
component = ReactDOM.render(<Wrapper showInner={false} />, el);
expect(ReactDOM.findDOMNode(component)).toBe(null);
await act(() => {
root.render(<Wrapper showInner={false} />);
});
expect(innerRef.current).toBe(null);

// ...then switch back.
component = ReactDOM.render(<Wrapper showInner={true} />, el);
expect(ReactDOM.findDOMNode(component)).not.toBe(null);
await act(() => {
root.render(<Wrapper showInner={true} />);
});
expect(innerRef.current).not.toBe(null);

expect(assertions).toBe(3);
expect.assertions(6);
});
rickhanlonii marked this conversation as resolved.
Show resolved Hide resolved

it('can render nullish at the top level', () => {
it('can render nullish at the top level', async () => {
const div = document.createElement('div');
ReactDOM.render(nullORUndefined, div);
const root = ReactDOMClient.createRoot(div);

await act(() => {
root.render(nullORUndefined);
});
expect(div.innerHTML).toBe('');
});

Expand Down Expand Up @@ -308,26 +349,30 @@ describe('ReactEmptyComponent', () => {
}
}

expect(function () {
ReactTestUtils.renderIntoDocument(<Parent />);
const root = ReactDOMClient.createRoot(container);
expect(() => {
ReactDOM.flushSync(() => {
root.render(<Parent />);
});
}).not.toThrow();
});

it('preserves the dom node during updates', () => {
class Empty extends React.Component {
render() {
return nullORUndefined;
}
it('preserves the dom node during updates', async () => {
function Empty() {
return nullORUndefined;
}

const container = document.createElement('div');

ReactDOM.render(<Empty />, container);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<Empty />);
});
const noscript1 = container.firstChild;
expect(noscript1).toBe(null);

// This update shouldn't create a DOM node
ReactDOM.render(<Empty />, container);
await act(() => {
root.render(<Empty />);
});
const noscript2 = container.firstChild;
expect(noscript2).toBe(null);
});
Expand All @@ -338,8 +383,11 @@ describe('ReactEmptyComponent', () => {
};
const EmptyForwardRef = React.forwardRef(Empty);

const root = ReactDOMClient.createRoot(container);
expect(() => {
ReactTestUtils.renderIntoDocument(<EmptyForwardRef />);
ReactDOM.flushSync(() => {
root.render(<EmptyForwardRef />);
});
}).not.toThrowError();
});

Expand All @@ -349,8 +397,11 @@ describe('ReactEmptyComponent', () => {
};
const EmptyMemo = React.memo(Empty);

const root = ReactDOMClient.createRoot(container);
expect(() => {
ReactTestUtils.renderIntoDocument(<EmptyMemo />);
ReactDOM.flushSync(() => {
root.render(<EmptyMemo />);
});
}).not.toThrowError();
});
});
Expand Down