prepack/scripts/test-react.js

177 lines
5.2 KiB
JavaScript
Raw Normal View History

Add React/Jest testing infrastructure Summary: Release note: none This PR adds the React/Jest testing infrastructure to Prepack and is stacked on the PR https://github.com/facebook/prepack/pull/1117. Thus, that PR should be merged before this PR is merged to reduce noise in the diff. This will allow us to test various runtime outputs of React 16 when running original source vs Prepacked source to see if there are any issues/differences that might have an impact on applications. The primary reason for this is to track regressions for the component folding PRs that will land in the future. Please note, this PR does not contain any reconciler changes, thus `__registerReactComponentRoot(App);` has been commented out of tests to ensure they don't prematurely fail. A follow up PR will enable them once the other React functional component folding PRs get landed. This PR also adds some mock React globals to be used within tests (and maybe to be further integrated into folding at a future point too). The mocks include `React.Component` and `React.cloneElement` for now. Furthermore, a `utils/json.js` utility file exists to help normalize the results from the React test renderer so that adjacent JSON text nodes get merged, which is something that may exist because of how the reconciler (once the PR lands) handles inlining of child nodes. The command to run React tests is `yarn test-react`. Closes https://github.com/facebook/prepack/pull/1118 Reviewed By: cblappert Differential Revision: D6208263 Pulled By: trueadm fbshipit-source-id: d54f3b0e1cc3240e1f142d3da08bc279e4153889
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");
let ReactTestRenderer = require("react-test-renderer");
let { mergeAdacentJSONTextNodes } = require("../lib/utils/json.js");
/* eslint-disable no-undef */
let { expect, describe, it } = global;
let reactTestRoot = path.join(__dirname, "../test/react/");
let prepackOptions = {
compatibility: "react-mocks",
errorHandler: diag => "Fail",
internalDebug: true,
serialize: true,
uniqueSuffix: "",
maxStackDepth: 100,
reactEnabled: true,
inlineExpressions: true,
omitInvariants: true,
};
function compileSourceWithPrepack(source) {
let code = `(function(){${source}})()`;
let serialized = prepackSources([{ filePath: "", fileContents: code, sourceMapContents: "" }], prepackOptions);
// add the React require back in, as we've removed it with our Prepack mock
// the regex checks for any Prepack variable that matches "_$**any digit**.React"
let compiledSource = serialized.code.replace(/_\$[\d].React/, "React = require('react')");
if (serialized == null || serialized.reactStatistics == null) {
throw new Error("React test runner failed during serialization");
}
return {
// replace the code to put back the generator (Prepack doesn't serialize them yet)
compiledSource,
statistics: serialized.reactStatistics,
};
}
function runSource(source) {
let codeAfterBabel = babel.transform(source, {
presets: ["babel-preset-react"],
plugins: ["transform-object-rest-spread"],
}).code;
/* eslint-disable no-new-func */
let fn = new Function("require", "module", codeAfterBabel);
let moduleShim = { exports: null };
let requireShim = name => {
switch (name) {
case "react":
return React;
default:
throw new Error(`Unrecognized import: "${name}".`);
}
};
let global = {
require: requireShim,
module: moduleShim,
Object,
String,
};
try {
// $FlowFixMe flow doesn't new Function
fn.call(global, requireShim, moduleShim);
} catch (e) {
console.log(codeAfterBabel);
throw e;
}
return moduleShim.exports;
}
async function runTest(directory, name) {
let source = fs.readFileSync(path.join(reactTestRoot, directory, name)).toString();
let { compiledSource } = compileSourceWithPrepack(source);
let A = runSource(source);
expect(typeof A).toBe("function");
let B = runSource(compiledSource);
expect(typeof B).toBe("function");
let rendererA = ReactTestRenderer.create(null);
let rendererB = ReactTestRenderer.create(null);
if (A == null || B == null) {
throw new Error("React test runner issue");
}
// // Use the original version of the test in case transforming messes it up.
let { getTrials } = A;
// // Run tests that assert the rendered output matches.
let resultA = getTrials(rendererA, A);
let resultB = getTrials(rendererB, B);
// // the test has returned many values for us to check
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);
}
}
// Jest tests
let originalConsoleError = console.error;
describe("Test React", () => {
describe("Functional component folding", () => {
let directory = "functional-components";
it("Simple", async () => {
await runTest(directory, "simple.js");
});
it("Simple children", async () => {
await runTest(directory, "simple-children.js");
});
it("Simple refs", async () => {
await runTest(directory, "simple-refs.js");
});
it("Conditional", async () => {
await runTest(directory, "conditional.js");
});
it("Key nesting", async () => {
await runTest(directory, "key-nesting.js");
});
it("Key change", async () => {
await runTest(directory, "key-change.js");
});
it("Component type change", async () => {
await runTest(directory, "type-change.js");
});
it("Dynamic props", async () => {
await runTest(directory, "dynamic-props.js");
});
it("Dynamic context", async () => {
await runTest(directory, "dynamic-context.js");
});
it("React.cloneElement", async () => {
await runTest(directory, "clone-element.js");
});
it("Return text", async () => {
await runTest(directory, "return-text.js");
});
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;
}
});
});
});