Skip to content
This repository has been archived by the owner on Feb 12, 2022. It is now read-only.

Add React/Jest testing infrastructure #1118

Closed

Conversation

trueadm
Copy link
Contributor

@trueadm trueadm commented Oct 31, 2017

Release note: none

This PR adds the React/Jest testing infrastructure to Prepack and is stacked on the PR #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.

Copy link
Contributor

@gaearon gaearon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks OK to me aside from style nits and some things I don't fully understand.
I reviewed bcb80bd.

let React = require("react");
let ReactTestRenderer = require("react-test-renderer");
let { normalize } = require("../lib/utils/json.js");
/* eslint-disable no-undef */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this disable them globally for the whole file? I think you want // eslint-disable-next-line or something like that.

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
let compiledSource = serialized.code.replace(/_\$[\d].React/, "React = require('react')");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a comment explaining what the regex is trying to find.

let resultB = getTrials(rendererB, B);

// // the test has returned many values for us to check
if (Array.isArray(resultA) && Array.isArray(resultA[0])) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we restructure this so that instead of repeating similar logic in both branches, we first wrap resultA and resultB into an array if necessary and then do the assertions unconditionally?

// with a false-negative error
global.console.error = () => {};
await runTest(directory, "return-undefined.js");
global.console.error = originalConsoleError;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe try { } finally { } so this is isolated from other tests in case it fails?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this wasn't actual JS error that is occurring, rather it's console.error being fired?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If runTest fails for some reason, global.console.error won't be restored.

If you do

global.console.error = ...
try {
  doStuff
} finally {
  global.console.error = original
}

it is restored no matter what.

let requireValue = AbstractValue.createFromTemplate(
realm,
buildExpressionTemplate("require"),
(type: any), // Flow complains that type of Value isn't compatible with class Value :/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to understand why.

type JSON = { [key: string]: JSONValue };

// this will mutate the original JSON object
export function normalize(node: JSON) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This utility name sounds very generic but it's not very clear what this does.

What is normalized JSON looking like?
Is it useful in any other places than this test infra?

if (this.__createReactMock) {
var React = __createReactMock();
} else {
var React = require('react');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: indent


function App() {
return (
<MaybeShow show={true}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: indent

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(and in most places below)

superclass.apply(this, arguments);
}

if ( superclass ) StatefulComponent.__proto__ = superclass;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: style is odd, can we use the same code style as in code?

return StatefulComponent;
}(React.Component));

this.StatefulComponent = StatefulComponent;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this.* assignment here but not in other places?

@sebmarkbage
Copy link
Contributor

The move from test/serializer/react/test/serializer/jsx/. That implementation of JSX is still React specific. I've tried to keep the brand name around JSX separate from React. So it would be good if we could call this something with React in it. test/serializer/react/jsx/ maybe?

@@ -155,6 +155,39 @@ export default function(realm: Realm): void {
configurable: true,
});

if (realm.react.enabled) {
global.$DefineOwnProperty("__reactComponentRoots", {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cblappert This is unrelated to this PR. I saw that this pattern was added in the additional functions one, but why do we make these a public object instead of just an internal Map? Seems weird for the compiled script to be able to reason about this and mutate this.

Copy link

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trueadm has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

Copy link

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trueadm has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

facebook-github-bot pushed a commit that referenced this pull request Nov 6, 2017
Summary:
Release note: Adds experimental React functional component folding optimizations

This PR is stacked upon PRs #1118 and #1117. Thus, those PRs should be merged before this PR is merged to reduce noise in the diff.

This PR adds a new React Reconciler into Prepack's serialization process, so that React components trees can be folded/inlined into a single component at build time. To fold a component tree, it must be explicitly done via `__registerReactComponentRoot(nameOfComponent)`.

This PR only attempts to fold React functional components, not React ES2015 class components (that will come in another PR at a later date). Furthermore, the `props` parameter on a root component must contain Flow type annotations (otherwise we will have no idea what the values might be). Support flow `propTypes` might also be an addition, but not for this PR.

If the reconciler comes across a component that it cannot fold/inline, it will "bail-out" and try and continue the process without that particular component being folded into the tree.

An example of how this all works (input):

```jsx
function App(props: {title: string}) {
  return (
    <div>
      <ChildComponent title={props.title} />
    </div>
  );
}

function ChildComponent(props) {
  return (
    <span>
      <SubChildComponent {...props} />
    </span>
  );
}

function SubChildComponent(props) {
  return <span>{props.title.toString()}</span>
}

__registerReactComponentRoot(App);

global.App = App;
```

Output:
```jsx
(function () {
  "use strict";

  var _$1 = this;

  var _0 = function (props) {
    var _$0 = props.title;
    return <div><span><span>{_$0}</span></span></div>;
  };

  _$1.App = _0;
}).call(this);
```
Closes #1120

Differential Revision: D6237333

Pulled By: trueadm

fbshipit-source-id: b58c7d8979ca79a766bb2ee2eb01a380d37c3101
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants