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]