2017-11-02 14:28:37 +03:00
|
|
|
/**
|
|
|
|
* 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 */
|
|
|
|
|
|
|
|
let fs = require("fs");
|
|
|
|
let path = require("path");
|
|
|
|
let { prepackSources } = require("../lib/prepack-node.js");
|
|
|
|
let babel = require("babel-core");
|
|
|
|
let React = require("react");
|
2018-02-16 18:58:44 +03:00
|
|
|
let PropTypes = require("prop-types");
|
2018-02-09 15:42:10 +03:00
|
|
|
let ReactRelay = require("react-relay");
|
2017-11-02 14:28:37 +03:00
|
|
|
let ReactTestRenderer = require("react-test-renderer");
|
|
|
|
let { mergeAdacentJSONTextNodes } = require("../lib/utils/json.js");
|
|
|
|
/* eslint-disable no-undef */
|
|
|
|
let { expect, describe, it } = global;
|
|
|
|
|
2018-02-06 01:08:06 +03:00
|
|
|
function cxShim(...args) {
|
|
|
|
let classNames = [];
|
|
|
|
for (let arg of args) {
|
|
|
|
if (typeof arg === "string") {
|
|
|
|
classNames.push(arg);
|
|
|
|
} else if (typeof arg === "object" && arg !== null) {
|
|
|
|
let keys = Object.keys(arg);
|
|
|
|
for (let key of keys) {
|
|
|
|
if (arg[key]) {
|
|
|
|
classNames.push(key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return classNames.join(" ");
|
|
|
|
}
|
|
|
|
|
|
|
|
// assign for tests that use the cx() global
|
|
|
|
global.cx = cxShim;
|
|
|
|
|
2018-02-16 18:58:44 +03:00
|
|
|
function getDataFile(directory, name) {
|
|
|
|
let reactTestRoot = path.join(__dirname, "../test/react/");
|
|
|
|
let data = fs.readFileSync(path.join(reactTestRoot, directory, name)).toString();
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
function runTestSuite(outputJsx) {
|
|
|
|
let reactTestRoot = path.join(__dirname, "../test/react/");
|
|
|
|
let prepackOptions = {
|
2018-01-24 22:23:51 +03:00
|
|
|
compatibility: "fb-www",
|
2017-12-16 19:04:37 +03:00
|
|
|
internalDebug: true,
|
|
|
|
serialize: true,
|
|
|
|
uniqueSuffix: "",
|
|
|
|
maxStackDepth: 100,
|
|
|
|
reactEnabled: true,
|
|
|
|
reactOutput: outputJsx ? "jsx" : "create-element",
|
|
|
|
inlineExpressions: true,
|
|
|
|
omitInvariants: true,
|
2018-01-17 10:14:14 +03:00
|
|
|
abstractEffectsInAdditionalFunctions: true,
|
2018-02-13 19:34:42 +03:00
|
|
|
stripFlow: true,
|
2017-11-02 14:28:37 +03:00
|
|
|
};
|
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
function compileSourceWithPrepack(source) {
|
|
|
|
let code = `(function(){${source}})()`;
|
|
|
|
let serialized = prepackSources([{ filePath: "", fileContents: code, sourceMapContents: "" }], prepackOptions);
|
|
|
|
if (serialized == null || serialized.reactStatistics == null) {
|
|
|
|
throw new Error("React test runner failed during serialization");
|
2017-11-02 14:28:37 +03:00
|
|
|
}
|
2017-12-16 19:04:37 +03:00
|
|
|
return {
|
|
|
|
compiledSource: serialized.code,
|
|
|
|
statistics: serialized.reactStatistics,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function runSource(source) {
|
|
|
|
let transformedSource = babel.transform(source, {
|
|
|
|
presets: ["babel-preset-react"],
|
|
|
|
plugins: ["transform-object-rest-spread"],
|
|
|
|
}).code;
|
|
|
|
/* eslint-disable no-new-func */
|
|
|
|
let fn = new Function("require", "module", transformedSource);
|
|
|
|
let moduleShim = { exports: null };
|
|
|
|
let requireShim = name => {
|
|
|
|
switch (name) {
|
2018-01-24 22:23:51 +03:00
|
|
|
case "React":
|
2017-12-16 19:04:37 +03:00
|
|
|
case "react":
|
|
|
|
return React;
|
2018-02-16 18:58:44 +03:00
|
|
|
case "PropTypes":
|
|
|
|
case "prop-types":
|
|
|
|
return PropTypes;
|
2018-01-24 22:23:51 +03:00
|
|
|
case "RelayModern":
|
2018-02-09 15:42:10 +03:00
|
|
|
return ReactRelay;
|
2018-02-06 01:08:06 +03:00
|
|
|
case "cx":
|
|
|
|
return cxShim;
|
2018-01-24 22:23:51 +03:00
|
|
|
case "FBEnvironment":
|
|
|
|
return {};
|
2017-12-16 19:04:37 +03:00
|
|
|
default:
|
|
|
|
throw new Error(`Unrecognized import: "${name}".`);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
try {
|
|
|
|
// $FlowFixMe flow doesn't new Function
|
|
|
|
fn(requireShim, moduleShim);
|
|
|
|
} catch (e) {
|
|
|
|
console.error(transformedSource);
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
return moduleShim.exports;
|
2017-11-02 14:28:37 +03:00
|
|
|
}
|
|
|
|
|
2018-02-16 18:58:44 +03:00
|
|
|
async function runTest(directory, name, data) {
|
2017-12-16 19:04:37 +03:00
|
|
|
let source = fs.readFileSync(path.join(reactTestRoot, directory, name)).toString();
|
2018-02-08 22:22:17 +03:00
|
|
|
let { compiledSource, statistics } = compileSourceWithPrepack(source);
|
2017-11-02 14:28:37 +03:00
|
|
|
|
2018-02-08 22:22:17 +03:00
|
|
|
expect(statistics).toMatchSnapshot();
|
2017-12-16 19:04:37 +03:00
|
|
|
let A = runSource(source);
|
|
|
|
let B = runSource(compiledSource);
|
2018-02-06 01:08:06 +03:00
|
|
|
|
2018-01-31 18:53:30 +03:00
|
|
|
expect(typeof A).toBe(typeof B);
|
|
|
|
if (typeof A !== "function") {
|
|
|
|
// Test without exports just verifies that the file compiles.
|
|
|
|
return;
|
|
|
|
}
|
2017-11-02 14:28:37 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
let rendererA = ReactTestRenderer.create(null);
|
|
|
|
let rendererB = ReactTestRenderer.create(null);
|
|
|
|
if (A == null || B == null) {
|
|
|
|
throw new Error("React test runner issue");
|
|
|
|
}
|
2018-01-31 18:53:30 +03:00
|
|
|
// Use the original version of the test in case transforming messes it up.
|
2017-12-16 19:04:37 +03:00
|
|
|
let { getTrials } = A;
|
2018-01-31 18:53:30 +03:00
|
|
|
// Run tests that assert the rendered output matches.
|
2018-02-16 18:58:44 +03:00
|
|
|
let resultA = getTrials(rendererA, A, data);
|
|
|
|
let resultB = getTrials(rendererB, B, data);
|
2017-12-16 19:04:37 +03:00
|
|
|
|
2018-01-31 18:53:30 +03:00
|
|
|
// The test has returned many values for us to check
|
2017-12-16 19:04:37 +03:00
|
|
|
for (let i = 0; i < resultA.length; i++) {
|
|
|
|
let [nameA, valueA] = resultA[i];
|
|
|
|
let [nameB, valueB] = resultB[i];
|
|
|
|
expect(mergeAdacentJSONTextNodes(valueB)).toEqual(mergeAdacentJSONTextNodes(valueA));
|
|
|
|
expect(nameB).toEqual(nameA);
|
|
|
|
}
|
2017-11-02 14:28:37 +03:00
|
|
|
}
|
|
|
|
|
2018-02-09 15:42:10 +03:00
|
|
|
async function stubReactRelay(f) {
|
|
|
|
let oldReactRelay = ReactRelay;
|
|
|
|
ReactRelay = {
|
|
|
|
QueryRenderer(props) {
|
|
|
|
return props.render({ props: {}, error: null });
|
|
|
|
},
|
|
|
|
createFragmentContainer() {
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
graphql() {
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
try {
|
|
|
|
await f();
|
|
|
|
} finally {
|
|
|
|
ReactRelay = oldReactRelay;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
// Jest tests
|
|
|
|
let originalConsoleError = console.error;
|
|
|
|
|
|
|
|
describe(`Test React (${outputJsx ? "JSX" : "create-element"})`, () => {
|
|
|
|
describe("Functional component folding", () => {
|
|
|
|
let directory = "functional-components";
|
2017-11-02 14:28:37 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Simple", async () => {
|
|
|
|
await runTest(directory, "simple.js");
|
|
|
|
});
|
2017-11-02 14:28:37 +03:00
|
|
|
|
2018-01-17 10:14:14 +03:00
|
|
|
it("Simple 2", async () => {
|
|
|
|
await runTest(directory, "simple-2.js");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("Simple 3", async () => {
|
|
|
|
await runTest(directory, "simple-3.js");
|
|
|
|
});
|
|
|
|
|
2018-01-26 14:04:15 +03:00
|
|
|
it("Simple 4", async () => {
|
|
|
|
await runTest(directory, "simple-4.js");
|
|
|
|
});
|
|
|
|
|
2018-01-26 22:47:51 +03:00
|
|
|
it("Simple 5", async () => {
|
|
|
|
await runTest(directory, "simple-5.js");
|
|
|
|
});
|
|
|
|
|
2018-01-31 00:59:52 +03:00
|
|
|
it("Simple 6", async () => {
|
|
|
|
await runTest(directory, "simple-6.js");
|
|
|
|
});
|
|
|
|
|
2018-02-16 16:14:45 +03:00
|
|
|
it("Simple 7", async () => {
|
|
|
|
await runTest(directory, "simple-7.js");
|
|
|
|
});
|
|
|
|
|
2018-02-09 01:58:22 +03:00
|
|
|
it("Simple fragments", async () => {
|
|
|
|
await runTest(directory, "simple-fragments.js");
|
|
|
|
});
|
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Simple children", async () => {
|
|
|
|
await runTest(directory, "simple-children.js");
|
|
|
|
});
|
2017-11-02 14:28:37 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Simple refs", async () => {
|
|
|
|
await runTest(directory, "simple-refs.js");
|
|
|
|
});
|
2017-11-02 14:28:37 +03:00
|
|
|
|
2018-01-26 22:53:17 +03:00
|
|
|
it("Circular reference", async () => {
|
|
|
|
await runTest(directory, "circular-reference.js");
|
|
|
|
});
|
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Conditional", async () => {
|
|
|
|
await runTest(directory, "conditional.js");
|
|
|
|
});
|
2017-11-02 14:28:37 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Key nesting", async () => {
|
|
|
|
await runTest(directory, "key-nesting.js");
|
|
|
|
});
|
2017-11-02 14:28:37 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Key nesting 2", async () => {
|
|
|
|
await runTest(directory, "key-nesting-2.js");
|
|
|
|
});
|
2017-11-02 14:28:37 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Key nesting 3", async () => {
|
|
|
|
await runTest(directory, "key-nesting-3.js");
|
|
|
|
});
|
2017-11-14 18:33:32 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Key change", async () => {
|
|
|
|
await runTest(directory, "key-change.js");
|
|
|
|
});
|
2017-11-14 18:33:32 +03:00
|
|
|
|
2018-02-09 01:58:22 +03:00
|
|
|
it("Key change with fragments", async () => {
|
|
|
|
await runTest(directory, "key-change-fragments.js");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("Key not changing with fragments", async () => {
|
|
|
|
await runTest(directory, "key-not-change-fragments.js");
|
|
|
|
});
|
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Component type change", async () => {
|
|
|
|
await runTest(directory, "type-change.js");
|
|
|
|
});
|
2017-11-02 14:28:37 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Component type same", async () => {
|
|
|
|
await runTest(directory, "type-same.js");
|
|
|
|
});
|
2017-11-02 14:28:37 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Dynamic props", async () => {
|
|
|
|
await runTest(directory, "dynamic-props.js");
|
|
|
|
});
|
2017-11-06 15:54:12 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Dynamic context", async () => {
|
|
|
|
await runTest(directory, "dynamic-context.js");
|
|
|
|
});
|
2017-11-02 14:28:37 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("React.cloneElement", async () => {
|
|
|
|
await runTest(directory, "clone-element.js");
|
|
|
|
});
|
2017-11-02 14:28:37 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Return text", async () => {
|
|
|
|
await runTest(directory, "return-text.js");
|
|
|
|
});
|
2017-11-02 14:28:37 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Return undefined", async () => {
|
|
|
|
// this test will cause a React console.error to show
|
|
|
|
// we monkey patch it to stop it polluting the test output
|
|
|
|
// with a false-negative error
|
|
|
|
global.console.error = () => {};
|
|
|
|
try {
|
|
|
|
await runTest(directory, "return-undefined.js");
|
|
|
|
} finally {
|
|
|
|
global.console.error = originalConsoleError;
|
|
|
|
}
|
|
|
|
});
|
2017-11-02 14:28:37 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Class component as root", async () => {
|
|
|
|
await runTest(directory, "class-root.js");
|
|
|
|
});
|
2017-11-16 16:12:31 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Class component as root with multiple render methods", async () => {
|
|
|
|
await runTest(directory, "class-root-with-render-methods.js");
|
|
|
|
});
|
2017-11-16 16:12:31 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Class component as root with props", async () => {
|
|
|
|
await runTest(directory, "class-root-with-props.js");
|
|
|
|
});
|
2017-11-16 16:12:31 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Class component as root with state", async () => {
|
|
|
|
await runTest(directory, "class-root-with-state.js");
|
|
|
|
});
|
2017-11-16 16:12:31 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Class component as root with refs", async () => {
|
|
|
|
await runTest(directory, "class-root-with-refs.js");
|
|
|
|
});
|
2017-11-16 16:12:31 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Class component as root with instance variables", async () => {
|
|
|
|
await runTest(directory, "class-root-with-instance-vars.js");
|
|
|
|
});
|
2017-11-16 16:12:31 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
it("Class component as root with instance variables #2", async () => {
|
|
|
|
await runTest(directory, "class-root-with-instance-vars-2.js");
|
|
|
|
});
|
2018-01-31 01:18:34 +03:00
|
|
|
|
|
|
|
it("Additional functions closure scope capturing", async () => {
|
|
|
|
await runTest(directory, "additional-function-regression.js");
|
|
|
|
});
|
2017-11-16 16:12:31 +03:00
|
|
|
});
|
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
describe("Class component folding", () => {
|
|
|
|
let directory = "class-components";
|
|
|
|
|
|
|
|
it("Simple classes", async () => {
|
|
|
|
await runTest(directory, "simple-classes.js");
|
|
|
|
});
|
2018-01-17 05:46:19 +03:00
|
|
|
|
|
|
|
it("Simple classes #2", async () => {
|
|
|
|
await runTest(directory, "simple-classes-2.js");
|
|
|
|
});
|
|
|
|
|
2018-01-31 01:52:01 +03:00
|
|
|
it("Simple classes #3", async () => {
|
|
|
|
await runTest(directory, "simple-classes-3.js");
|
|
|
|
});
|
|
|
|
|
2018-02-07 22:24:56 +03:00
|
|
|
it("Inheritance chaining", async () => {
|
|
|
|
await runTest(directory, "inheritance-chain.js");
|
|
|
|
});
|
|
|
|
|
2018-01-17 05:46:19 +03:00
|
|
|
it("Classes with state", async () => {
|
|
|
|
await runTest(directory, "classes-with-state.js");
|
|
|
|
});
|
2017-11-16 16:12:31 +03:00
|
|
|
});
|
2018-01-24 22:23:51 +03:00
|
|
|
|
2018-02-03 04:39:53 +03:00
|
|
|
describe("Factory class component folding", () => {
|
|
|
|
let directory = "factory-components";
|
|
|
|
|
|
|
|
it("Simple factory classes", async () => {
|
|
|
|
await runTest(directory, "simple.js");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("Simple factory classes 2", async () => {
|
|
|
|
await runTest(directory, "simple2.js");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-01-24 22:23:51 +03:00
|
|
|
describe("fb-www mocks", () => {
|
|
|
|
let directory = "mocks";
|
|
|
|
|
|
|
|
it("fb-www", async () => {
|
2018-02-09 15:42:10 +03:00
|
|
|
await stubReactRelay(async () => {
|
|
|
|
await runTest(directory, "fb1.js");
|
|
|
|
});
|
2018-01-24 22:23:51 +03:00
|
|
|
});
|
2018-01-26 18:45:32 +03:00
|
|
|
|
|
|
|
it("fb-www 2", async () => {
|
|
|
|
await runTest(directory, "fb2.js");
|
|
|
|
});
|
2018-01-31 18:53:30 +03:00
|
|
|
|
|
|
|
it("fb-www 3", async () => {
|
2018-02-09 15:42:10 +03:00
|
|
|
await stubReactRelay(async () => {
|
|
|
|
await runTest(directory, "fb3.js");
|
|
|
|
});
|
2018-01-31 18:53:30 +03:00
|
|
|
});
|
2018-01-31 22:48:33 +03:00
|
|
|
|
|
|
|
it("fb-www 4", async () => {
|
2018-02-09 15:42:10 +03:00
|
|
|
await stubReactRelay(async () => {
|
|
|
|
await runTest(directory, "fb4.js");
|
|
|
|
});
|
2018-01-31 22:48:33 +03:00
|
|
|
});
|
2018-02-02 15:41:47 +03:00
|
|
|
|
|
|
|
it("fb-www 5", async () => {
|
|
|
|
await runTest(directory, "fb5.js");
|
|
|
|
});
|
2018-02-06 01:08:06 +03:00
|
|
|
|
|
|
|
it("fb-www 6", async () => {
|
|
|
|
await runTest(directory, "fb6.js");
|
|
|
|
});
|
2018-02-09 15:42:10 +03:00
|
|
|
|
|
|
|
it("fb-www 7", async () => {
|
|
|
|
await runTest(directory, "fb7.js");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("fb-www 8", async () => {
|
|
|
|
await runTest(directory, "fb8.js");
|
|
|
|
});
|
2018-02-13 19:34:42 +03:00
|
|
|
|
|
|
|
it("repl example", async () => {
|
|
|
|
await runTest(directory, "repl-example.js");
|
|
|
|
});
|
2018-02-16 18:58:44 +03:00
|
|
|
|
|
|
|
it("Hacker News app", async () => {
|
|
|
|
let data = JSON.parse(getDataFile(directory, "hacker-news.json"));
|
|
|
|
await runTest(directory, "hacker-news.js", data);
|
|
|
|
});
|
2018-01-24 22:23:51 +03:00
|
|
|
});
|
2017-11-02 14:28:37 +03:00
|
|
|
});
|
2017-12-16 19:04:37 +03:00
|
|
|
}
|
2017-12-05 19:10:54 +03:00
|
|
|
|
2017-12-16 19:04:37 +03:00
|
|
|
runTestSuite(true);
|
|
|
|
runTestSuite(false);
|