diff --git a/scripts/fiber/tests-failing.txt b/scripts/fiber/tests-failing.txt index 85a13ca7c625..b2f3df94f4e6 100644 --- a/scripts/fiber/tests-failing.txt +++ b/scripts/fiber/tests-failing.txt @@ -102,6 +102,7 @@ src/renderers/shared/shared/__tests__/ReactEmptyComponent-test.js src/renderers/shared/shared/__tests__/ReactMultiChildText-test.js * should correctly handle all possible children for render and update +* should reorder keyed text nodes src/renderers/shared/shared/__tests__/ReactStatelessComponent-test.js * should warn when stateless component returns array diff --git a/scripts/fiber/tests-passing-except-dev.txt b/scripts/fiber/tests-passing-except-dev.txt index f917d24e3551..5590ed90a215 100644 --- a/scripts/fiber/tests-passing-except-dev.txt +++ b/scripts/fiber/tests-passing-except-dev.txt @@ -46,6 +46,8 @@ src/renderers/dom/shared/__tests__/ReactDOMInvalidARIAHook-test.js * should warn for an improperly cased aria-* prop src/renderers/dom/shared/__tests__/ReactMount-test.js +* should warn if mounting into dirty rendered markup +* should warn when mounting into document.body * should account for escaping on a checksum mismatch * should warn if render removes React-rendered children * should warn if the unmounted node was rendered by another copy of React @@ -169,14 +171,9 @@ src/renderers/shared/shared/__tests__/ReactCompositeComponent-test.js src/renderers/shared/shared/__tests__/ReactMultiChild-test.js * should warn for duplicated keys with component stack info - -src/renderers/shared/shared/__tests__/ReactMultiChildText-test.js -* should reorder keyed text nodes +* should warn for using maps as children with owner info src/renderers/shared/shared/__tests__/ReactStatelessComponent-test.js * should warn for childContextTypes on a functional component * should warn when given a ref * should use correct name in key warning - -src/shared/utils/__tests__/traverseAllChildren-test.js -* should warn for using maps as children with owner info diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index 072432448638..ba28e7da449e 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -108,6 +108,15 @@ src/isomorphic/children/__tests__/ReactChildren-test.js * should support identity for simple * should treat single arrayless child as being in array * should treat single child in array as expected +* should be called for each child +* should traverse children of different kinds +* should be called for each child in nested structure +* should retain key across two mappings +* should be called for each child in an iterable without keys +* should be called for each child in an iterable with keys +* should use keys from entry iterables +* should not enumerate enumerable numbers (#4776) +* should allow extension of native prototypes * should pass key to returned component * should invoke callback with the right context * should be called for each child @@ -122,6 +131,8 @@ src/isomorphic/children/__tests__/ReactChildren-test.js * should count the number of children in flat structure * should count the number of children in nested structure * should flatten children to an array +* should throw on object +* should throw on regex src/isomorphic/children/__tests__/onlyChild-test.js * should fail when passed two children @@ -665,9 +676,7 @@ src/renderers/dom/shared/__tests__/ReactMount-test.js * should render different components in same root * should unmount and remount if the key changes * should reuse markup if rendering to the same target twice -* should warn if mounting into dirty rendered markup * should not warn if mounting into non-empty node -* should warn when mounting into document.body * passes the correct callback context src/renderers/dom/shared/__tests__/ReactMountDestruction-test.js @@ -1336,7 +1345,8 @@ src/renderers/shared/shared/__tests__/ReactMultiChild-test.js * should replace children with different keys src/renderers/shared/shared/__tests__/ReactMultiChildReconcile-test.js -* should reset internal state if removed then readded +* should reset internal state if removed then readded in an array +* should reset internal state if removed then readded in an iterable * should create unique identity * should preserve order if children order has not changed * should transition from zero to one children correctly @@ -1537,22 +1547,6 @@ src/shared/utils/__tests__/PooledClass-test.js src/shared/utils/__tests__/reactProdInvariant-test.js * should throw with the correct number of `%s`s in the URL -src/shared/utils/__tests__/traverseAllChildren-test.js -* should support identity for simple -* should treat single arrayless child as being in array -* should treat single child in array as expected -* should be called for each child -* should traverse children of different kinds -* should be called for each child in nested structure -* should retain key across two mappings -* should be called for each child in an iterable without keys -* should be called for each child in an iterable with keys -* should use keys from entry iterables -* should not enumerate enumerable numbers (#4776) -* should allow extension of native prototypes -* should throw on object -* should throw on regex - src/test/__tests__/ReactTestUtils-test.js * should have shallow rendering * should shallow render a functional component diff --git a/src/isomorphic/children/__tests__/ReactChildren-test.js b/src/isomorphic/children/__tests__/ReactChildren-test.js index 6eb4c89e2d12..c1e3bc342624 100644 --- a/src/isomorphic/children/__tests__/ReactChildren-test.js +++ b/src/isomorphic/children/__tests__/ReactChildren-test.js @@ -12,18 +12,19 @@ 'use strict'; describe('ReactChildren', () => { - var ReactChildren; var React; var ReactFragment; beforeEach(() => { - ReactChildren = require('ReactChildren'); + jest.resetModuleRegistry(); React = require('React'); ReactFragment = require('ReactFragment'); }); it('should support identity for simple', () => { + var context = {}; var callback = jasmine.createSpy().and.callFake(function(kid, index) { + expect(this).toBe(context); return kid; }); @@ -33,43 +34,478 @@ describe('ReactChildren', () => { // using structures that arrive from transforms. var instance =
{simpleKid}
; - ReactChildren.forEach(instance.props.children, callback); + React.Children.forEach(instance.props.children, callback, context); expect(callback).toHaveBeenCalledWith(simpleKid, 0); callback.calls.reset(); - var mappedChildren = ReactChildren.map(instance.props.children, callback); + var mappedChildren = React.Children.map(instance.props.children, callback, context); expect(callback).toHaveBeenCalledWith(simpleKid, 0); expect(mappedChildren[0]).toEqual(); }); it('should treat single arrayless child as being in array', () => { + var context = {}; var callback = jasmine.createSpy().and.callFake(function(kid, index) { + expect(this).toBe(context); return kid; }); var simpleKid = ; var instance =
{simpleKid}
; - ReactChildren.forEach(instance.props.children, callback); + React.Children.forEach(instance.props.children, callback, context); expect(callback).toHaveBeenCalledWith(simpleKid, 0); callback.calls.reset(); - var mappedChildren = ReactChildren.map(instance.props.children, callback); + var mappedChildren = React.Children.map(instance.props.children, callback, context); expect(callback).toHaveBeenCalledWith(simpleKid, 0); expect(mappedChildren[0]).toEqual(); }); it('should treat single child in array as expected', () => { + var context = {}; var callback = jasmine.createSpy().and.callFake(function(kid, index) { + expect(this).toBe(context); return kid; }); var simpleKid = ; var instance =
{[simpleKid]}
; - ReactChildren.forEach(instance.props.children, callback); + React.Children.forEach(instance.props.children, callback, context); expect(callback).toHaveBeenCalledWith(simpleKid, 0); callback.calls.reset(); - var mappedChildren = ReactChildren.map(instance.props.children, callback); + var mappedChildren = React.Children.map(instance.props.children, callback, context); expect(callback).toHaveBeenCalledWith(simpleKid, 0); expect(mappedChildren[0]).toEqual(); + }); + + it('should be called for each child', () => { + var zero =
; + var one = null; + var two =
; + var three = null; + var four =
; + var context = {}; + + var callback = + jasmine.createSpy().and.callFake(function(kid) { + expect(this).toBe(context); + return kid; + }); + + var instance = ( +
+ {zero} + {one} + {two} + {three} + {four} +
+ ); + + 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.calls.reset(); + } + + React.Children.forEach(instance.props.children, callback, context); + assertCalls(); + + var mappedChildren = React.Children.map(instance.props.children, callback, context); + assertCalls(); + expect(mappedChildren).toEqual([ +
, +
, +
, + ]); + }); + + it('should traverse children of different kinds', () => { + var div =
; + var span = ; + var a = ; + + var context = {}; + var callback = + jasmine.createSpy().and.callFake(function(kid) { + expect(this).toBe(context); + return kid; + }); + + var instance = ( +
+ {div} + {[ReactFragment.create({span})]} + {ReactFragment.create({a: a})} + {'string'} + {1234} + {true} + {false} + {null} + {undefined} +
+ ); + + function assertCalls() { + expect(callback.calls.count()).toBe(9); + expect(callback).toHaveBeenCalledWith(div, 0); + expect(callback).toHaveBeenCalledWith(, 1); + expect(callback).toHaveBeenCalledWith(
, 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); + callback.calls.reset(); + } + + React.Children.forEach(instance.props.children, callback, context); + assertCalls(); + + var mappedChildren = React.Children.map(instance.props.children, callback, context); + assertCalls(); + expect(mappedChildren).toEqual([ +
, + , + , + 'string', + 1234, + ]); + }); + + it('should be called for each child in nested structure', () => { + var zero =
; + var one = null; + var two =
; + var three = null; + var four =
; + var five =
; + // five is placed into a JS object with a key that is joined to the + // component key attribute. + // Precedence is as follows: + // 1. If grouped in an Object, the object key combined with `key` prop + // 2. If grouped in an Array, the `key` prop, falling back to array index + + var context = {}; + var callback = + jasmine.createSpy().and.callFake(function(kid) { + return kid; + }); + var instance = ( +
{[ + ReactFragment.create({ + firstHalfKey: [zero, one, two], + secondHalfKey: [three, four], + keyFive: five, + }), + ]}
+ ); + + function assertCalls() { + expect(callback.calls.count()).toBe(4); + expect(callback).toHaveBeenCalledWith(
, 0); + expect(callback).toHaveBeenCalledWith(
, 1); + expect(callback).toHaveBeenCalledWith(
, 2); + expect(callback).toHaveBeenCalledWith(
, 3); + callback.calls.reset(); + } + + React.Children.forEach(instance.props.children, callback, context); + assertCalls(); + + var mappedChildren = React.Children.map(instance.props.children, callback, context); + assertCalls(); + expect(mappedChildren).toEqual([ +
, +
, +
, +
, + ]); + }); + + it('should retain key across two mappings', () => { + var zeroForceKey =
; + var oneForceKey =
; + var context = {}; + var callback = + jasmine.createSpy().and.callFake(function(kid) { + expect(this).toBe(context); + return kid; + }); + + var forcedKeys = ( +
+ {zeroForceKey} + {oneForceKey} +
+ ); + + function assertCalls() { + expect(callback).toHaveBeenCalledWith(zeroForceKey, 0); + expect(callback).toHaveBeenCalledWith(oneForceKey, 1); + callback.calls.reset(); + } + + React.Children.forEach(forcedKeys.props.children, callback, context); + assertCalls(); + + var mappedChildren = React.Children.map(forcedKeys.props.children, callback, context); + assertCalls(); + expect(mappedChildren).toEqual([ +
, +
, + ]); + }); + + it('should be called for each child in an iterable without keys', () => { + spyOn(console, 'error'); + var threeDivIterable = { + '@@iterator': function() { + var i = 0; + return { + next: function() { + if (i++ < 3) { + return {value:
, done: false}; + } else { + return {value: undefined, done: true}; + } + }, + }; + }, + }; + + var context = {}; + var callback = + jasmine.createSpy().and.callFake(function(kid) { + expect(this).toBe(context); + return kid; + }); + + var instance = ( +
+ {threeDivIterable} +
+ ); + + function assertCalls() { + expect(callback.calls.count()).toBe(3); + expect(callback).toHaveBeenCalledWith(
, 0); + expect(callback).toHaveBeenCalledWith(
, 1); + expect(callback).toHaveBeenCalledWith(
, 2); + callback.calls.reset(); + } + + React.Children.forEach(instance.props.children, callback, context); + assertCalls(); + expectDev(console.error.calls.count()).toBe(1); + expectDev(console.error.calls.argsFor(0)[0]).toContain( + 'Warning: Each child in an array or iterator should have a unique "key" prop.' + ); + console.error.calls.reset(); + + var mappedChildren = React.Children.map(instance.props.children, callback, context); + assertCalls(); + expectDev(console.error.calls.count()).toBe(0); + expect(mappedChildren).toEqual([ +
, +
, +
, + ]); + }); + + it('should be called for each child in an iterable with keys', () => { + var threeDivIterable = { + '@@iterator': function() { + var i = 0; + return { + next: function() { + if (i++ < 3) { + return {value:
, done: false}; + } else { + return {value: undefined, done: true}; + } + }, + }; + }, + }; + + var context = {}; + var callback = + jasmine.createSpy().and.callFake(function(kid) { + expect(this).toBe(context); + return kid; + }); + + var instance = ( +
+ {threeDivIterable} +
+ ); + + function assertCalls() { + expect(callback.calls.count()).toBe(3); + expect(callback).toHaveBeenCalledWith(
, 0); + expect(callback).toHaveBeenCalledWith(
, 1); + expect(callback).toHaveBeenCalledWith(
, 2); + callback.calls.reset(); + } + + React.Children.forEach(instance.props.children, callback, context); + assertCalls(); + + var mappedChildren = React.Children.map(instance.props.children, callback, context); + assertCalls(); + expect(mappedChildren).toEqual([ +
, +
, +
, + ]); + }); + + it('should use keys from entry iterables', () => { + spyOn(console, 'error'); + + var threeDivEntryIterable = { + '@@iterator': function() { + var i = 0; + return { + next: function() { + if (i++ < 3) { + return {value: ['#' + i,
], done: false}; + } else { + return {value: undefined, done: true}; + } + }, + }; + }, + }; + threeDivEntryIterable.entries = threeDivEntryIterable['@@iterator']; + + var context = {}; + var callback = + jasmine.createSpy().and.callFake(function(kid) { + expect(this).toBe(context); + return kid; + }); + + var instance = ( +
+ {threeDivEntryIterable} +
+ ); + + function assertCalls() { + expect(callback.calls.count()).toBe(3); + // TODO: why + expect(callback).toHaveBeenCalledWith(
, 0); + expect(callback).toHaveBeenCalledWith(
, 1); + expect(callback).toHaveBeenCalledWith(
, 2); + + callback.calls.reset(); + } + + React.Children.forEach(instance.props.children, callback, context); + assertCalls(); + expectDev(console.error.calls.count()).toBe(1); + expectDev(console.error.calls.argsFor(0)[0]).toContain( + 'Warning: Using Maps as children is not yet fully supported. It is an ' + + 'experimental feature that might be removed. Convert it to a sequence ' + + '/ iterable of keyed ReactElements instead.' + ); + console.error.calls.reset(); + + var mappedChildren = React.Children.map(instance.props.children, callback, context); + assertCalls(); + expect(mappedChildren).toEqual([ +
, +
, +
, + ]); + expectDev(console.error.calls.count()).toBe(0); + }); + + 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 { + var instance = ( +
+ {5} + {12} + {13} +
+ ); + + var context = {}; + var callback = jasmine.createSpy().and.callFake(function(kid) { + expect(this).toBe(context); + return kid; + }); + + var assertCalls = function() { + expect(callback.calls.count()).toBe(3); + expect(callback).toHaveBeenCalledWith(5, 0); + expect(callback).toHaveBeenCalledWith(12, 1); + expect(callback).toHaveBeenCalledWith(13, 2); + callback.calls.reset(); + }; + + React.Children.forEach(instance.props.children, callback, context); + assertCalls(); + + var 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 */ + + var instance = ( +
+ {'a'} + {13} +
+ ); + + var context = {}; + var callback = jasmine.createSpy().and.callFake(function(kid) { + expect(this).toBe(context); + return kid; + }); + + function assertCalls() { + expect(callback.calls.count()).toBe(2, 0); + expect(callback).toHaveBeenCalledWith('a', 0); + expect(callback).toHaveBeenCalledWith(13, 1); + callback.calls.reset(); + } + + React.Children.forEach(instance.props.children, callback, context); + assertCalls(); + + var 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', () => { @@ -80,9 +516,9 @@ describe('ReactChildren', () => { var simpleKid = ; var instance =
{simpleKid}
; - var mappedChildren = ReactChildren.map(instance.props.children, mapFn); + var mappedChildren = React.Children.map(instance.props.children, mapFn); - expect(ReactChildren.count(mappedChildren)).toBe(1); + 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'); @@ -100,13 +536,13 @@ describe('ReactChildren', () => { var simpleKid = ; var instance =
{simpleKid}
; - ReactChildren.forEach(instance.props.children, callback, scopeTester); + React.Children.forEach(instance.props.children, callback, scopeTester); expect(lastContext).toBe(scopeTester); var mappedChildren = - ReactChildren.map(instance.props.children, callback, scopeTester); + React.Children.map(instance.props.children, callback, scopeTester); - expect(ReactChildren.count(mappedChildren)).toBe(1); + expect(React.Children.count(mappedChildren)).toBe(1); expect(mappedChildren[0]).toBe(scopeTester); }); @@ -138,7 +574,7 @@ describe('ReactChildren', () => {
); - ReactChildren.forEach(instance.props.children, callback); + React.Children.forEach(instance.props.children, callback); expect(callback).toHaveBeenCalledWith(zero, 0); expect(callback).toHaveBeenCalledWith(one, 1); expect(callback).toHaveBeenCalledWith(two, 2); @@ -147,9 +583,9 @@ describe('ReactChildren', () => { callback.calls.reset(); var mappedChildren = - ReactChildren.map(instance.props.children, callback); + React.Children.map(instance.props.children, callback); expect(callback.calls.count()).toBe(5); - expect(ReactChildren.count(mappedChildren)).toBe(4); + expect(React.Children.count(mappedChildren)).toBe(4); // Keys default to indices. expect([ mappedChildren[0].key, @@ -215,7 +651,7 @@ describe('ReactChildren', () => { 'keyFive/.$keyFiveInner', ]); - ReactChildren.forEach(instance.props.children, callback); + React.Children.forEach(instance.props.children, callback); expect(callback.calls.count()).toBe(4); expect(callback).toHaveBeenCalledWith(frag[0], 0); expect(callback).toHaveBeenCalledWith(frag[1], 1); @@ -223,14 +659,14 @@ describe('ReactChildren', () => { expect(callback).toHaveBeenCalledWith(frag[3], 3); callback.calls.reset(); - var mappedChildren = ReactChildren.map(instance.props.children, callback); + var mappedChildren = React.Children.map(instance.props.children, callback); expect(callback.calls.count()).toBe(4); expect(callback).toHaveBeenCalledWith(frag[0], 0); expect(callback).toHaveBeenCalledWith(frag[1], 1); expect(callback).toHaveBeenCalledWith(frag[2], 2); expect(callback).toHaveBeenCalledWith(frag[3], 3); - expect(ReactChildren.count(mappedChildren)).toBe(4); + expect(React.Children.count(mappedChildren)).toBe(4); // Keys default to indices. expect([ mappedChildren[0].key, @@ -272,7 +708,7 @@ describe('ReactChildren', () => { var expectedForcedKeys = ['giraffe/.$keyZero', '.$keyOne']; var mappedChildrenForcedKeys = - ReactChildren.map(forcedKeys.props.children, mapFn); + React.Children.map(forcedKeys.props.children, mapFn); var mappedForcedKeys = mappedChildrenForcedKeys.map((c) => c.key); expect(mappedForcedKeys).toEqual(expectedForcedKeys); @@ -281,7 +717,7 @@ describe('ReactChildren', () => { '.$.$keyOne', ]; var remappedChildrenForcedKeys = - ReactChildren.map(mappedChildrenForcedKeys, mapFn); + React.Children.map(mappedChildrenForcedKeys, mapFn); expect( remappedChildrenForcedKeys.map((c) => c.key) ).toEqual(expectedRemappedForcedKeys); @@ -304,7 +740,7 @@ describe('ReactChildren', () => { ); expect(function() { - ReactChildren.map(instance.props.children, mapFn); + React.Children.map(instance.props.children, mapFn); }).not.toThrow(); }); @@ -315,12 +751,12 @@ describe('ReactChildren', () => {
); - var mapped = ReactChildren.map( + var mapped = React.Children.map( instance.props.children, element => element, ); - var mappedWithClone = ReactChildren.map( + var mappedWithClone = React.Children.map( instance.props.children, element => React.cloneElement(element), ); @@ -335,12 +771,12 @@ describe('ReactChildren', () => {
); - var mapped = ReactChildren.map( + var mapped = React.Children.map( instance.props.children, element => element, ); - var mappedWithClone = ReactChildren.map( + var mappedWithClone = React.Children.map( instance.props.children, element => React.cloneElement(element, {key: 'unique'}), ); @@ -349,19 +785,19 @@ describe('ReactChildren', () => { }); it('should return 0 for null children', () => { - var numberOfChildren = ReactChildren.count(null); + var numberOfChildren = React.Children.count(null); expect(numberOfChildren).toBe(0); }); it('should return 0 for undefined children', () => { - var numberOfChildren = ReactChildren.count(undefined); + var numberOfChildren = React.Children.count(undefined); expect(numberOfChildren).toBe(0); }); it('should return 1 for single child', () => { var simpleKid = ; var instance =
{simpleKid}
; - var numberOfChildren = ReactChildren.count(instance.props.children); + var numberOfChildren = React.Children.count(instance.props.children); expect(numberOfChildren).toBe(1); }); @@ -381,7 +817,7 @@ describe('ReactChildren', () => { {four}
); - var numberOfChildren = ReactChildren.count(instance.props.children); + var numberOfChildren = React.Children.count(instance.props.children); expect(numberOfChildren).toBe(5); }); @@ -408,23 +844,23 @@ describe('ReactChildren', () => { null, ]}
); - var numberOfChildren = ReactChildren.count(instance.props.children); + var numberOfChildren = React.Children.count(instance.props.children); expect(numberOfChildren).toBe(5); }); it('should flatten children to an array', () => { - expect(ReactChildren.toArray(undefined)).toEqual([]); - expect(ReactChildren.toArray(null)).toEqual([]); + expect(React.Children.toArray(undefined)).toEqual([]); + expect(React.Children.toArray(null)).toEqual([]); - expect(ReactChildren.toArray(
).length).toBe(1); - expect(ReactChildren.toArray([
]).length).toBe(1); + expect(React.Children.toArray(
).length).toBe(1); + expect(React.Children.toArray([
]).length).toBe(1); expect( - ReactChildren.toArray(
)[0].key + React.Children.toArray(
)[0].key ).toBe( - ReactChildren.toArray([
])[0].key + React.Children.toArray([
])[0].key ); - var flattened = ReactChildren.toArray([ + var flattened = React.Children.toArray([ [
,
,
], [
,
,
], ]); @@ -433,7 +869,7 @@ describe('ReactChildren', () => { expect(flattened[3].key).toContain('banana'); expect(flattened[1].key).not.toBe(flattened[3].key); - var reversed = ReactChildren.toArray([ + var reversed = React.Children.toArray([ [
,
,
], [
,
,
], ]); @@ -445,9 +881,31 @@ describe('ReactChildren', () => { expect(flattened[5].key).toBe(reversed[3].key); // null/undefined/bool are all omitted - expect(ReactChildren.toArray([1, 'two', null, undefined, true])).toEqual( + expect(React.Children.toArray([1, 'two', null, undefined, true])).toEqual( [1, 'two'] ); }); + 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}). If you meant to render a collection of children, use an ' + + 'array instead or wrap the object using createFragment(object) from ' + + 'the React add-ons.' + ); + }); + + 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/). If you meant ' + + 'to render a collection of children, use an array instead or wrap the ' + + 'object using createFragment(object) from the React add-ons.' + ); + }); }); diff --git a/src/renderers/shared/fiber/ReactChildFiber.js b/src/renderers/shared/fiber/ReactChildFiber.js index 6a094f8250b6..e07fdf7646c0 100644 --- a/src/renderers/shared/fiber/ReactChildFiber.js +++ b/src/renderers/shared/fiber/ReactChildFiber.js @@ -545,6 +545,9 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) { // In this first iteration, we'll just live with hitting the bad case // (adding everything to a Map) in for every insert/move. + // If you change this code, also update reconcileChildrenIterator() which + // uses the same algorithm. + let resultingFirstChild : ?Fiber = null; let previousNewFiber : ?Fiber = null; @@ -676,10 +679,138 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) { returnFiber : Fiber, currentFirstChild : ?Fiber, newChildren : Iterator<*>, - priority : PriorityLevel) : null { - // TODO: Copy everything from reconcileChildrenArray but use the iterator - // instead. - return null; + priority : PriorityLevel) : ?Fiber { + + // This is the same implementation as reconcileChildrenArray(), + // but using the iterator instead. + + let resultingFirstChild : ?Fiber = null; + let previousNewFiber : ?Fiber = null; + + let oldFiber = currentFirstChild; + let lastPlacedIndex = 0; + let newIdx = 0; + let nextOldFiber = null; + + let step = newChildren.next(); + for (; oldFiber && !step.done; newIdx++, step = newChildren.next()) { + if (oldFiber) { + if (oldFiber.index > newIdx) { + nextOldFiber = oldFiber; + oldFiber = null; + } else { + nextOldFiber = oldFiber.sibling; + } + } + const newFiber = updateSlot( + returnFiber, + oldFiber, + step.value, + priority + ); + if (!newFiber) { + // TODO: This breaks on empty slots like null children. That's + // unfortunate because it triggers the slow path all the time. We need + // a better way to communicate whether this was a miss or null, + // boolean, undefined, etc. + if (!oldFiber) { + oldFiber = nextOldFiber; + } + break; + } + if (shouldTrackSideEffects) { + if (oldFiber && !newFiber.alternate) { + // We matched the slot, but we didn't reuse the existing fiber, so we + // need to delete the existing child. + deleteChild(returnFiber, oldFiber); + } + } + lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); + if (!previousNewFiber) { + // TODO: Move out of the loop. This only happens for the first run. + resultingFirstChild = newFiber; + } else { + // TODO: Defer siblings if we're not at the right index for this slot. + // I.e. if we had null values before, then we want to defer this + // for each null value. However, we also don't want to call updateSlot + // with the previous one. + previousNewFiber.sibling = newFiber; + } + previousNewFiber = newFiber; + oldFiber = nextOldFiber; + } + + if (step.done) { + // We've reached the end of the new children. We can delete the rest. + deleteRemainingChildren(returnFiber, oldFiber); + return resultingFirstChild; + } + + if (!oldFiber) { + // If we don't have any more existing children we can choose a fast path + // since the rest will all be insertions. + for (; !step.done; newIdx++, step = newChildren.next()) { + const newFiber = createChild( + returnFiber, + step.value, + priority + ); + if (!newFiber) { + continue; + } + lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); + if (!previousNewFiber) { + // TODO: Move out of the loop. This only happens for the first run. + resultingFirstChild = newFiber; + } else { + previousNewFiber.sibling = newFiber; + } + previousNewFiber = newFiber; + } + return resultingFirstChild; + } + + // Add all children to a key map for quick lookups. + const existingChildren = mapRemainingChildren(returnFiber, oldFiber); + + // Keep scanning and use the map to restore deleted items as moves. + for (; !step.done; newIdx++, step = newChildren.next()) { + const newFiber = updateFromMap( + existingChildren, + returnFiber, + newIdx, + step.value, + priority + ); + if (newFiber) { + if (shouldTrackSideEffects) { + if (newFiber.alternate) { + // The new fiber is a work in progress, but if there exists a + // current, that means that we reused the fiber. We need to delete + // it from the child list so that we don't add it to the deletion + // list. + existingChildren.delete( + newFiber.key === null ? newIdx : newFiber.key + ); + } + } + lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); + if (!previousNewFiber) { + resultingFirstChild = newFiber; + } else { + previousNewFiber.sibling = newFiber; + } + previousNewFiber = newFiber; + } + } + + if (shouldTrackSideEffects) { + // Any existing children that weren't consumed above were deleted. We need + // to add them to the deletion list. + existingChildren.forEach(child => deleteChild(returnFiber, child)); + } + + return resultingFirstChild; } function reconcileSingleTextNode( @@ -919,10 +1050,14 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) { const iteratorFn = getIteratorFn(newChild); if (iteratorFn) { + const iterator = iteratorFn.call(newChild); + if (iterator == null) { + throw new Error('An iterable object provided no iterator.'); + } return reconcileChildrenIterator( returnFiber, currentFirstChild, - newChild, + iterator, priority ); } diff --git a/src/renderers/shared/shared/__tests__/ReactMultiChild-test.js b/src/renderers/shared/shared/__tests__/ReactMultiChild-test.js index 500d00aa39d8..a7f69035308b 100644 --- a/src/renderers/shared/shared/__tests__/ReactMultiChild-test.js +++ b/src/renderers/shared/shared/__tests__/ReactMultiChild-test.js @@ -197,5 +197,27 @@ describe('ReactMultiChild', () => { ' in Parent (at **)' ); }); + + it('should warn for using maps as children with owner info', () => { + spyOn(console, 'error'); + + class Parent extends React.Component { + render() { + return ( +
{new Map([['foo', 0], ['bar', 1]])}
+ ); + } + } + + var container = document.createElement('div'); + ReactDOM.render(, container); + + expectDev(console.error.calls.count()).toBe(1); + expectDev(console.error.calls.argsFor(0)[0]).toBe( + 'Warning: Using Maps as children is not yet fully supported. It is an ' + + 'experimental feature that might be removed. Convert it to a sequence ' + + '/ iterable of keyed ReactElements instead. Check the render method of `Parent`.' + ); + }); }); }); diff --git a/src/renderers/shared/shared/__tests__/ReactMultiChildReconcile-test.js b/src/renderers/shared/shared/__tests__/ReactMultiChildReconcile-test.js index 43977bff1a6f..5de86177b0b4 100644 --- a/src/renderers/shared/shared/__tests__/ReactMultiChildReconcile-test.js +++ b/src/renderers/shared/shared/__tests__/ReactMultiChildReconcile-test.js @@ -129,9 +129,10 @@ class FriendsStatusDisplay extends React.Component { /> ); } + var childrenToRender = this.props.prepareChildren(children); return (
- {children} + {childrenToRender}
); } @@ -224,13 +225,13 @@ function verifyDomOrderingAccurate(outerContainer, statusDisplays) { expect(orderedDomKeys).toEqual(orderedLogicalKeys); } -/** - * Todo: Check that internal state is preserved across transitions - */ -function testPropsSequence(sequence) { +function testPropsSequenceWithPreparedChildren(sequence, prepareChildren) { var container = document.createElement('div'); var parentInstance = ReactDOM.render( - , + , container ); var statusDisplays = parentInstance.getStatusDisplays(); @@ -239,7 +240,10 @@ function testPropsSequence(sequence) { for (var i = 1; i < sequence.length; i++) { ReactDOM.render( - , + , container ); statusDisplays = parentInstance.getStatusDisplays(); @@ -251,12 +255,66 @@ function testPropsSequence(sequence) { } } +function prepareChildrenArray(childrenArray) { + return childrenArray; +} + +function prepareChildrenIterable(childrenArray) { + return { + '@@iterator': function*() { + for (const child of childrenArray) { + yield child; + } + }, + }; +} + +function testPropsSequence(sequence) { + testPropsSequenceWithPreparedChildren(sequence, prepareChildrenArray); + testPropsSequenceWithPreparedChildren(sequence, prepareChildrenIterable); +} + describe('ReactMultiChildReconcile', () => { beforeEach(() => { jest.resetModuleRegistry(); }); - it('should reset internal state if removed then readded', () => { + it('should reset internal state if removed then readded in an array', () => { + // Test basics. + var props = { + usernameToStatus: { + jcw: 'jcwStatus', + }, + }; + + var container = document.createElement('div'); + var parentInstance = ReactDOM.render( + , + container + ); + var statusDisplays = parentInstance.getStatusDisplays(); + var startingInternalState = statusDisplays.jcw.getInternalState(); + + // Now remove the child. + ReactDOM.render( + , + container + ); + statusDisplays = parentInstance.getStatusDisplays(); + expect(statusDisplays.jcw).toBeFalsy(); + + // Now reset the props that cause there to be a child + ReactDOM.render( + , + container + ); + statusDisplays = parentInstance.getStatusDisplays(); + expect(statusDisplays.jcw).toBeTruthy(); + expect(statusDisplays.jcw.getInternalState()) + .not.toBe(startingInternalState); + }); + + it('should reset internal state if removed then readded in an iterable', () => { // Test basics. var props = { usernameToStatus: { @@ -266,7 +324,7 @@ describe('ReactMultiChildReconcile', () => { var container = document.createElement('div'); var parentInstance = ReactDOM.render( - , + , container ); var statusDisplays = parentInstance.getStatusDisplays(); @@ -274,7 +332,7 @@ describe('ReactMultiChildReconcile', () => { // Now remove the child. ReactDOM.render( - , + , container ); statusDisplays = parentInstance.getStatusDisplays(); @@ -282,7 +340,7 @@ describe('ReactMultiChildReconcile', () => { // Now reset the props that cause there to be a child ReactDOM.render( - , + , container ); statusDisplays = parentInstance.getStatusDisplays(); diff --git a/src/shared/utils/__tests__/traverseAllChildren-test.js b/src/shared/utils/__tests__/traverseAllChildren-test.js deleted file mode 100644 index d6f17280997d..000000000000 --- a/src/shared/utils/__tests__/traverseAllChildren-test.js +++ /dev/null @@ -1,560 +0,0 @@ -/** - * Copyright 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @emails react-core - */ - -'use strict'; - -describe('traverseAllChildren', () => { - var traverseAllChildren; - var React; - var ReactFragment; - var ReactTestUtils; - - beforeEach(() => { - jest.resetModuleRegistry(); - traverseAllChildren = require('traverseAllChildren'); - React = require('React'); - ReactFragment = require('ReactFragment'); - ReactTestUtils = require('ReactTestUtils'); - }); - - function frag(obj) { - return ReactFragment.create(obj); - } - - it('should support identity for simple', () => { - var traverseContext = []; - var traverseFn = - jasmine.createSpy().and.callFake(function(context, kid, key, index) { - context.push(true); - }); - - var simpleKid = ; - - // Jasmine doesn't provide a way to test that the fn was invoked with scope. - var instance =
{simpleKid}
; - traverseAllChildren(instance.props.children, traverseFn, traverseContext); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, - simpleKid, - '.$simple' - ); - expect(traverseContext.length).toEqual(1); - }); - - it('should treat single arrayless child as being in array', () => { - var traverseContext = []; - var traverseFn = - jasmine.createSpy().and.callFake(function(context, kid, key, index) { - context.push(true); - }); - - var simpleKid = ; - var instance =
{simpleKid}
; - traverseAllChildren(instance.props.children, traverseFn, traverseContext); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, - simpleKid, - '.0' - ); - expect(traverseContext.length).toEqual(1); - }); - - it('should treat single child in array as expected', () => { - spyOn(console, 'error'); - var traverseContext = []; - var traverseFn = - jasmine.createSpy().and.callFake(function(context, kid, key, index) { - context.push(true); - }); - - var simpleKid = ; - var instance =
{[simpleKid]}
; - traverseAllChildren(instance.props.children, traverseFn, traverseContext); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, - simpleKid, - '.0' - ); - expect(traverseContext.length).toEqual(1); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Warning: Each child in an array or iterator should have a unique "key" prop.' - ); - }); - - it('should be called for each child', () => { - var zero =
; - var one = null; - var two =
; - var three = null; - var four =
; - - var traverseContext = []; - var traverseFn = - jasmine.createSpy().and.callFake(function(context, kid, key, index) { - context.push(true); - }); - - var instance = ( -
- {zero} - {one} - {two} - {three} - {four} -
- ); - - traverseAllChildren(instance.props.children, traverseFn, traverseContext); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, - zero, - '.$keyZero' - ); - expect(traverseFn).toHaveBeenCalledWith(traverseContext, one, '.1'); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, - two, - '.$keyTwo' - ); - expect(traverseFn).toHaveBeenCalledWith(traverseContext, three, '.3'); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, - four, - '.$keyFour' - ); - }); - - it('should traverse children of different kinds', () => { - var div =
; - var span = ; - var a = ; - - var traverseContext = []; - var traverseFn = - jasmine.createSpy().and.callFake(function(context, kid, key, index) { - context.push(true); - }); - - var instance = ( -
- {div} - {[frag({span})]} - {frag({a: a})} - {'string'} - {1234} - {true} - {false} - {null} - {undefined} -
- ); - - traverseAllChildren(instance.props.children, traverseFn, traverseContext); - - expect(traverseFn.calls.count()).toBe(9); - expect(traverseContext.length).toEqual(9); - - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, div, '.$divNode' - ); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, , '.1:0:$span/.$spanNode' - ); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext,
, '.2:$a/.$aNode' - ); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, 'string', '.3' - ); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, 1234, '.4' - ); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, null, '.5' - ); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, null, '.6' - ); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, null, '.7' - ); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, null, '.8' - ); - }); - - it('should be called for each child in nested structure', () => { - var zero =
; - var one = null; - var two =
; - var three = null; - var four =
; - var five =
; - // five is placed into a JS object with a key that is joined to the - // component key attribute. - // Precedence is as follows: - // 1. If grouped in an Object, the object key combined with `key` prop - // 2. If grouped in an Array, the `key` prop, falling back to array index - - - var traverseContext = []; - var traverseFn = - jasmine.createSpy().and.callFake(function(context, kid, key, index) { - context.push(true); - }); - - var instance = ( -
{[ - frag({ - firstHalfKey: [zero, one, two], - secondHalfKey: [three, four], - keyFive: five, - }), - ]}
- ); - - traverseAllChildren(instance.props.children, traverseFn, traverseContext); - expect(traverseFn.calls.count()).toBe(4); - expect(traverseContext.length).toEqual(4); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, -
, - '.0:$firstHalfKey/.$keyZero' - ); - - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, -
, - '.0:$firstHalfKey/.$keyTwo' - ); - - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, -
, - '.0:$secondHalfKey/.$keyFour' - ); - - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, -
, - '.0:$keyFive/.$keyFiveInner' - ); - }); - - it('should retain key across two mappings', () => { - var zeroForceKey =
; - var oneForceKey =
; - var traverseContext = []; - var traverseFn = - jasmine.createSpy().and.callFake(function(context, kid, key, index) { - context.push(true); - }); - - var forcedKeys = ( -
- {zeroForceKey} - {oneForceKey} -
- ); - - traverseAllChildren(forcedKeys.props.children, traverseFn, traverseContext); - expect(traverseContext.length).toEqual(2); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, - zeroForceKey, - '.$keyZero' - ); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, - oneForceKey, - '.$keyOne' - ); - }); - - it('should be called for each child in an iterable without keys', () => { - spyOn(console, 'error'); - var threeDivIterable = { - '@@iterator': function() { - var i = 0; - return { - next: function() { - if (i++ < 3) { - return {value:
, done: false}; - } else { - return {value: undefined, done: true}; - } - }, - }; - }, - }; - - var traverseContext = []; - var traverseFn = - jasmine.createSpy().and.callFake(function(context, kid, key, index) { - context.push(kid); - }); - - var instance = ( -
- {threeDivIterable} -
- ); - - traverseAllChildren(instance.props.children, traverseFn, traverseContext); - expect(traverseFn.calls.count()).toBe(3); - - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, - traverseContext[0], - '.0' - ); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, - traverseContext[1], - '.1' - ); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, - traverseContext[2], - '.2' - ); - - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Warning: Each child in an array or iterator should have a unique "key" prop.' - ); - }); - - it('should be called for each child in an iterable with keys', () => { - var threeDivIterable = { - '@@iterator': function() { - var i = 0; - return { - next: function() { - if (i++ < 3) { - return {value:
, done: false}; - } else { - return {value: undefined, done: true}; - } - }, - }; - }, - }; - - var traverseContext = []; - var traverseFn = - jasmine.createSpy().and.callFake(function(context, kid, key, index) { - context.push(kid); - }); - - var instance = ( -
- {threeDivIterable} -
- ); - - traverseAllChildren(instance.props.children, traverseFn, traverseContext); - expect(traverseFn.calls.count()).toBe(3); - - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, - traverseContext[0], - '.$#1' - ); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, - traverseContext[1], - '.$#2' - ); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, - traverseContext[2], - '.$#3' - ); - }); - - it('should use keys from entry iterables', () => { - spyOn(console, 'error'); - - var threeDivEntryIterable = { - '@@iterator': function() { - var i = 0; - return { - next: function() { - if (i++ < 3) { - return {value: ['#' + i,
], done: false}; - } else { - return {value: undefined, done: true}; - } - }, - }; - }, - }; - threeDivEntryIterable.entries = threeDivEntryIterable['@@iterator']; - - var traverseContext = []; - var traverseFn = - jasmine.createSpy().and.callFake(function(context, kid, key, index) { - context.push(kid); - }); - - var instance = ( -
- {threeDivEntryIterable} -
- ); - - traverseAllChildren(instance.props.children, traverseFn, traverseContext); - expect(traverseFn.calls.count()).toBe(3); - - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, - traverseContext[0], - '.$#1:0' - ); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, - traverseContext[1], - '.$#2:0' - ); - expect(traverseFn).toHaveBeenCalledWith( - traverseContext, - traverseContext[2], - '.$#3:0' - ); - - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Warning: Using Maps as children is not yet fully supported. It is an ' + - 'experimental feature that might be removed. Convert it to a sequence ' + - '/ iterable of keyed ReactElements instead.' - ); - }); - - 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 { - var instance = ( -
- {5} - {12} - {13} -
- ); - - var traverseFn = jasmine.createSpy(); - - traverseAllChildren(instance.props.children, traverseFn, null); - expect(traverseFn.calls.count()).toBe(3); - - expect(traverseFn).toHaveBeenCalledWith( - null, - 5, - '.0' - ); - expect(traverseFn).toHaveBeenCalledWith( - null, - 12, - '.1' - ); - expect(traverseFn).toHaveBeenCalledWith( - null, - 13, - '.2' - ); - } 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 */ - - var instance = ( -
- {'a'} - {13} -
- ); - - var traverseFn = jasmine.createSpy(); - - traverseAllChildren(instance.props.children, traverseFn, null); - expect(traverseFn.calls.count()).toBe(2); - - expect(traverseFn).toHaveBeenCalledWith( - null, - 'a', - '.0' - ); - expect(traverseFn).toHaveBeenCalledWith( - null, - 13, - '.1' - ); - - delete String.prototype.key; - delete Number.prototype.key; - }); - - it('should throw on object', () => { - expect(function() { - traverseAllChildren({a: 1, b: 2}, function() {}, null); - }).toThrowError( - 'Objects are not valid as a React child (found: object with keys ' + - '{a, b}). If you meant to render a collection of children, use an ' + - 'array instead or wrap the object using createFragment(object) from ' + - 'the React add-ons.' - ); - }); - - 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() { - traverseAllChildren(/abc/, function() {}, null); - }).toThrowError( - 'Objects are not valid as a React child (found: /abc/). If you meant ' + - 'to render a collection of children, use an array instead or wrap the ' + - 'object using createFragment(object) from the React add-ons.' - ); - }); - - it('should warn for using maps as children with owner info', () => { - spyOn(console, 'error'); - - class Parent extends React.Component { - render() { - return ( -
{new Map([['foo', 0], ['bar', 1]])}
- ); - } - } - - ReactTestUtils.renderIntoDocument(); - - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: Using Maps as children is not yet fully supported. It is an ' + - 'experimental feature that might be removed. Convert it to a sequence ' + - '/ iterable of keyed ReactElements instead. Check the render method of `Parent`.' - ); - }); -}); diff --git a/src/shared/utils/getIteratorFn.js b/src/shared/utils/getIteratorFn.js index 4ed780aa204e..8958e63c4a53 100644 --- a/src/shared/utils/getIteratorFn.js +++ b/src/shared/utils/getIteratorFn.js @@ -30,7 +30,7 @@ var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec. * @param {?object} maybeIterable * @return {?function} */ -function getIteratorFn(maybeIterable: ?any): ?(p: ReactElement) => void { +function getIteratorFn(maybeIterable: ?any): ?(() => ?Iterator<*>) { var iteratorFn = maybeIterable && ( (ITERATOR_SYMBOL && maybeIterable[ITERATOR_SYMBOL]) || maybeIterable[FAUX_ITERATOR_SYMBOL]