Add full support for React.Children.map mock (#2519)

Summary:
Release notes: none

The `React.Children.map` mock was not fully finished and had side-effects. This fixes that and adds a test to show it properly working.
Pull Request resolved: https://github.com/facebook/prepack/pull/2519

Differential Revision: D9614166

Pulled By: trueadm

fbshipit-source-id: b646d13cc46f3747b07a111dc5bc9de295e25212
This commit is contained in:
Dominic Gannaway 2018-08-31 09:46:35 -07:00 committed by Facebook Github Bot
parent e0a90f82ba
commit 8f03c5f445
4 changed files with 183 additions and 23 deletions

View File

@ -17,13 +17,14 @@ import {
AbstractValue,
ECMAScriptSourceFunctionValue,
FunctionValue,
NativeFunctionValue,
NullValue,
NumberValue,
ObjectValue,
Value,
} from "../../values/index.js";
import { Environment } from "../../singletons.js";
import { getReactSymbol } from "../../react/utils.js";
import { createInternalReactElement, getReactSymbol } from "../../react/utils.js";
import { cloneReactElement, createReactElement } from "../../react/elements.js";
import { Properties, Create, To } from "../../singletons.js";
import invariant from "../../invariant.js";
@ -37,6 +38,7 @@ let reactCode = `
REACT_FRAGMENT_TYPE,
REACT_PORTAL_TYPE,
REACT_FORWARD_REF_TYPE,
ReactElement,
ReactCurrentOwner
) {
function makeEmptyFunction(arg) {
@ -123,30 +125,19 @@ let reactCode = `
var SEPARATOR = '.';
var SUBSEPARATOR = ':';
var POOL_SIZE = 10;
var traverseContextPool = [];
function getPooledTraverseContext(
mapResult,
keyPrefix,
mapFunction,
mapContext,
) {
if (traverseContextPool.length) {
const traverseContext = traverseContextPool.pop();
traverseContext.result = mapResult;
traverseContext.keyPrefix = keyPrefix;
traverseContext.func = mapFunction;
traverseContext.context = mapContext;
traverseContext.count = 0;
return traverseContext;
} else {
return {
result: mapResult,
keyPrefix: keyPrefix,
func: mapFunction,
context: mapContext,
count: 0,
};
}
return {
result: mapResult,
keyPrefix: keyPrefix,
func: mapFunction,
context: mapContext,
count: 0,
};
}
function releaseTraverseContext(traverseContext) {
@ -155,9 +146,6 @@ let reactCode = `
traverseContext.func = null;
traverseContext.context = null;
traverseContext.count = 0;
if (traverseContextPool.length < POOL_SIZE) {
traverseContextPool.push(traverseContext);
}
}
function traverseAllChildren(children, callback, traverseContext) {
@ -268,6 +256,40 @@ let reactCode = `
return subtreeCount;
}
function cloneAndReplaceKey(oldElement, newKey) {
var newElement = ReactElement(
oldElement.type,
newKey,
oldElement.ref,
oldElement.props,
);
return newElement;
}
function mapSingleChildIntoContext(bookKeeping, child, childKey) {
var {result, keyPrefix, func, context} = bookKeeping;
let mappedChild = func.call(context, child, bookKeeping.count++);
if (Array.isArray(mappedChild)) {
mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c);
} else if (mappedChild != null) {
if (isValidElement(mappedChild)) {
mappedChild = cloneAndReplaceKey(
mappedChild,
// Keep both the (mapped) and old keys if they differ, just as
// traverseAllChildren used to do for objects as children
keyPrefix +
(mappedChild.key && (!child || child.key !== mappedChild.key)
? escapeUserProvidedKey(mappedChild.key) + '/'
: '') +
childKey,
);
}
result.push(mappedChild);
}
}
function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
var escapedPrefix = '';
if (prefix != null) {
@ -284,7 +306,7 @@ let reactCode = `
}
function forEachSingleChild(bookKeeping, child, name) {
const {func, context} = bookKeeping;
var {func, context} = bookKeeping;
func.call(context, child, bookKeeping.count++);
}
@ -409,11 +431,23 @@ export function createMockReact(realm: Realm, reactRequireName: string): ObjectV
let factory = reactFactory.$Call;
invariant(factory !== undefined);
let mockReactElementBuilder = new NativeFunctionValue(
realm,
undefined,
"ReactElement",
0,
(context, [type, key, ref, props]) => {
invariant(props instanceof ObjectValue);
return createInternalReactElement(realm, type, key, ref, props);
}
);
let reactValue = factory(realm.intrinsics.undefined, [
getReactSymbol("react.element", realm),
getReactSymbol("react.fragment", realm),
getReactSymbol("react.portal", realm),
getReactSymbol("react.forward_ref", realm),
mockReactElementBuilder,
currentOwner,
]);
invariant(reactValue instanceof ObjectValue);

View File

@ -136,6 +136,10 @@ it("Bound type 2", () => {
runTest(__dirname + "/FunctionalComponents/bound-type2.js");
});
it("React.Children.map", () => {
runTest(__dirname + "/FunctionalComponents/react-children-map.js");
});
it("Two roots", () => {
runTest(__dirname + "/FunctionalComponents/two-roots.js");
});

View File

@ -0,0 +1,26 @@
var React = require("react");
function Child(props) {
return React.Children.map(props.children, function(child) {
return <span>{child}</span>;
});
}
function App() {
return (
<div>
<Child>{[1, 2, 3]}</Child>
</div>
);
}
App.getTrials = function(renderer, Root) {
renderer.update(<Root />);
return [["simple conditions #3", renderer.toJSON()]];
};
if (this.__optimizeReactComponentTree) {
__optimizeReactComponentTree(App);
}
module.exports = App;

View File

@ -8944,6 +8944,102 @@ ReactStatistics {
}
`;
exports[`React.Children.map: (JSX => JSX) 1`] = `
ReactStatistics {
"componentsEvaluated": 2,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 1,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`React.Children.map: (JSX => createElement) 1`] = `
ReactStatistics {
"componentsEvaluated": 2,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 1,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`React.Children.map: (createElement => JSX) 1`] = `
ReactStatistics {
"componentsEvaluated": 2,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 1,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`React.Children.map: (createElement => createElement) 1`] = `
ReactStatistics {
"componentsEvaluated": 2,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 1,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`React.cloneElement 2: (JSX => JSX) 1`] = `
ReactStatistics {
"componentsEvaluated": 1,