/** * 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; let assertConsoleErrorDev; describe('ReactFragment', () => { beforeEach(function () { jest.resetModules(); React = require('react'); ReactNoop = require('react-noop-renderer'); const InternalTestUtils = require('internal-test-utils'); waitForAll = InternalTestUtils.waitForAll; assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev; }); it('should render a single child via noop renderer', async () => { const element = ( <> foo ); ReactNoop.render(element); await waitForAll([]); expect(ReactNoop).toMatchRenderedOutput(foo); }); it('should render zero children via noop renderer', async () => { const element = ; ReactNoop.render(element); await waitForAll([]); expect(ReactNoop).toMatchRenderedOutput(null); }); it('should render multiple children via noop renderer', async () => { const element = ( <> hello world ); ReactNoop.render(element); await waitForAll([]); expect(ReactNoop).toMatchRenderedOutput( <> hello world , ); }); it('should render an iterable via noop renderer', async () => { const element = ( <>{new Set([hi, bye])} ); ReactNoop.render(element); await waitForAll([]); expect(ReactNoop).toMatchRenderedOutput( <> hi bye , ); }); 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
Hello
; } } function Foo({condition}) { return condition ? ( ) : ( <>
World
); } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual(['Update Stateful']); expect(ReactNoop).toMatchRenderedOutput( <>
Hello
World
, ); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual(['Update Stateful', 'Update Stateful']); expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); it('should preserve state between top-level fragments', async function () { const ops = []; class Stateful extends React.Component { componentDidUpdate() { ops.push('Update Stateful'); } render() { return
Hello
; } } function Foo({condition}) { return condition ? ( <> ) : ( <> ); } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual(['Update Stateful']); expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual(['Update Stateful', 'Update Stateful']); expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); 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
Hello
; } } function Foo({condition}) { return condition ? ( <> <> <> ) : ( <> <> <>
); } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual(['Update Stateful']); expect(ReactNoop).toMatchRenderedOutput( <>
Hello
, ); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual(['Update Stateful', 'Update Stateful']); expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); 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
Hello
; } } function Foo({condition}) { return condition ? ( <> <> ) : ( <> ); } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); 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
Hello
; } } function Foo({condition}) { return condition ? ( ) : ( <> <> ); } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); 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
Hello
; } } function Foo({condition}) { return condition ? ( ) : ( <> <>
); } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput( <>
Hello
, ); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); 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
Hello
; } } function Foo({condition}) { return condition ? ( <> ) : ( <>{[]} ); } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual(['Update Stateful']); expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual(['Update Stateful', 'Update Stateful']); expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); 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
Hello
; } } function Foo({condition}) { return condition ? ( [] ) : ( <> ); } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual(['Update Stateful']); expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual(['Update Stateful', 'Update Stateful']); expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); 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
Hello
; } } function Foo({condition}) { return condition ? ( <>{[]} ) : ( <> <> ); } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); 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
Hello
; } } function Foo({condition}) { return condition ? ( <>{[]} ) : ( [[]] ); } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); 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
Hello
; } } function Foo({condition}) { return condition ? ( <> <> ) : ( [[]] ); } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual(['Update Stateful']); expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual(['Update Stateful', 'Update Stateful']); expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); 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
Hello
; } } function Foo({condition}) { return condition ? ( ) : ( World ); } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput( <>
Hello
World , ); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); 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
Hello
; } } function Foo({condition}) { return condition ? ( ) : ( <> ); } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); 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
Hello
; } } function Foo({condition}) { return condition ? (
foo
boop
) : (
beep
bar
); } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual(['Update Stateful']); expect(ReactNoop).toMatchRenderedOutput(
beep
Hello
bar
, ); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual(['Update Stateful', 'Update Stateful']); expect(ReactNoop).toMatchRenderedOutput(
foo
Hello
boop
, ); }); 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
Hello
; } } function Foo({condition}) { return condition ? (
{ }
) : (
{[]}
); } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); assertConsoleErrorDev([ 'Each child in a list should have a unique "key" prop.\n' + '\n' + 'Check the render method of `div`. ' + 'It was passed a child from Foo. ' + 'See https://react.dev/link/warning-keys for more information.\n' + ' in Foo (at **)', ]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
, ); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
, ); }); 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
Hello
; } } function Foo({condition}) { return condition ? ( <> <> ) : ( <> ); } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); 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
Hello
; } } function Foo({condition}) { return condition ? ( <> ) : ( <> ); } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); 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
Hello
; } } function Foo({condition}) { return condition ? ( <>{[]} ) : ( <> ); } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual([]); expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); 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
Hello
; } } function Foo({condition}) { return condition ? [ , <> , ] : [ , <> , ]; } ReactNoop.render(); await waitForAll([]); assertConsoleErrorDev([ 'Each child in a list should have a unique "key" prop.\n' + '\n' + 'Check the top-level render call using . ' + 'It was passed a child from Foo. ' + 'See https://react.dev/link/warning-keys for more information.\n' + ' in span (at **)\n' + ' in Foo (at **)', ]); ReactNoop.render(); // The key warning gets deduped because it's in the same component. await waitForAll([]); expect(ops).toEqual(['Update Stateful']); expect(ReactNoop).toMatchRenderedOutput( <>
Hello
, ); ReactNoop.render(); // The key warning gets deduped because it's in the same component. await waitForAll([]); expect(ops).toEqual(['Update Stateful', 'Update Stateful']); expect(ReactNoop).toMatchRenderedOutput( <>
Hello
, ); }); it('should preserve state of children when adding a fragment wrapped in Lazy', async function () { const ops = []; class Stateful extends React.Component { componentDidUpdate() { ops.push('Update Stateful'); } render() { return
Hello
; } } const lazyChild = React.lazy(async () => ({ default: ( <>
World
), })); function Foo({condition}) { return condition ? : lazyChild; } ReactNoop.render(); await waitForAll([]); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual(['Update Stateful']); expect(ReactNoop).toMatchRenderedOutput( <>
Hello
World
, ); ReactNoop.render(); await waitForAll([]); expect(ops).toEqual(['Update Stateful', 'Update Stateful']); expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); });