mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-08-16 18:10:33 +03:00
Changes to React mocks for internal testing
Summary: Release notes: none This PR changes `react-mocks` -> `fb-www` and adds some `RelayModern` mocks. This is mainly for internal testing to unblock work. Closes https://github.com/facebook/prepack/pull/1347 Differential Revision: D6798242 Pulled By: trueadm fbshipit-source-id: 9c8a4aeef074e2a29e44fdd26352633f47bbc862
This commit is contained in:
parent
577694ceb7
commit
b891ee1166
@ -22,7 +22,7 @@ let { expect, describe, it } = global;
|
||||
function runTestSuite(outputJsx) {
|
||||
let reactTestRoot = path.join(__dirname, "../test/react/");
|
||||
let prepackOptions = {
|
||||
compatibility: "react-mocks",
|
||||
compatibility: "fb-www",
|
||||
internalDebug: true,
|
||||
serialize: true,
|
||||
uniqueSuffix: "",
|
||||
@ -57,8 +57,20 @@ function runTestSuite(outputJsx) {
|
||||
let moduleShim = { exports: null };
|
||||
let requireShim = name => {
|
||||
switch (name) {
|
||||
case "React":
|
||||
case "react":
|
||||
return React;
|
||||
case "RelayModern":
|
||||
return {
|
||||
QueryRenderer(props) {
|
||||
return props.render({ props: {}, error: null });
|
||||
},
|
||||
graphql() {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
case "FBEnvironment":
|
||||
return {};
|
||||
default:
|
||||
throw new Error(`Unrecognized import: "${name}".`);
|
||||
}
|
||||
@ -230,6 +242,14 @@ function runTestSuite(outputJsx) {
|
||||
await runTest(directory, "classes-with-state.js");
|
||||
});
|
||||
});
|
||||
|
||||
describe("fb-www mocks", () => {
|
||||
let directory = "mocks";
|
||||
|
||||
it("fb-www", async () => {
|
||||
await runTest(directory, "fb1.js");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -13,14 +13,14 @@ import type { Realm } from "./realm.js";
|
||||
import initializePrepackGlobals from "./intrinsics/prepack/global.js";
|
||||
import initializeDOMGlobals from "./intrinsics/dom/global.js";
|
||||
import initializeReactNativeGlobals from "./intrinsics/react-native/global.js";
|
||||
import initializeReactMocks from "./intrinsics/react-mocks/global.js";
|
||||
import initializeReactMocks from "./intrinsics/fb-www/global.js";
|
||||
|
||||
export default function(realm: Realm): Realm {
|
||||
initializePrepackGlobals(realm);
|
||||
if (realm.isCompatibleWith("browser")) {
|
||||
initializeDOMGlobals(realm);
|
||||
}
|
||||
if (realm.isCompatibleWith("react-mocks")) {
|
||||
if (realm.isCompatibleWith("fb-www")) {
|
||||
initializeDOMGlobals(realm);
|
||||
initializeReactMocks(realm);
|
||||
}
|
||||
|
@ -12,7 +12,8 @@
|
||||
import type { Realm } from "../../realm.js";
|
||||
import { AbstractValue, NativeFunctionValue, Value, StringValue } from "../../values/index.js";
|
||||
import buildExpressionTemplate from "../../utils/builder.js";
|
||||
import { createMockReact } from "./mocks.js";
|
||||
import { createMockReact } from "./react-mocks.js";
|
||||
import { createMockReactRelay } from "./relay-mocks.js";
|
||||
import invariant from "../../invariant";
|
||||
|
||||
export default function(realm: Realm): void {
|
||||
@ -31,13 +32,22 @@ export default function(realm: Realm): void {
|
||||
global.$DefineOwnProperty("require", {
|
||||
value: new NativeFunctionValue(realm, "global.require", "require", 0, (context, [requireNameVal]) => {
|
||||
invariant(requireNameVal instanceof StringValue);
|
||||
if (requireNameVal.value === "react" || requireNameVal.value === "React") {
|
||||
if (realm.react.reactLibraryObject === undefined) {
|
||||
let reactLibraryObject = createMockReact(realm);
|
||||
realm.react.reactLibraryObject = reactLibraryObject;
|
||||
return reactLibraryObject;
|
||||
let requireNameValValue = requireNameVal.value;
|
||||
|
||||
if (requireNameValValue === "react" || requireNameValValue === "React") {
|
||||
if (realm.fbLibraries.react === undefined) {
|
||||
let react = createMockReact(realm, requireNameValValue);
|
||||
realm.fbLibraries.react = react;
|
||||
return react;
|
||||
}
|
||||
return realm.react.reactLibraryObject;
|
||||
return realm.fbLibraries.react;
|
||||
} else if (requireNameValValue === "react-relay" || requireNameValValue === "RelayModern") {
|
||||
if (realm.fbLibraries.reactRelay === undefined) {
|
||||
let reactRelay = createMockReactRelay(realm, requireNameValValue);
|
||||
realm.fbLibraries.reactRelay = reactRelay;
|
||||
return reactRelay;
|
||||
}
|
||||
return realm.fbLibraries.reactRelay;
|
||||
}
|
||||
let requireName = `require("${requireNameVal.value}")`;
|
||||
let type = Value.getTypeFromName("function");
|
@ -251,7 +251,7 @@ let reactCode = `
|
||||
`;
|
||||
let reactAst = parseExpression(reactCode, { plugins: ["flow"] });
|
||||
|
||||
export function createMockReact(realm: Realm): ObjectValue {
|
||||
export function createMockReact(realm: Realm, reactRequireName: string): ObjectValue {
|
||||
let reactFactory = Environment.GetValue(realm, realm.$GlobalEnv.evaluate(reactAst, false));
|
||||
invariant(reactFactory instanceof ECMAScriptSourceFunctionValue);
|
||||
|
||||
@ -268,35 +268,35 @@ export function createMockReact(realm: Realm): ObjectValue {
|
||||
getReactSymbol("react.symbol", realm),
|
||||
currentOwner,
|
||||
]);
|
||||
reactValue.intrinsicName = `require("react")`;
|
||||
reactValue.intrinsicName = `require("${reactRequireName}")`;
|
||||
invariant(reactValue instanceof ObjectValue);
|
||||
|
||||
let reactComponentValue = Get(realm, reactValue, "Component");
|
||||
reactComponentValue.intrinsicName = `require("react").Component`;
|
||||
reactComponentValue.intrinsicName = `require("${reactRequireName}").Component`;
|
||||
invariant(reactComponentValue instanceof ECMAScriptFunctionValue);
|
||||
let reactPureComponentValue = Get(realm, reactValue, "PureComponent");
|
||||
reactPureComponentValue.intrinsicName = `require("react").PureComponent`;
|
||||
reactPureComponentValue.intrinsicName = `require("${reactRequireName}").PureComponent`;
|
||||
invariant(reactPureComponentValue instanceof ECMAScriptFunctionValue);
|
||||
reactComponentValue.$FunctionKind = "normal";
|
||||
invariant(reactComponentValue instanceof ObjectValue);
|
||||
|
||||
let reactComponentPrototypeValue = Get(realm, reactComponentValue, "prototype");
|
||||
reactComponentPrototypeValue.intrinsicName = `require("react").Component.prototype`;
|
||||
reactComponentPrototypeValue.intrinsicName = `require("${reactRequireName}").Component.prototype`;
|
||||
|
||||
let reactPureComponentPrototypeValue = Get(realm, reactPureComponentValue, "prototype");
|
||||
reactPureComponentPrototypeValue.intrinsicName = `require("react").PureComponent.prototype`;
|
||||
reactPureComponentPrototypeValue.intrinsicName = `require("${reactRequireName}").PureComponent.prototype`;
|
||||
|
||||
let reactCloneElementValue = Get(realm, reactValue, "cloneElement");
|
||||
reactCloneElementValue.intrinsicName = `require("react").cloneElement`;
|
||||
reactCloneElementValue.intrinsicName = `require("${reactRequireName}").cloneElement`;
|
||||
|
||||
let reactCreateElementValue = Get(realm, reactValue, "createElement");
|
||||
reactCreateElementValue.intrinsicName = `require("react").createElement`;
|
||||
reactCreateElementValue.intrinsicName = `require("${reactRequireName}").createElement`;
|
||||
|
||||
let reactIsValidElementValue = Get(realm, reactValue, "isValidElement");
|
||||
reactIsValidElementValue.intrinsicName = `require("react").isValidElement`;
|
||||
reactIsValidElementValue.intrinsicName = `require("${reactRequireName}").isValidElement`;
|
||||
|
||||
let reactChildrenValue = Get(realm, reactValue, "Children");
|
||||
reactChildrenValue.intrinsicName = `require("react").Children`;
|
||||
reactChildrenValue.intrinsicName = `require("${reactRequireName}").Children`;
|
||||
|
||||
return reactValue;
|
||||
}
|
33
src/intrinsics/fb-www/relay-mocks.js
Normal file
33
src/intrinsics/fb-www/relay-mocks.js
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2017-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.
|
||||
*/
|
||||
|
||||
/* @flow */
|
||||
|
||||
import type { Realm } from "../../realm.js";
|
||||
import { ObjectValue } from "../../values/index.js";
|
||||
import { Create } from "../../singletons.js";
|
||||
import { createAbstract } from "../prepack/utils.js";
|
||||
|
||||
export function createMockReactRelay(realm: Realm, relayRequireName: string): ObjectValue {
|
||||
let reactRelay = new ObjectValue(realm, realm.intrinsics.ObjectPrototype, `require("${relayRequireName}")`, true);
|
||||
// for QueryRenderer, we want to leave the component alone but process it's "render" prop
|
||||
let queryRendererComponent = createAbstract(realm, "function", `require("${relayRequireName}").QueryRenderer`);
|
||||
Create.CreateDataPropertyOrThrow(realm, reactRelay, "QueryRenderer", queryRendererComponent);
|
||||
|
||||
let graphql = createAbstract(realm, "function", `require("${relayRequireName}").graphql`);
|
||||
Create.CreateDataPropertyOrThrow(realm, reactRelay, "graphql", graphql);
|
||||
|
||||
let createFragmentContainer = createAbstract(
|
||||
realm,
|
||||
"function",
|
||||
`require("${relayRequireName}").createFragmentContainer`
|
||||
);
|
||||
Create.CreateDataPropertyOrThrow(realm, reactRelay, "createFragmentContainer", createFragmentContainer);
|
||||
return reactRelay;
|
||||
}
|
@ -11,15 +11,8 @@
|
||||
|
||||
import type { ErrorHandler } from "./errors.js";
|
||||
|
||||
export type Compatibility = "browser" | "jsc-600-1-4-17" | "mobile" | "node-source-maps" | "node-cli" | "react-mocks";
|
||||
export const CompatibilityValues = [
|
||||
"browser",
|
||||
"jsc-600-1-4-17",
|
||||
"mobile",
|
||||
"node-source-maps",
|
||||
"node-cli",
|
||||
"react-mocks",
|
||||
];
|
||||
export type Compatibility = "browser" | "jsc-600-1-4-17" | "mobile" | "node-source-maps" | "node-cli" | "fb-www";
|
||||
export const CompatibilityValues = ["browser", "jsc-600-1-4-17", "mobile", "node-source-maps", "node-cli", "fb-www"];
|
||||
export type ReactOutputTypes = "create-element" | "jsx";
|
||||
|
||||
export type RealmOptions = {
|
||||
|
@ -25,6 +25,7 @@ import {
|
||||
import { type ReactSerializerState } from "../serializer/types.js";
|
||||
import { isReactElement, addKeyToReactElement, mapOverArrayValue } from "./utils";
|
||||
import { ExpectedBailOut } from "./errors.js";
|
||||
import invariant from "../invariant";
|
||||
|
||||
// Branch status is used for when Prepack returns an abstract value from a render
|
||||
// that results in a conditional path occuring. This can be problematic for reconcilation
|
||||
@ -90,7 +91,8 @@ export class BranchState {
|
||||
}
|
||||
}
|
||||
}
|
||||
captureBranchedValue(type: StringValue | ECMAScriptSourceFunctionValue, value: Value): Value {
|
||||
captureBranchedValue(type: Value, value: Value): Value {
|
||||
invariant(type instanceof ECMAScriptSourceFunctionValue || type instanceof StringValue);
|
||||
this._branchesToValidate.push({ type, value });
|
||||
return value;
|
||||
}
|
||||
|
@ -29,16 +29,21 @@ const lifecycleMethods = new Set([
|
||||
"componentWillReceiveProps",
|
||||
]);
|
||||
|
||||
export function getInitialProps(realm: Realm, componentType: ECMAScriptSourceFunctionValue): AbstractObjectValue {
|
||||
export function getInitialProps(
|
||||
realm: Realm,
|
||||
componentType: ECMAScriptSourceFunctionValue | null
|
||||
): AbstractObjectValue {
|
||||
let propsName = null;
|
||||
if (valueIsClassComponent(realm, componentType)) {
|
||||
propsName = "this.props";
|
||||
} else {
|
||||
// otherwise it's a functional component, where the first paramater of the function is "props" (if it exists)
|
||||
if (componentType.$FormalParameters.length > 0) {
|
||||
let firstParam = componentType.$FormalParameters[0];
|
||||
if (t.isIdentifier(firstParam)) {
|
||||
propsName = ((firstParam: any): BabelNodeIdentifier).name;
|
||||
if (componentType !== null) {
|
||||
if (valueIsClassComponent(realm, componentType)) {
|
||||
propsName = "this.props";
|
||||
} else {
|
||||
// otherwise it's a functional component, where the first paramater of the function is "props" (if it exists)
|
||||
if (componentType.$FormalParameters.length > 0) {
|
||||
let firstParam = componentType.$FormalParameters[0];
|
||||
if (t.isIdentifier(firstParam)) {
|
||||
propsName = ((firstParam: any): BabelNodeIdentifier).name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ import { BranchState, type BranchStatusEnum } from "./branching.js";
|
||||
import { getInitialProps, getInitialContext, createClassInstance, createSimpleClassInstance } from "./components.js";
|
||||
import { ExpectedBailOut, SimpleClassBailOut } from "./errors.js";
|
||||
|
||||
type RenderStrategy = "NORMAL" | "RELAY_QUERY_RENDERER";
|
||||
|
||||
export class Reconciler {
|
||||
constructor(
|
||||
realm: Realm,
|
||||
@ -149,13 +151,26 @@ export class Reconciler {
|
||||
return componentType.$Call(this.realm.intrinsics.undefined, [props, context]);
|
||||
}
|
||||
|
||||
_renderRelayQueryRendererComponent(
|
||||
reactElement: ObjectValue,
|
||||
props: ObjectValue | AbstractObjectValue,
|
||||
context: ObjectValue | AbstractObjectValue
|
||||
) {
|
||||
// TODO: for now we do nothing, in the future we want to evaluate the render prop of this component
|
||||
return {
|
||||
result: reactElement,
|
||||
childContext: context,
|
||||
};
|
||||
}
|
||||
|
||||
_renderComponent(
|
||||
componentType: ECMAScriptSourceFunctionValue,
|
||||
componentType: Value,
|
||||
props: ObjectValue | AbstractObjectValue,
|
||||
context: ObjectValue | AbstractObjectValue,
|
||||
branchStatus: BranchStatusEnum,
|
||||
branchState: BranchState | null
|
||||
) {
|
||||
invariant(componentType instanceof ECMAScriptSourceFunctionValue);
|
||||
let value;
|
||||
let childContext = context;
|
||||
|
||||
@ -207,6 +222,17 @@ export class Reconciler {
|
||||
};
|
||||
}
|
||||
|
||||
_getRenderStrategy(func: Value): RenderStrategy {
|
||||
// check if it's a ReactRelay.QueryRenderer
|
||||
if (this.realm.fbLibraries.reactRelay !== undefined) {
|
||||
let QueryRenderer = Get(this.realm, this.realm.fbLibraries.reactRelay, "QueryRenderer");
|
||||
if (func === QueryRenderer) {
|
||||
return "RELAY_QUERY_RENDERER";
|
||||
}
|
||||
}
|
||||
return "NORMAL";
|
||||
}
|
||||
|
||||
_resolveDeeply(
|
||||
value: Value,
|
||||
context: ObjectValue | AbstractObjectValue,
|
||||
@ -275,7 +301,9 @@ export class Reconciler {
|
||||
);
|
||||
return reactElement;
|
||||
}
|
||||
if (!(typeValue instanceof ECMAScriptSourceFunctionValue)) {
|
||||
let renderStrategy = this._getRenderStrategy(typeValue);
|
||||
|
||||
if (renderStrategy === "NORMAL" && !(typeValue instanceof ECMAScriptSourceFunctionValue)) {
|
||||
this._assignBailOutMessage(
|
||||
reactElement,
|
||||
`Bail-out: type on <Component /> was not a ECMAScriptSourceFunctionValue`
|
||||
@ -283,13 +311,28 @@ export class Reconciler {
|
||||
return reactElement;
|
||||
}
|
||||
try {
|
||||
let { result } = this._renderComponent(
|
||||
typeValue,
|
||||
propsValue,
|
||||
context,
|
||||
branchStatus === "NEW_BRANCH" ? "BRANCH" : branchStatus,
|
||||
null
|
||||
);
|
||||
let result;
|
||||
switch (renderStrategy) {
|
||||
case "NORMAL": {
|
||||
let render = this._renderComponent(
|
||||
typeValue,
|
||||
propsValue,
|
||||
context,
|
||||
branchStatus === "NEW_BRANCH" ? "BRANCH" : branchStatus,
|
||||
null
|
||||
);
|
||||
result = render.result;
|
||||
break;
|
||||
}
|
||||
case "RELAY_QUERY_RENDERER": {
|
||||
let render = this._renderRelayQueryRendererComponent(reactElement, propsValue, context);
|
||||
result = render.result;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
invariant(false, "unsupported render strategy");
|
||||
}
|
||||
|
||||
if (result instanceof UndefinedValue) {
|
||||
this._assignBailOutMessage(reactElement, `Bail-out: undefined was returned from render`);
|
||||
if (branchStatus === "NEW_BRANCH" && branchState) {
|
||||
|
@ -97,7 +97,7 @@ export function valueIsClassComponent(realm: Realm, value: Value): boolean {
|
||||
|
||||
// logger isn't typed otherwise it will increase flow cycle length :()
|
||||
export function valueIsReactLibraryObject(realm: Realm, value: ObjectValue, logger: any): boolean {
|
||||
if (realm.react.reactLibraryObject === value) {
|
||||
if (realm.fbLibraries.react === value) {
|
||||
return true;
|
||||
}
|
||||
// we check that the object is the React or React-like library by checking for
|
||||
|
18
src/realm.js
18
src/realm.js
@ -176,11 +176,18 @@ export class Realm {
|
||||
output: opts.reactOutput || "create-element",
|
||||
symbols: new Map(),
|
||||
currentOwner: undefined,
|
||||
reactLibraryObject: undefined,
|
||||
hoistableReactElements: new WeakMap(),
|
||||
hoistableFunctions: new WeakMap(),
|
||||
};
|
||||
|
||||
this.fbLibraries = {
|
||||
react: undefined,
|
||||
reactRelay: undefined,
|
||||
cx: undefined,
|
||||
fbt: undefined,
|
||||
jsResource: undefined,
|
||||
};
|
||||
|
||||
this.errorHandler = opts.errorHandler;
|
||||
|
||||
this.globalSymbolRegistry = [];
|
||||
@ -227,11 +234,18 @@ export class Realm {
|
||||
output?: ReactOutputTypes,
|
||||
symbols: Map<ReactSymbolTypes, SymbolValue>,
|
||||
currentOwner?: ObjectValue,
|
||||
reactLibraryObject?: ObjectValue,
|
||||
hoistableReactElements: WeakMap<ObjectValue, boolean>,
|
||||
hoistableFunctions: WeakMap<FunctionValue, boolean>,
|
||||
};
|
||||
|
||||
fbLibraries: {
|
||||
react: void | ObjectValue,
|
||||
reactRelay: void | ObjectValue,
|
||||
cx: void | ObjectValue,
|
||||
fbt: void | ObjectValue,
|
||||
jsResource: void | ObjectValue,
|
||||
};
|
||||
|
||||
$GlobalObject: ObjectValue | AbstractObjectValue;
|
||||
compatibility: Compatibility;
|
||||
|
||||
|
@ -606,7 +606,7 @@ export class ResidualHeapVisitor {
|
||||
this.logger.logError(val, `Arguments object is not supported in residual heap.`);
|
||||
}
|
||||
if (this.realm.react.enabled && valueIsReactLibraryObject(this.realm, val, this.logger)) {
|
||||
this.realm.react.reactLibraryObject = val;
|
||||
this.realm.fbLibraries.react = val;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -920,7 +920,7 @@ export class ResidualHeapVisitor {
|
||||
|
||||
_visitReactLibrary() {
|
||||
// find and visit the React library
|
||||
let reactLibraryObject = this.realm.react.reactLibraryObject;
|
||||
let reactLibraryObject = this.realm.fbLibraries.react;
|
||||
if (this.realm.react.output === "jsx") {
|
||||
// React might not be defined in scope, i.e. another library is using JSX
|
||||
// we don't throw an error as we should support JSX stand-alone
|
||||
|
@ -125,7 +125,7 @@ export class ResidualReactElements {
|
||||
}
|
||||
}
|
||||
}
|
||||
let reactLibraryObject = this.realm.react.reactLibraryObject;
|
||||
let reactLibraryObject = this.realm.fbLibraries.react;
|
||||
let shouldHoist =
|
||||
this.residualHeapSerializer.currentFunctionBody !== this.residualHeapSerializer.mainBody &&
|
||||
canHoistReactElement(this.realm, val);
|
||||
|
32
test/react/mocks/fb1.js
Normal file
32
test/react/mocks/fb1.js
Normal file
@ -0,0 +1,32 @@
|
||||
var React = require('React');
|
||||
// the JSX transform converts to React, so we need to add it back in
|
||||
this['React'] = React;
|
||||
var {QueryRenderer, graphql} = require('RelayModern');
|
||||
|
||||
var FBEnvironment = require('FBEnvironment');
|
||||
|
||||
function App({ initialNumComments, someVariables, query, pageSize, onCommit }) {
|
||||
return (
|
||||
<QueryRenderer
|
||||
environment={FBEnvironment}
|
||||
query={graphql`
|
||||
${query}
|
||||
`}
|
||||
variables={someVariables}
|
||||
render={({error, props}) => {
|
||||
return <span>Hello world</span>
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
App.getTrials = function(renderer, Root) {
|
||||
renderer.update(<Root />);
|
||||
return [['fb1 mocks', renderer.toJSON()]];
|
||||
};
|
||||
|
||||
if (this.__registerReactComponentRoot) {
|
||||
__registerReactComponentRoot(App);
|
||||
}
|
||||
|
||||
module.exports = App;
|
@ -40,7 +40,7 @@ var optionsConfig = [
|
||||
{
|
||||
type: "choice",
|
||||
name: "compatibility",
|
||||
choices: ["browser", "jsc-600-1-4-17", "node-source-maps", "node-cli", "react-mocks"],
|
||||
choices: ["browser", "jsc-600-1-4-17", "node-source-maps", "node-cli", "fb-wwww"],
|
||||
defaultVal: "browser",
|
||||
description: "The target environment for Prepack"
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user