@samitouri / QOS-React-1 / commits / 2a5a8d552f

[tests] Revive Forget e2e tests

--- Revives e2e test infra from #587. - All React component-like functions are compiled. - `yarn jest` runs each e2e test twice (forget and no forget) Github Actions is already running `yarn test`, which includes all jest tests ``` Run yarn test yarn run v1.22.19 $ yarn workspaces run test > babel-plugin-react-forget $ yarn jest && yarn snap:build && yarn snap $ tsc && jest PASS main src/__tests__/Result-test.ts PASS main src/__tests__/DisjointSet-test.ts PASS e2e with forget src/__tests__/e2e/hello.e2e.js PASS e2e no forget src/__tests__/e2e/hello.e2e.js Test Suites: [4](https://github.com/facebook/react-forget/actions/runs/5732016200/job/15534129231?pr=1881#step:8:5) passed, 4 total Tests: 23 passed, 23 total Snapshots: 11 passed, 11 total Time: 6.1[5](https://github.com/facebook/react-forget/actions/runs/5732016200/job/15534129231?pr=1881#step:8:6)3 s ```

Mofei Zhang committed Aug 2, 2023 at 17:30 UTC 2a5a8d552fdbbd0153d30c02725b4cf6379a6cb3
6 files changed +208 -25
compiler/forget/packages/babel-plugin-react-forget/package.json
+2 -2
@@ -55,8 +55,8 @@
55 "glob": "^7.1.6",
56 "jest": "^29.0.3",
57 "jest-environment-jsdom": "^29.0.3",
58 - "react": "^18.2.0",
59 - "react-dom": "^18.2.0",
58 + "react": "^0.0.0-experimental-493f72b0a-20230727",
59 + "react-dom": "^0.0.0-experimental-493f72b0a-20230727",
60 "rimraf": "^3.0.2",
61 "test262-harness": "^8.0.0",
62 "ts-jest": "^29.1.1",
compiler/forget/packages/babel-plugin-react-forget/scripts/jest/makeTransform.js
+78 -12
@@ -5,9 +5,10 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -const ReactForgetBabelPlugin = require("../../dist").BabelPlugin;
8 const babelJest = require("babel-jest");
10 -const { readFileSync } = require("fs");
9 +const { compile } = require("babel-plugin-react-forget");
10 +const { jsx } = require("@babel/plugin-syntax-jsx");
11 +const { execSync } = require("child_process");
12
13 module.exports = (useForget) => {
14 function createTransformer() {
@@ -17,18 +18,18 @@ module.exports = (useForget) => {
18 "@babel/preset-typescript",
19 {
20 plugins: [
20 - "@babel/plugin-syntax-jsx",
21 - ...(useForget
21 + useForget
22 ? [
23 - [
24 - ReactForgetBabelPlugin,
25 - {
26 - // Jest hashes the babel config as a cache breaker.
27 - cacheBreaker: readFileSync("dist/HASH", "utf8"),
28 - },
29 - ],
23 + ReactForgetFunctionTransform,
24 + {
25 + // Jest hashes the babel config as a cache breaker.
26 + // (see https://github.com/jestjs/jest/blob/v29.6.2/packages/babel-jest/src/index.ts#L84)
27 + cacheKey: execSync(
28 + "yarn --silent --cwd ../.. hash packages/babel-plugin-react-forget/dist"
29 + ).toString(),
30 + },
31 ]
31 - : []),
32 + : "@babel/plugin-syntax-jsx",
33 ],
34 },
35 "@babel/preset-react",
@@ -68,3 +69,68 @@ module.exports = (useForget) => {
69 createTransformer,
70 };
71 };
72 +
73 +// Copied from react/scripts/babel/transform-forget.js
74 +function isReactComponentLike(fn) {
75 + let isReactComponent = false;
76 + let hasNoUseForgetDirective = false;
77 +
78 + // React components start with an upper case letter
79 + if (fn.node.id.name[0].toUpperCase() !== fn.node.id.name[0]) {
80 + return false;
81 + }
82 +
83 + fn.traverse({
84 + DirectiveLiteral(path) {
85 + if (path.node.value === "use no forget") {
86 + hasNoUseForgetDirective = true;
87 + }
88 + },
89 +
90 + JSX(path) {
91 + // Is there is a JSX node created in the current function context?
92 + if (path.scope.getFunctionParent()?.path.node === fn.node) {
93 + isReactComponent = true;
94 + }
95 + },
96 +
97 + CallExpression(path) {
98 + // Is there hook usage?
99 + if (
100 + path.node.callee.type === "Identifier" &&
101 + path.node.callee.name.startsWith("use")
102 + ) {
103 + isReactComponent = true;
104 + }
105 + },
106 + });
107 +
108 + if (hasNoUseForgetDirective) {
109 + return false;
110 + }
111 +
112 + return isReactComponent;
113 +}
114 +
115 +function ReactForgetFunctionTransform() {
116 + const compiledFns = new Set();
117 + return {
118 + name: "react-forget-e2e",
119 + inherits: jsx,
120 + visitor: {
121 + FunctionDeclaration(fn) {
122 + if (compiledFns.has(fn.node)) {
123 + return;
124 + }
125 +
126 + if (!isReactComponentLike(fn)) {
127 + return;
128 + }
129 +
130 + const compiled = compile(fn);
131 + compiledFns.add(compiled);
132 + fn.replaceWith(compiled);
133 + },
134 + },
135 + };
136 +}
compiler/forget/packages/babel-plugin-react-forget/scripts/jest/setupEnvE2E.js
+6 -3
@@ -6,6 +6,9 @@
6 */
7
8 const React = require("react");
9 -const ForgetRuntime = require("../../packages/react-forget-runtime");
10 -React.unstable_ForgetRuntime = ForgetRuntime;
11 -React.unstable_useMemoCache = ForgetRuntime.unstable_useMemoCache;
9 +
10 +// Our e2e babel transform currently only compiles functions, not programs.
11 +// As a result, our e2e transpiled code does not contain an import for `useMemoCache`
12 +// This is a hack.
13 +React.useMemoCache = React.unstable_useMemoCache;
14 +globalThis.useMemoCache = React.unstable_useMemoCache;
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/e2e/expectLogs.js new
+17
@@ -0,0 +1,17 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +const logs = [];
9 +
10 +export function log(message) {
11 + logs.push(message);
12 +}
13 +
14 +export function expectLogsAndClear(expected) {
15 + expect(logs).toEqual(expected);
16 + logs.length = 0;
17 +}
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/e2e/hello.e2e.js new
+75
@@ -0,0 +1,75 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +import * as React from "react";
9 +import { render } from "@testing-library/react";
10 +import { expectLogsAndClear, log } from "./expectLogs";
11 +
12 +function Hello({ name }) {
13 + const items = [1, 2, 3].map((item) => {
14 + log(`recomputing ${item}`);
15 + return <div key={item}>Item {item}</div>;
16 + });
17 + return (
18 + <div>
19 + Hello<b>{name}</b>
20 + {items}
21 + </div>
22 + );
23 +}
24 +
25 +test("hello", () => {
26 + const { asFragment, rerender } = render(<Hello name="World" />);
27 +
28 + expect(asFragment()).toMatchInlineSnapshot(`
29 + <DocumentFragment>
30 + <div>
31 + Hello
32 + <b>
33 + World
34 + </b>
35 + <div>
36 + Item 1
37 + </div>
38 + <div>
39 + Item 2
40 + </div>
41 + <div>
42 + Item 3
43 + </div>
44 + </div>
45 + </DocumentFragment>
46 + `);
47 +
48 + expectLogsAndClear(["recomputing 1", "recomputing 2", "recomputing 3"]);
49 +
50 + rerender(<Hello name="Universe" />);
51 +
52 + expect(asFragment()).toMatchInlineSnapshot(`
53 + <DocumentFragment>
54 + <div>
55 + Hello
56 + <b>
57 + Universe
58 + </b>
59 + <div>
60 + Item 1
61 + </div>
62 + <div>
63 + Item 2
64 + </div>
65 + <div>
66 + Item 3
67 + </div>
68 + </div>
69 + </DocumentFragment>
70 + `);
71 +
72 + expectLogsAndClear(
73 + __FORGET__ ? [] : ["recomputing 1", "recomputing 2", "recomputing 3"]
74 + );
75 +});
compiler/forget/yarn.lock
+30 -8
@@ -4208,9 +4208,9 @@ caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.300014
4208 integrity sha512-2efF8SAZwgAX1FJr87KWhvuJxnGJKOnctQa8xLOskAXNXq8oiuqgl6u1kk3fFpsp3GgvzlRjiK1sl63hNtFADw==
4209
4210 caniuse-lite@^1.0.30001503:
4211 - version "1.0.30001517"
4212 - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz#90fabae294215c3495807eb24fc809e11dc2f0a8"
4213 - integrity sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==
4211 + version "1.0.30001518"
4212 + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001518.tgz#b3ca93904cb4699c01218246c4d77a71dbe97150"
4213 + integrity sha512-rup09/e3I0BKjncL+FesTayKtPrdwKhUufQFd3riFw1hHg8JmIFoInYfB102cFcY/pPgGmdyl/iy+jgiDi2vdA==
4214
4215 caseless@~0.12.0:
4216 version "0.12.0"
@@ -4979,9 +4979,9 @@ electron-to-chromium@^1.4.411:
4979 integrity sha512-1KnpDTS9onwAfMzW50LcpNtyOkMyjd/OLoD2Kx/DDITZqgNYixY71XNszPHNxyQQ/Brh+FDcUnf4BaM041sdWg==
4980
4981 electron-to-chromium@^1.4.431:
4982 - version "1.4.476"
4983 - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.476.tgz#693df619ce1785ada6d5aec71fd3ce7ace71adc3"
4984 - integrity sha512-gzWl1m8pNy+5Kj17XcziNcbOhripjTqR2wAQmtdlFUngPYuFy7zUpJScVQAvCvQSFHNk3mS5fetNKW6BSpytFg==
4982 + version "1.4.479"
4983 + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.479.tgz#ec9f676f23d3a0b0e429bc454d25e0b3253d2118"
4984 + integrity sha512-ABv1nHMIR8I5n3O3Een0gr6i0mfM+YcTZqjHy3pAYaOjgFG+BMquuKrSyfYf5CbEkLr9uM05RA3pOk4udNB/aQ==
4985
4986 elliptic@^6.5.3:
4987 version "6.5.4"
@@ -9695,7 +9695,7 @@ rc@^1.2.7:
9695 minimist "^1.2.0"
9696 strip-json-comments "~2.0.1"
9697
9698 -react-dom@18.2.0, react-dom@^18.2.0:
9698 +react-dom@18.2.0:
9699 version "18.2.0"
9700 resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
9701 integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
@@ -9703,6 +9703,14 @@ react-dom@18.2.0, react-dom@^18.2.0:
9703 loose-envify "^1.1.0"
9704 scheduler "^0.23.0"
9705
9706 +react-dom@^0.0.0-experimental-493f72b0a-20230727:
9707 + version "0.0.0-experimental-493f72b0a-20230727"
9708 + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-0.0.0-experimental-493f72b0a-20230727.tgz#02b95966cbccafdb5eff411bd77cf9a99b4ac38c"
9709 + integrity sha512-Ms+rLKteABVNHRjqj2VIQg7biJYb3jB2paJJGGF579Mi22UQnyAVypbCTedzFUDhtpxs+LDR45QNFT5kDIEeaw==
9710 + dependencies:
9711 + loose-envify "^1.1.0"
9712 + scheduler "0.0.0-experimental-493f72b0a-20230727"
9713 +
9714 react-is@^16.13.1, react-is@^16.8.4:
9715 version "16.13.1"
9716 resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -9718,13 +9726,20 @@ react-is@^18.0.0:
9726 resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
9727 integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
9728
9721 -react@18.2.0, react@^18.2.0:
9729 +react@18.2.0:
9730 version "18.2.0"
9731 resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
9732 integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
9733 dependencies:
9734 loose-envify "^1.1.0"
9735
9736 +react@^0.0.0-experimental-493f72b0a-20230727:
9737 + version "0.0.0-experimental-493f72b0a-20230727"
9738 + resolved "https://registry.yarnpkg.com/react/-/react-0.0.0-experimental-493f72b0a-20230727.tgz#c1e165db1d1c2dc944e5e1759a860807b06503ed"
9739 + integrity sha512-AivNXEA/rd+x9Oe+5JIgsAFGaBQZg0c85upbziIYbgxW1BT6iOFUFA38i+SMfd1ktD82E1yj4nH/OpfA8kC3Cg==
9740 + dependencies:
9741 + loose-envify "^1.1.0"
9742 +
9743 read-cache@^1.0.0:
9744 version "1.0.0"
9745 resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
@@ -10098,6 +10113,13 @@ saxes@^6.0.0:
10113 dependencies:
10114 xmlchars "^2.2.0"
10115
10116 +scheduler@0.0.0-experimental-493f72b0a-20230727:
10117 + version "0.0.0-experimental-493f72b0a-20230727"
10118 + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.0.0-experimental-493f72b0a-20230727.tgz#a4d1fd57d9bfbd68b1b3e3c3249765f92c1a7b34"
10119 + integrity sha512-6bKnt2pR0XjH4ix7rYCp8+KR5eutR0hDpCiU++14SuImTH0uMYa3Gp4ayLeMyAvOEFTCgewDS/faZF1J14sRew==
10120 + dependencies:
10121 + loose-envify "^1.1.0"
10122 +
10123 scheduler@^0.23.0:
10124 version "0.23.0"
10125 resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"