/
githubmr
/
facebook-react
Обзор
Документация
Войти
/
githubmr
/
facebook-react
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
packages/react/src/__tests__/ReactChildren-test.js
1 225 строк
36 KB
Ricky
[flags] remove enableOwnerStacks (#32426)
04 мар 2025, 20:34
Не верифицирован
04 мар 2025, 20:34
e0fe347
Код
Авторство
О чём код?
/** * 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 */ 'use strict'; describe('ReactChildren', () => { let React; let ReactDOMClient; let act; let assertConsoleErrorDev; beforeEach(() => { jest.resetModules(); React = require('react'); ReactDOMClient = require('react-dom/client'); ({act, assertConsoleErrorDev} = require('internal-test-utils')); }); it('should support identity for simple', () => { const context = {}; const callback = jest.fn().mockImplementation(function (kid, index) { expect(this).toBe(context); return kid; }); const simpleKid = <span key="simple" />; // First pass children into a component to fully simulate what happens when // using structures that arrive from transforms. const instance = <div>{simpleKid}</div>; React.Children.forEach(instance.props.children, callback, context); expect(callback).toHaveBeenCalledWith(simpleKid, 0); callback.mockClear(); const mappedChildren = React.Children.map( instance.props.children, callback, context, ); expect(callback).toHaveBeenCalledWith(simpleKid, 0); expect(mappedChildren[0]).toEqual(<span key=".$simple" />); }); it('should support Portal components', () => { const context = {}; const callback = jest.fn().mockImplementation(function (kid, index) { expect(this).toBe(context); return kid; }); const ReactDOM = require('react-dom'); const portalContainer = document.createElement('div'); const simpleChild = <span key="simple" />; const reactPortal = ReactDOM.createPortal(simpleChild, portalContainer); const parentInstance = <div>{reactPortal}</div>; React.Children.forEach(parentInstance.props.children, callback, context); expect(callback).toHaveBeenCalledWith(reactPortal, 0); callback.mockClear(); const mappedChildren = React.Children.map( parentInstance.props.children, callback, context, ); expect(callback).toHaveBeenCalledWith(reactPortal, 0); expect(mappedChildren[0]).toEqual(reactPortal); }); it('should treat single arrayless child as being in array', () => { const context = {}; const callback = jest.fn().mockImplementation(function (kid, index) { expect(this).toBe(context); return kid; }); const simpleKid = <span />; const instance = <div>{simpleKid}</div>; React.Children.forEach(instance.props.children, callback, context); expect(callback).toHaveBeenCalledWith(simpleKid, 0); callback.mockClear(); const mappedChildren = React.Children.map( instance.props.children, callback, context, ); expect(callback).toHaveBeenCalledWith(simpleKid, 0); expect(mappedChildren[0]).toEqual(<span key=".0" />); }); it('should treat single child in array as expected', () => { const context = {}; const callback = jest.fn().mockImplementation(function (kid, index) { expect(this).toBe(context); return kid; }); const simpleKid = <span key="simple" />; const instance = <div>{[simpleKid]}</div>; React.Children.forEach(instance.props.children, callback, context); expect(callback).toHaveBeenCalledWith(simpleKid, 0); callback.mockClear(); const mappedChildren = React.Children.map( instance.props.children, callback, context, ); expect(callback).toHaveBeenCalledWith(simpleKid, 0); expect(mappedChildren[0]).toEqual(<span key=".$simple" />); }); it('should be called for each child', () => { const zero = <div key="keyZero" />; const one = null; const two = <div key="keyTwo" />; const three = null; const four = <div key="keyFour" />; const context = {}; const callback = jest.fn().mockImplementation(function (kid) { expect(this).toBe(context); return kid; }); const instance = ( <div> {zero} {one} {two} {three} {four} </div> ); function assertCalls() { expect(callback).toHaveBeenCalledWith(zero, 0); expect(callback).toHaveBeenCalledWith(one, 1); expect(callback).toHaveBeenCalledWith(two, 2); expect(callback).toHaveBeenCalledWith(three, 3); expect(callback).toHaveBeenCalledWith(four, 4); callback.mockClear(); } React.Children.forEach(instance.props.children, callback, context); assertCalls(); const mappedChildren = React.Children.map( instance.props.children, callback, context, ); assertCalls(); expect(mappedChildren).toEqual([ <div key=".$keyZero" />, <div key=".$keyTwo" />, <div key=".$keyFour" />, ]); }); it('should traverse children of different kinds', () => { const div = <div key="divNode" />; const span = <span key="spanNode" />; const a = <a key="aNode" />; const context = {}; const callback = jest.fn().mockImplementation(function (kid) { expect(this).toBe(context); return kid; }); const instance = ( <div> {div} {[[span]]} {[a]} {'string'} {1234} {true} {false} {null} {undefined} {9n} </div> ); function assertCalls() { expect(callback).toHaveBeenCalledTimes(10); expect(callback).toHaveBeenCalledWith(div, 0); expect(callback).toHaveBeenCalledWith(span, 1); expect(callback).toHaveBeenCalledWith(a, 2); expect(callback).toHaveBeenCalledWith('string', 3); expect(callback).toHaveBeenCalledWith(1234, 4); expect(callback).toHaveBeenCalledWith(null, 5); expect(callback).toHaveBeenCalledWith(null, 6); expect(callback).toHaveBeenCalledWith(null, 7); expect(callback).toHaveBeenCalledWith(null, 8); expect(callback).toHaveBeenCalledWith(9n, 9); callback.mockClear(); } React.Children.forEach(instance.props.children, callback, context); assertCalls(); const mappedChildren = React.Children.map( instance.props.children, callback, context, ); assertCalls(); expect(mappedChildren).toEqual([ <div key=".$divNode" />, <span key=".1:0:$spanNode" />, <a key=".2:$aNode" />, 'string', 1234, 9n, ]); }); it('should be called for each child in nested structure', () => { const zero = <div key="keyZero" />; const one = null; const two = <div key="keyTwo" />; const three = null; const four = <div key="keyFour" />; const five = <div key="keyFive" />; const context = {}; const callback = jest.fn().mockImplementation(function (kid) { return kid; }); const instance = <div>{[[zero, one, two], [three, four], five]}</div>; function assertCalls() { expect(callback).toHaveBeenCalledTimes(6); expect(callback).toHaveBeenCalledWith(zero, 0); expect(callback).toHaveBeenCalledWith(one, 1); expect(callback).toHaveBeenCalledWith(two, 2); expect(callback).toHaveBeenCalledWith(three, 3); expect(callback).toHaveBeenCalledWith(four, 4); expect(callback).toHaveBeenCalledWith(five, 5); callback.mockClear(); } React.Children.forEach(instance.props.children, callback, context); assertCalls(); const mappedChildren = React.Children.map( instance.props.children, callback, context, ); assertCalls(); expect(mappedChildren).toEqual([ <div key=".0:$keyZero" />, <div key=".0:$keyTwo" />, <div key=".1:$keyFour" />, <div key=".$keyFive" />, ]); }); it('should retain key across two mappings', () => { const zeroForceKey = <div key="keyZero" />; const oneForceKey = <div key="keyOne" />; const context = {}; const callback = jest.fn().mockImplementation(function (kid) { expect(this).toBe(context); return kid; }); const forcedKeys = ( <div> {zeroForceKey} {oneForceKey} </div> ); function assertCalls() { expect(callback).toHaveBeenCalledWith(zeroForceKey, 0); expect(callback).toHaveBeenCalledWith(oneForceKey, 1); callback.mockClear(); } React.Children.forEach(forcedKeys.props.children, callback, context); assertCalls(); const mappedChildren = React.Children.map( forcedKeys.props.children, callback, context, ); assertCalls(); expect(mappedChildren).toEqual([ <div key=".$keyZero" />, <div key=".$keyOne" />, ]); }); it('should be called for each child in an iterable without keys', async () => { const threeDivIterable = { '@@iterator': function () { let i = 0; return { next: function () { if (i++ < 3) { return {value: <div />, done: false}; } else { return {value: undefined, done: true}; } }, }; }, }; const context = {}; const callback = jest.fn().mockImplementation(function (kid) { expect(this).toBe(context); return kid; }); function assertCalls() { expect(callback).toHaveBeenCalledTimes(3); expect(callback).toHaveBeenCalledWith(<div />, 0); expect(callback).toHaveBeenCalledWith(<div />, 1); expect(callback).toHaveBeenCalledWith(<div />, 2); callback.mockClear(); } const instance = <div>{threeDivIterable}</div>; React.Children.forEach(instance.props.children, callback, context); assertCalls(); const mappedChildren = React.Children.map( instance.props.children, callback, context, ); assertCalls(); expect(mappedChildren).toEqual([ <div key=".0" />, <div key=".1" />, <div key=".2" />, ]); const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(instance); }); assertConsoleErrorDev([ 'Each child in a list should have a unique "key" prop.\n\n' + 'Check the top-level render call using <div>. It was passed a child from div.' + ' See https://react.dev/link/warning-keys for more information.\n' + ' in div (at **)', ]); }); it('should be called for each child in an iterable with keys', () => { const threeDivIterable = { '@@iterator': function () { let i = 0; return { next: function () { if (i++ < 3) { return {value: <div key={'#' + i} />, done: false}; } else { return {value: undefined, done: true}; } }, }; }, }; const context = {}; const callback = jest.fn().mockImplementation(function (kid) { expect(this).toBe(context); return kid; }); const instance = <div>{threeDivIterable}</div>; function assertCalls() { expect(callback).toHaveBeenCalledTimes(3); expect(callback).toHaveBeenCalledWith(<div key="#1" />, 0); expect(callback).toHaveBeenCalledWith(<div key="#2" />, 1); expect(callback).toHaveBeenCalledWith(<div key="#3" />, 2); callback.mockClear(); } React.Children.forEach(instance.props.children, callback, context); assertCalls(); const mappedChildren = React.Children.map( instance.props.children, callback, context, ); assertCalls(); expect(mappedChildren).toEqual([ <div key=".$#1" />, <div key=".$#2" />, <div key=".$#3" />, ]); }); it('should not enumerate enumerable numbers (#4776)', () => { /*eslint-disable no-extend-native */ Number.prototype['@@iterator'] = function () { throw new Error('number iterator called'); }; /*eslint-enable no-extend-native */ try { const instance = ( <div> {5} {12} {13} </div> ); const context = {}; const callback = jest.fn().mockImplementation(function (kid) { expect(this).toBe(context); return kid; }); const assertCalls = function () { expect(callback).toHaveBeenCalledTimes(3); expect(callback).toHaveBeenCalledWith(5, 0); expect(callback).toHaveBeenCalledWith(12, 1); expect(callback).toHaveBeenCalledWith(13, 2); callback.mockClear(); }; React.Children.forEach(instance.props.children, callback, context); assertCalls(); const mappedChildren = React.Children.map( instance.props.children, callback, context, ); assertCalls(); expect(mappedChildren).toEqual([5, 12, 13]); } finally { delete Number.prototype['@@iterator']; } }); it('should allow extension of native prototypes', () => { /*eslint-disable no-extend-native */ String.prototype.key = 'react'; Number.prototype.key = 'rocks'; /*eslint-enable no-extend-native */ const instance = ( <div> {'a'} {13} </div> ); const context = {}; const callback = jest.fn().mockImplementation(function (kid) { expect(this).toBe(context); return kid; }); function assertCalls() { expect(callback).toHaveBeenCalledTimes(2); expect(callback).toHaveBeenCalledWith('a', 0); expect(callback).toHaveBeenCalledWith(13, 1); callback.mockClear(); } React.Children.forEach(instance.props.children, callback, context); assertCalls(); const mappedChildren = React.Children.map( instance.props.children, callback, context, ); assertCalls(); expect(mappedChildren).toEqual(['a', 13]); delete String.prototype.key; delete Number.prototype.key; }); it('should pass key to returned component', () => { const mapFn = function (kid, index) { return <div>{kid}</div>; }; const simpleKid = <span key="simple" />; const instance = <div>{simpleKid}</div>; const mappedChildren = React.Children.map(instance.props.children, mapFn); expect(React.Children.count(mappedChildren)).toBe(1); expect(mappedChildren[0]).not.toBe(simpleKid); expect(mappedChildren[0].props.children).toBe(simpleKid); expect(mappedChildren[0].key).toBe('.$simple'); }); it('should invoke callback with the right context', () => { let lastContext; const callback = function (kid, index) { lastContext = this; return this; }; // TODO: Use an object to test, after non-object fragments has fully landed. const scopeTester = 'scope tester'; const simpleKid = <span key="simple" />; const instance = <div>{simpleKid}</div>; React.Children.forEach(instance.props.children, callback, scopeTester); expect(lastContext).toBe(scopeTester); const mappedChildren = React.Children.map( instance.props.children, callback, scopeTester, ); expect(React.Children.count(mappedChildren)).toBe(1); expect(mappedChildren[0]).toBe(scopeTester); }); it('should be called for each child in array', () => { const zero = <div key="keyZero" />; const one = null; const two = <div key="keyTwo" />; const three = null; const four = <div key="keyFour" />; const mapped = [ <div key="giraffe" />, // Key should be joined to obj key null, // Key should be added even if we don't supply it! <div />, // Key should be added even if not supplied! <span />, // Map from null to something. <div key="keyFour" />, ]; const callback = jest.fn().mockImplementation(function (kid, index) { return mapped[index]; }); const instance = ( <div> {zero} {one} {two} {three} {four} </div> ); React.Children.forEach(instance.props.children, callback); expect(callback).toHaveBeenCalledWith(zero, 0); expect(callback).toHaveBeenCalledWith(one, 1); expect(callback).toHaveBeenCalledWith(two, 2); expect(callback).toHaveBeenCalledWith(three, 3); expect(callback).toHaveBeenCalledWith(four, 4); callback.mockClear(); const mappedChildren = React.Children.map( instance.props.children, callback, ); expect(callback).toHaveBeenCalledTimes(5); expect(React.Children.count(mappedChildren)).toBe(4); // Keys default to indices. expect([ mappedChildren[0].key, mappedChildren[1].key, mappedChildren[2].key, mappedChildren[3].key, ]).toEqual(['giraffe/.$keyZero', '.$keyTwo', '.3', '.$keyFour']); expect(callback).toHaveBeenCalledWith(zero, 0); expect(callback).toHaveBeenCalledWith(one, 1); expect(callback).toHaveBeenCalledWith(two, 2); expect(callback).toHaveBeenCalledWith(three, 3); expect(callback).toHaveBeenCalledWith(four, 4); expect(mappedChildren[0]).toEqual(<div key="giraffe/.$keyZero" />); expect(mappedChildren[1]).toEqual(<div key=".$keyTwo" />); expect(mappedChildren[2]).toEqual(<span key=".3" />); expect(mappedChildren[3]).toEqual(<div key=".$keyFour" />); }); it('should be called for each child in nested structure with mapping', () => { const zero = <div key="keyZero" />; const one = null; const two = <div key="keyTwo" />; const three = null; const four = <div key="keyFour" />; const five = <div key="keyFive" />; const zeroMapped = <div key="giraffe" />; // Key should be overridden const twoMapped = <div />; // Key should be added even if not supplied! const fourMapped = <div key="keyFour" />; const fiveMapped = <div />; const callback = jest.fn().mockImplementation(function (kid) { switch (kid) { case zero: return zeroMapped; case two: return twoMapped; case four: return fourMapped; case five: return fiveMapped; default: return kid; } }); const frag = [[zero, one, two], [three, four], five]; const instance = <div>{[frag]}</div>; React.Children.forEach(instance.props.children, callback); expect(callback).toHaveBeenCalledTimes(6); expect(callback).toHaveBeenCalledWith(zero, 0); expect(callback).toHaveBeenCalledWith(one, 1); expect(callback).toHaveBeenCalledWith(two, 2); expect(callback).toHaveBeenCalledWith(three, 3); expect(callback).toHaveBeenCalledWith(four, 4); expect(callback).toHaveBeenCalledWith(five, 5); callback.mockClear(); const mappedChildren = React.Children.map( instance.props.children, callback, ); expect(callback).toHaveBeenCalledTimes(6); expect(callback).toHaveBeenCalledWith(zero, 0); expect(callback).toHaveBeenCalledWith(one, 1); expect(callback).toHaveBeenCalledWith(two, 2); expect(callback).toHaveBeenCalledWith(three, 3); expect(callback).toHaveBeenCalledWith(four, 4); expect(callback).toHaveBeenCalledWith(five, 5); expect(React.Children.count(mappedChildren)).toBe(4); // Keys default to indices. expect([ mappedChildren[0].key, mappedChildren[1].key, mappedChildren[2].key, mappedChildren[3].key, ]).toEqual([ 'giraffe/.0:0:$keyZero', '.0:0:$keyTwo', '.0:1:$keyFour', '.0:$keyFive', ]); expect(mappedChildren[0]).toEqual(<div key="giraffe/.0:0:$keyZero" />); expect(mappedChildren[1]).toEqual(<div key=".0:0:$keyTwo" />); expect(mappedChildren[2]).toEqual(<div key=".0:1:$keyFour" />); expect(mappedChildren[3]).toEqual(<div key=".0:$keyFive" />); }); it('should retain key across two mappings with conditions', () => { const zeroForceKey = <div key="keyZero" />; const oneForceKey = <div key="keyOne" />; // Key should be joined to object key const zeroForceKeyMapped = <div key="giraffe" />; // Key should be added even if we don't supply it! const oneForceKeyMapped = <div />; const mapFn = function (kid, index) { return index === 0 ? zeroForceKeyMapped : oneForceKeyMapped; }; const forcedKeys = ( <div> {zeroForceKey} {oneForceKey} </div> ); const expectedForcedKeys = ['giraffe/.$keyZero', '.$keyOne']; const mappedChildrenForcedKeys = React.Children.map( forcedKeys.props.children, mapFn, ); const mappedForcedKeys = mappedChildrenForcedKeys.map(c => c.key); expect(mappedForcedKeys).toEqual(expectedForcedKeys); const expectedRemappedForcedKeys = [ 'giraffe/.$giraffe/.$keyZero', '.$.$keyOne', ]; const remappedChildrenForcedKeys = React.Children.map( mappedChildrenForcedKeys, mapFn, ); expect(remappedChildrenForcedKeys.map(c => c.key)).toEqual( expectedRemappedForcedKeys, ); }); it('should not throw if key provided is a dupe with array key', () => { const zero = <div />; const one = <div key="0" />; const mapFn = function () { return null; }; const instance = ( <div> {zero} {one} </div> ); expect(function () { React.Children.map(instance.props.children, mapFn); }).not.toThrow(); }); it('should use the same key for a cloned element', () => { const instance = ( <div> <div /> </div> ); const mapped = React.Children.map( instance.props.children, element => element, ); const mappedWithClone = React.Children.map( instance.props.children, element => React.cloneElement(element), ); expect(mapped[0].key).toBe(mappedWithClone[0].key); }); it('should use the same key for a cloned element with key', () => { const instance = ( <div> <div key="unique" /> </div> ); const mapped = React.Children.map( instance.props.children, element => element, ); const mappedWithClone = React.Children.map( instance.props.children, element => React.cloneElement(element, {key: 'unique'}), ); expect(mapped[0].key).toBe(mappedWithClone[0].key); }); it('should return 0 for null children', () => { const numberOfChildren = React.Children.count(null); expect(numberOfChildren).toBe(0); }); it('should return 0 for undefined children', () => { const numberOfChildren = React.Children.count(undefined); expect(numberOfChildren).toBe(0); }); it('should return 1 for single child', () => { const simpleKid = <span key="simple" />; const instance = <div>{simpleKid}</div>; const numberOfChildren = React.Children.count(instance.props.children); expect(numberOfChildren).toBe(1); }); it('should count the number of children in flat structure', () => { const zero = <div key="keyZero" />; const one = null; const two = <div key="keyTwo" />; const three = null; const four = <div key="keyFour" />; const instance = ( <div> {zero} {one} {two} {three} {four} </div> ); const numberOfChildren = React.Children.count(instance.props.children); expect(numberOfChildren).toBe(5); }); it('should count the number of children in nested structure', () => { const zero = <div key="keyZero" />; const one = null; const two = <div key="keyTwo" />; const three = null; const four = <div key="keyFour" />; const five = <div key="keyFive" />; const instance = ( <div>{[[[zero, one, two], [three, four], five], null]}</div> ); const numberOfChildren = React.Children.count(instance.props.children); expect(numberOfChildren).toBe(7); }); it('should flatten children to an array', () => { expect(React.Children.toArray(undefined)).toEqual([]); expect(React.Children.toArray(null)).toEqual([]); expect(React.Children.toArray(<div />).length).toBe(1); expect(React.Children.toArray([<div />]).length).toBe(1); expect(React.Children.toArray(<div />)[0].key).toBe( React.Children.toArray([<div />])[0].key, ); const flattened = React.Children.toArray([ [<div key="apple" />, <div key="banana" />, <div key="camel" />], [<div key="banana" />, <div key="camel" />, <div key="deli" />], ]); expect(flattened.length).toBe(6); expect(flattened[1].key).toContain('banana'); expect(flattened[3].key).toContain('banana'); expect(flattened[1].key).not.toBe(flattened[3].key); const reversed = React.Children.toArray([ [<div key="camel" />, <div key="banana" />, <div key="apple" />], [<div key="deli" />, <div key="camel" />, <div key="banana" />], ]); expect(flattened[0].key).toBe(reversed[2].key); expect(flattened[1].key).toBe(reversed[1].key); expect(flattened[2].key).toBe(reversed[0].key); expect(flattened[3].key).toBe(reversed[5].key); expect(flattened[4].key).toBe(reversed[4].key); expect(flattened[5].key).toBe(reversed[3].key); // null/undefined/bool are all omitted expect(React.Children.toArray([1, 'two', null, undefined, true])).toEqual([ 1, 'two', ]); }); it('warns for mapped list children without keys', async () => { function ComponentRenderingMappedChildren({children}) { return ( <div> {React.Children.map(children, child => ( <div /> ))} </div> ); } const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render( <ComponentRenderingMappedChildren> {[<div />]} </ComponentRenderingMappedChildren>, ); }); assertConsoleErrorDev([ 'Each child in a list should have a unique "key" prop.\n\n' + 'Check the render method of `ComponentRenderingMappedChildren`.' + ' See https://react.dev/link/warning-keys for more information.\n' + ' in div (at **)\n' + ' in **/ReactChildren-test.js:**:** (at **)', ]); }); it('does not warn for mapped static children without keys', async () => { function ComponentRenderingMappedChildren({children}) { return ( <div> {React.Children.map(children, child => ( <div /> ))} </div> ); } const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render( <ComponentRenderingMappedChildren> <div /> <div /> </ComponentRenderingMappedChildren>, ); }); }); it('warns for cloned list children without keys', async () => { function ComponentRenderingClonedChildren({children}) { return ( <div> {React.Children.map(children, child => React.cloneElement(child))} </div> ); } const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render( <ComponentRenderingClonedChildren> {[<div />]} </ComponentRenderingClonedChildren>, ); }); assertConsoleErrorDev([ 'Each child in a list should have a unique "key" prop.\n\n' + 'Check the render method of `ComponentRenderingClonedChildren`.' + ' See https://react.dev/link/warning-keys for more information.\n' + ' in div (at **)', ]); }); it('does not warn for cloned static children without keys', async () => { function ComponentRenderingClonedChildren({children}) { return ( <div> {React.Children.map(children, child => React.cloneElement(child))} </div> ); } const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render( <ComponentRenderingClonedChildren> <div /> <div /> </ComponentRenderingClonedChildren>, ); }); }); it('warns for flattened list children without keys', async () => { function ComponentRenderingFlattenedChildren({children}) { return <div>{React.Children.toArray(children)}</div>; } const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render( <ComponentRenderingFlattenedChildren> {[<div />]} </ComponentRenderingFlattenedChildren>, ); }); assertConsoleErrorDev([ 'Each child in a list should have a unique "key" prop.\n\n' + 'Check the render method of `ComponentRenderingFlattenedChildren`.' + ' See https://react.dev/link/warning-keys for more information.\n' + ' in div (at **)', ]); }); it('does not warn for flattened static children without keys', async () => { function ComponentRenderingFlattenedChildren({children}) { return <div>{React.Children.toArray(children)}</div>; } const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render( <ComponentRenderingFlattenedChildren> <div /> <div /> </ComponentRenderingFlattenedChildren>, ); }); }); it('does not throw on children without `_store`', async () => { function ComponentRenderingFlattenedChildren({children}) { return <div>{React.Children.toArray(children)}</div>; } const source = <div />; const productionElement = {}; Object.entries(source).forEach(([key, value]) => { if (key !== '_owner' && key !== '_store') { productionElement[key] = value; } }); Object.freeze(productionElement); const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render( <ComponentRenderingFlattenedChildren> {productionElement} </ComponentRenderingFlattenedChildren>, ); }); }); it('should escape keys', () => { const zero = <div key="1" />; const one = <div key="1=::=2" />; const instance = ( <div> {zero} {one} </div> ); const mappedChildren = React.Children.map( instance.props.children, kid => kid, ); expect(mappedChildren).toEqual([ <div key=".$1" />, <div key=".$1=0=2=2=02" />, ]); }); it('should combine keys when map returns an array', () => { const instance = ( <div> <div key="a" /> {false} <div key="b" /> <p /> </div> ); const mappedChildren = React.Children.map( instance.props.children, // Try a few things: keyed, unkeyed, hole, and a cloned element. kid => [ <span key="x" />, null, <span key="y" />, kid, kid && React.cloneElement(kid, {key: 'z'}), <hr />, ], ); expect(mappedChildren.length).toBe(18); // <div key="a"> expect(mappedChildren[0].type).toBe('span'); expect(mappedChildren[0].key).toBe('.$a/.$x'); expect(mappedChildren[1].type).toBe('span'); expect(mappedChildren[1].key).toBe('.$a/.$y'); expect(mappedChildren[2].type).toBe('div'); expect(mappedChildren[2].key).toBe('.$a/.$a'); expect(mappedChildren[3].type).toBe('div'); expect(mappedChildren[3].key).toBe('.$a/.$z'); expect(mappedChildren[4].type).toBe('hr'); expect(mappedChildren[4].key).toBe('.$a/.5'); // false expect(mappedChildren[5].type).toBe('span'); expect(mappedChildren[5].key).toBe('.1/.$x'); expect(mappedChildren[6].type).toBe('span'); expect(mappedChildren[6].key).toBe('.1/.$y'); expect(mappedChildren[7].type).toBe('hr'); expect(mappedChildren[7].key).toBe('.1/.5'); // <div key="b"> expect(mappedChildren[8].type).toBe('span'); expect(mappedChildren[8].key).toBe('.$b/.$x'); expect(mappedChildren[9].type).toBe('span'); expect(mappedChildren[9].key).toBe('.$b/.$y'); expect(mappedChildren[10].type).toBe('div'); expect(mappedChildren[10].key).toBe('.$b/.$b'); expect(mappedChildren[11].type).toBe('div'); expect(mappedChildren[11].key).toBe('.$b/.$z'); expect(mappedChildren[12].type).toBe('hr'); expect(mappedChildren[12].key).toBe('.$b/.5'); // <p> expect(mappedChildren[13].type).toBe('span'); expect(mappedChildren[13].key).toBe('.3/.$x'); expect(mappedChildren[14].type).toBe('span'); expect(mappedChildren[14].key).toBe('.3/.$y'); expect(mappedChildren[15].type).toBe('p'); expect(mappedChildren[15].key).toBe('.3/.3'); expect(mappedChildren[16].type).toBe('p'); expect(mappedChildren[16].key).toBe('.3/.$z'); expect(mappedChildren[17].type).toBe('hr'); expect(mappedChildren[17].key).toBe('.3/.5'); }); it('should throw on object', () => { expect(function () { React.Children.forEach({a: 1, b: 2}, function () {}, null); }).toThrowError( 'Objects are not valid as a React child (found: object with keys ' + '{a, b}).' + (__DEV__ ? ' If you meant to render a collection of children, use an ' + 'array instead.' : ''), ); }); it('should render React.lazy after suspending', async () => { const lazyElement = React.lazy(async () => ({default: <div key="hi" />})); function Component() { return React.Children.map([lazyElement], c => React.cloneElement(c, {children: 'hi'}), ); } const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(<Component />); }); expect(container.innerHTML).toBe('<div>hi</div>'); }); it('should render cached Promises after suspending', async () => { const promise = Promise.resolve(<div key="hi" />); function Component() { return React.Children.map([promise], c => React.cloneElement(c, {children: 'hi'}), ); } const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(<Component />); }); expect(container.innerHTML).toBe('<div>hi</div>'); }); it('should throw on regex', () => { // Really, we care about dates (#4840) but those have nondeterministic // serialization (timezones) so let's test a regex instead: expect(function () { React.Children.forEach(/abc/, function () {}, null); }).toThrowError( 'Objects are not valid as a React child (found: /abc/).' + (__DEV__ ? ' If you meant to render a collection of children, use an ' + 'array instead.' : ''), ); }); describe('with fragments enabled', () => { it('warns for keys for arrays of elements in a fragment', async () => { class ComponentReturningArray extends React.Component { render() { return [<div />, <div />]; } } const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(<ComponentReturningArray />); }); assertConsoleErrorDev([ 'Each child in a list should have a unique "key" prop.' + '\n\nCheck the top-level render call using <ComponentReturningArray>. It was passed a child from ComponentReturningArray. ' + 'See https://react.dev/link/warning-keys for more information.' + '\n in div (at **)' + '\n in ComponentReturningArray (at **)', ]); }); it('does not warn when there are keys on elements in a fragment', async () => { class ComponentReturningArray extends React.Component { render() { return [<div key="foo" />, <div key="bar" />]; } } const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(<ComponentReturningArray />); }); }); it('warns for keys for arrays at the top level', async () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render([<div />, <div />]); }); assertConsoleErrorDev([ 'Each child in a list should have a unique "key" prop.' + '\n\nCheck the top-level render call using <Root>. ' + 'See https://react.dev/link/warning-keys for more information.' + '\n in div (at **)', ]); }); }); });