/*** Copyright (c) Meta Platforms, Inc. and affiliates.** This source code is licensed under the MIT license found in the* LICENSE file in the root directory of this source tree.** @emails react-core* @jest-environment node*/'use strict';
let React;
let ReactNoop;
let waitForAll;
describe('ReactFragment', () => {
beforeEach(function () {
jest.resetModules();
React = require('react');
ReactNoop = require('react-noop-renderer');
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
});it('should render a single child via noop renderer', async () => {
const element = (
<><span>foo</span>
</>
);
ReactNoop.render(element);
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(<span>foo</span>);
});it('should render zero children via noop renderer', async () => {const element = <React.Fragment />;
ReactNoop.render(element);
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(null);
});it('should render multiple children via noop renderer', async () => {const element = (<>hello <span>world</span></>);ReactNoop.render(element);
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(
<>hello <span>world</span></>,);});it('should render an iterable via noop renderer', async () => {const element = (<>{new Set([<span key="a">hi</span>, <span key="b">bye</span>])}</>
);ReactNoop.render(element);
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(
<><span>hi</span><span>bye</span></>,);});it('should preserve state of children with 1 level nesting', async function () {const ops = [];
class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition ? (
<Stateful key="a" />) : (<><Stateful key="a" /><div key="b">World</div></>);}ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
await waitForAll([]);
expect(ops).toEqual(['Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(
<><div>Hello</div><div>World</div></>,);ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});it('should preserve state between top-level fragments', async function () {const ops = [];
class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition ? (
<><Stateful /></>) : (<><Stateful /></>);}ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
await waitForAll([]);
expect(ops).toEqual(['Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});it('should preserve state of children nested at same level', async function () {const ops = [];
class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition ? (
<><><><Stateful key="a" /></></></>) : (<><><><div /><Stateful key="a" /></></></>);}ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
await waitForAll([]);
expect(ops).toEqual(['Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(
<><div /><div>Hello</div></>,);ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});it('should not preserve state in non-top-level fragment nesting', async function () {const ops = [];
class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition ? (
<><><Stateful key="a" /></></>) : (<><Stateful key="a" /></>);}ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});it('should not preserve state of children if nested 2 levels without siblings', async function () {const ops = [];
class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition ? (
<Stateful key="a" />) : (<><><Stateful key="a" /></></>);}ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});it('should not preserve state of children if nested 2 levels with siblings', async function () {const ops = [];
class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition ? (
<Stateful key="a" />) : (<><><Stateful key="a" /></><div /></>);}ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(
<><div>Hello</div><div /></>,);ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});it('should preserve state between array nested in fragment and fragment', async function () {const ops = [];
class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition ? (
<><Stateful key="a" /></>) : (<>{[<Stateful key="a" />]}</>
);}ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
await waitForAll([]);
expect(ops).toEqual(['Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});it('should preserve state between top level fragment and array', async function () {const ops = [];
class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition ? (
[<Stateful key="a" />]
) : (<><Stateful key="a" /></>);}ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
await waitForAll([]);
expect(ops).toEqual(['Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});it('should not preserve state between array nested in fragment and double nested fragment', async function () {const ops = [];
class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition ? (
<>{[<Stateful key="a" />]}</>
) : (<><><Stateful key="a" /></></>);}ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});it('should not preserve state between array nested in fragment and double nested array', async function () {const ops = [];
class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition ? (
<>{[<Stateful key="a" />]}</>
) : ([[<Stateful key="a" />]]
);}ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});it('should preserve state between double nested fragment and double nested array', async function () {const ops = [];
class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition ? (
<><><Stateful key="a" /></></>) : ([[<Stateful key="a" />]]
);}ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
await waitForAll([]);
expect(ops).toEqual(['Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});it('should not preserve state of children when the keys are different', async function () {const ops = [];
class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition ? (
<React.Fragment key="a">
<Stateful /></React.Fragment>
) : (<React.Fragment key="b">
<Stateful /><span>World</span></React.Fragment>
);}ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(
<><div>Hello</div><span>World</span></>,);ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});it('should not preserve state between unkeyed and keyed fragment', async function () {const ops = [];
class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition ? (
<React.Fragment key="a">
<Stateful /></React.Fragment>
) : (<><Stateful /></>);}ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});it('should preserve state with reordering in multiple levels', async function () {const ops = [];
class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition ? (
<div><React.Fragment key="c">
<span>foo</span><div key="b"><Stateful key="a" /></div></React.Fragment>
<span>boop</span></div>) : (<div><span>beep</span><React.Fragment key="c">
<div key="b"><Stateful key="a" /></div><span>bar</span></React.Fragment>
</div>);}ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
await waitForAll([]);
expect(ops).toEqual(['Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(
<div><span>beep</span><div><div>Hello</div></div><span>bar</span></div>,);ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(
<div><span>foo</span><div><div>Hello</div></div><span>boop</span></div>,);});it('should not preserve state when switching to a keyed fragment to an array', async () => {const ops = [];
class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition ? (
<div>{<React.Fragment key="foo">
<Stateful /></React.Fragment>
}<span /></div>) : (<div>{[<Stateful />]}
<span /></div>);}ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
await expect(async () => await waitForAll([])).toErrorDev(
'Each child in a list should have a unique "key" prop.',
);expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(
<div><div>Hello</div><span /></div>,);ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(
<div><div>Hello</div><span /></div>,);});it('should not preserve state when switching a nested unkeyed fragment to a passthrough component', async function () {const ops = [];
function Passthrough({children}) {return children;}class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition ? (
<><><Stateful /></></>) : (<><Passthrough><Stateful /></Passthrough></>);}ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});it('should not preserve state when switching a nested keyed fragment to a passthrough component', async function () {const ops = [];
function Passthrough({children}) {return children;}class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition ? (
<><React.Fragment key="a">
<Stateful /></React.Fragment>
</>) : (<><Passthrough><Stateful /></Passthrough></>);}ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});it('should not preserve state when switching a nested keyed array to a passthrough component', async function () {const ops = [];
function Passthrough({children}) {return children;}class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition ? (
<>{[<Stateful key="a" />]}</>
) : (<><Passthrough><Stateful /></Passthrough></>);}ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});it('should preserve state when it does not change positions', async function () {const ops = [];
class Stateful extends React.Component {
componentDidUpdate() {ops.push('Update Stateful');
}render() {return <div>Hello</div>;}}function Foo({condition}) {return condition? [
<span />,<><Stateful /></>,]: [
<span />,<><Stateful /></>,];
}ReactNoop.render(<Foo condition={true} />);
await expect(async () => await waitForAll([])).toErrorDev(
'Each child in a list should have a unique "key" prop.',
);ReactNoop.render(<Foo condition={false} />);
// The key warning gets deduped because it's in the same component.
await waitForAll([]);
expect(ops).toEqual(['Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(
<><span /><div>Hello</div></>,);ReactNoop.render(<Foo condition={true} />);
// The key warning gets deduped because it's in the same component.
await waitForAll([]);
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(
<><span /><div>Hello</div></>,);});});