From 2a5a8d552fdbbd0153d30c02725b4cf6379a6cb3 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 2 Aug 2023 17:30:55 -0400 Subject: [PATCH] [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 ``` --- .../babel-plugin-react-forget/package.json | 4 +- .../scripts/jest/makeTransform.js | 90 ++++++++++++++++--- .../scripts/jest/setupEnvE2E.js | 9 +- .../src/__tests__/e2e/expectLogs.js | 17 ++++ .../src/__tests__/e2e/hello.e2e.js | 75 ++++++++++++++++ compiler/forget/yarn.lock | 38 ++++++-- 6 files changed, 208 insertions(+), 25 deletions(-) create mode 100644 compiler/forget/packages/babel-plugin-react-forget/src/__tests__/e2e/expectLogs.js create mode 100644 compiler/forget/packages/babel-plugin-react-forget/src/__tests__/e2e/hello.e2e.js diff --git a/compiler/forget/packages/babel-plugin-react-forget/package.json b/compiler/forget/packages/babel-plugin-react-forget/package.json index 30c8a712dc..cafd7258e8 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/package.json +++ b/compiler/forget/packages/babel-plugin-react-forget/package.json @@ -55,8 +55,8 @@ "glob": "^7.1.6", "jest": "^29.0.3", "jest-environment-jsdom": "^29.0.3", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "react": "^0.0.0-experimental-493f72b0a-20230727", + "react-dom": "^0.0.0-experimental-493f72b0a-20230727", "rimraf": "^3.0.2", "test262-harness": "^8.0.0", "ts-jest": "^29.1.1", diff --git a/compiler/forget/packages/babel-plugin-react-forget/scripts/jest/makeTransform.js b/compiler/forget/packages/babel-plugin-react-forget/scripts/jest/makeTransform.js index af4e10a19f..db9d2caf2c 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/scripts/jest/makeTransform.js +++ b/compiler/forget/packages/babel-plugin-react-forget/scripts/jest/makeTransform.js @@ -5,9 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -const ReactForgetBabelPlugin = require("../../dist").BabelPlugin; const babelJest = require("babel-jest"); -const { readFileSync } = require("fs"); +const { compile } = require("babel-plugin-react-forget"); +const { jsx } = require("@babel/plugin-syntax-jsx"); +const { execSync } = require("child_process"); module.exports = (useForget) => { function createTransformer() { @@ -17,18 +18,18 @@ module.exports = (useForget) => { "@babel/preset-typescript", { plugins: [ - "@babel/plugin-syntax-jsx", - ...(useForget + useForget ? [ - [ - ReactForgetBabelPlugin, - { - // Jest hashes the babel config as a cache breaker. - cacheBreaker: readFileSync("dist/HASH", "utf8"), - }, - ], + ReactForgetFunctionTransform, + { + // Jest hashes the babel config as a cache breaker. + // (see https://github.com/jestjs/jest/blob/v29.6.2/packages/babel-jest/src/index.ts#L84) + cacheKey: execSync( + "yarn --silent --cwd ../.. hash packages/babel-plugin-react-forget/dist" + ).toString(), + }, ] - : []), + : "@babel/plugin-syntax-jsx", ], }, "@babel/preset-react", @@ -68,3 +69,68 @@ module.exports = (useForget) => { createTransformer, }; }; + +// Copied from react/scripts/babel/transform-forget.js +function isReactComponentLike(fn) { + let isReactComponent = false; + let hasNoUseForgetDirective = false; + + // React components start with an upper case letter + if (fn.node.id.name[0].toUpperCase() !== fn.node.id.name[0]) { + return false; + } + + fn.traverse({ + DirectiveLiteral(path) { + if (path.node.value === "use no forget") { + hasNoUseForgetDirective = true; + } + }, + + JSX(path) { + // Is there is a JSX node created in the current function context? + if (path.scope.getFunctionParent()?.path.node === fn.node) { + isReactComponent = true; + } + }, + + CallExpression(path) { + // Is there hook usage? + if ( + path.node.callee.type === "Identifier" && + path.node.callee.name.startsWith("use") + ) { + isReactComponent = true; + } + }, + }); + + if (hasNoUseForgetDirective) { + return false; + } + + return isReactComponent; +} + +function ReactForgetFunctionTransform() { + const compiledFns = new Set(); + return { + name: "react-forget-e2e", + inherits: jsx, + visitor: { + FunctionDeclaration(fn) { + if (compiledFns.has(fn.node)) { + return; + } + + if (!isReactComponentLike(fn)) { + return; + } + + const compiled = compile(fn); + compiledFns.add(compiled); + fn.replaceWith(compiled); + }, + }, + }; +} diff --git a/compiler/forget/packages/babel-plugin-react-forget/scripts/jest/setupEnvE2E.js b/compiler/forget/packages/babel-plugin-react-forget/scripts/jest/setupEnvE2E.js index f48a0c241d..7f0ff80e6c 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/scripts/jest/setupEnvE2E.js +++ b/compiler/forget/packages/babel-plugin-react-forget/scripts/jest/setupEnvE2E.js @@ -6,6 +6,9 @@ */ const React = require("react"); -const ForgetRuntime = require("../../packages/react-forget-runtime"); -React.unstable_ForgetRuntime = ForgetRuntime; -React.unstable_useMemoCache = ForgetRuntime.unstable_useMemoCache; + +// Our e2e babel transform currently only compiles functions, not programs. +// As a result, our e2e transpiled code does not contain an import for `useMemoCache` +// This is a hack. +React.useMemoCache = React.unstable_useMemoCache; +globalThis.useMemoCache = React.unstable_useMemoCache; diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/e2e/expectLogs.js b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/e2e/expectLogs.js new file mode 100644 index 0000000000..7e00bd5014 --- /dev/null +++ b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/e2e/expectLogs.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const logs = []; + +export function log(message) { + logs.push(message); +} + +export function expectLogsAndClear(expected) { + expect(logs).toEqual(expected); + logs.length = 0; +} diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/e2e/hello.e2e.js b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/e2e/hello.e2e.js new file mode 100644 index 0000000000..34d0e34d80 --- /dev/null +++ b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/e2e/hello.e2e.js @@ -0,0 +1,75 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import * as React from "react"; +import { render } from "@testing-library/react"; +import { expectLogsAndClear, log } from "./expectLogs"; + +function Hello({ name }) { + const items = [1, 2, 3].map((item) => { + log(`recomputing ${item}`); + return
Item {item}
; + }); + return ( +
+ Hello{name} + {items} +
+ ); +} + +test("hello", () => { + const { asFragment, rerender } = render(); + + expect(asFragment()).toMatchInlineSnapshot(` + +
+ Hello + + World + +
+ Item 1 +
+
+ Item 2 +
+
+ Item 3 +
+
+
+ `); + + expectLogsAndClear(["recomputing 1", "recomputing 2", "recomputing 3"]); + + rerender(); + + expect(asFragment()).toMatchInlineSnapshot(` + +
+ Hello + + Universe + +
+ Item 1 +
+
+ Item 2 +
+
+ Item 3 +
+
+
+ `); + + expectLogsAndClear( + __FORGET__ ? [] : ["recomputing 1", "recomputing 2", "recomputing 3"] + ); +}); diff --git a/compiler/forget/yarn.lock b/compiler/forget/yarn.lock index 51b73a99bc..a509e3f22a 100644 --- a/compiler/forget/yarn.lock +++ b/compiler/forget/yarn.lock @@ -4208,9 +4208,9 @@ caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.300014 integrity sha512-2efF8SAZwgAX1FJr87KWhvuJxnGJKOnctQa8xLOskAXNXq8oiuqgl6u1kk3fFpsp3GgvzlRjiK1sl63hNtFADw== caniuse-lite@^1.0.30001503: - version "1.0.30001517" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz#90fabae294215c3495807eb24fc809e11dc2f0a8" - integrity sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA== + version "1.0.30001518" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001518.tgz#b3ca93904cb4699c01218246c4d77a71dbe97150" + integrity sha512-rup09/e3I0BKjncL+FesTayKtPrdwKhUufQFd3riFw1hHg8JmIFoInYfB102cFcY/pPgGmdyl/iy+jgiDi2vdA== caseless@~0.12.0: version "0.12.0" @@ -4979,9 +4979,9 @@ electron-to-chromium@^1.4.411: integrity sha512-1KnpDTS9onwAfMzW50LcpNtyOkMyjd/OLoD2Kx/DDITZqgNYixY71XNszPHNxyQQ/Brh+FDcUnf4BaM041sdWg== electron-to-chromium@^1.4.431: - version "1.4.476" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.476.tgz#693df619ce1785ada6d5aec71fd3ce7ace71adc3" - integrity sha512-gzWl1m8pNy+5Kj17XcziNcbOhripjTqR2wAQmtdlFUngPYuFy7zUpJScVQAvCvQSFHNk3mS5fetNKW6BSpytFg== + version "1.4.479" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.479.tgz#ec9f676f23d3a0b0e429bc454d25e0b3253d2118" + integrity sha512-ABv1nHMIR8I5n3O3Een0gr6i0mfM+YcTZqjHy3pAYaOjgFG+BMquuKrSyfYf5CbEkLr9uM05RA3pOk4udNB/aQ== elliptic@^6.5.3: version "6.5.4" @@ -9695,7 +9695,7 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-dom@18.2.0, react-dom@^18.2.0: +react-dom@18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== @@ -9703,6 +9703,14 @@ react-dom@18.2.0, react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-dom@^0.0.0-experimental-493f72b0a-20230727: + version "0.0.0-experimental-493f72b0a-20230727" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-0.0.0-experimental-493f72b0a-20230727.tgz#02b95966cbccafdb5eff411bd77cf9a99b4ac38c" + integrity sha512-Ms+rLKteABVNHRjqj2VIQg7biJYb3jB2paJJGGF579Mi22UQnyAVypbCTedzFUDhtpxs+LDR45QNFT5kDIEeaw== + dependencies: + loose-envify "^1.1.0" + scheduler "0.0.0-experimental-493f72b0a-20230727" + react-is@^16.13.1, react-is@^16.8.4: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -9718,13 +9726,20 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -react@18.2.0, react@^18.2.0: +react@18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== dependencies: loose-envify "^1.1.0" +react@^0.0.0-experimental-493f72b0a-20230727: + version "0.0.0-experimental-493f72b0a-20230727" + resolved "https://registry.yarnpkg.com/react/-/react-0.0.0-experimental-493f72b0a-20230727.tgz#c1e165db1d1c2dc944e5e1759a860807b06503ed" + integrity sha512-AivNXEA/rd+x9Oe+5JIgsAFGaBQZg0c85upbziIYbgxW1BT6iOFUFA38i+SMfd1ktD82E1yj4nH/OpfA8kC3Cg== + dependencies: + loose-envify "^1.1.0" + read-cache@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" @@ -10098,6 +10113,13 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" +scheduler@0.0.0-experimental-493f72b0a-20230727: + version "0.0.0-experimental-493f72b0a-20230727" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.0.0-experimental-493f72b0a-20230727.tgz#a4d1fd57d9bfbd68b1b3e3c3249765f92c1a7b34" + integrity sha512-6bKnt2pR0XjH4ix7rYCp8+KR5eutR0hDpCiU++14SuImTH0uMYa3Gp4ayLeMyAvOEFTCgewDS/faZF1J14sRew== + dependencies: + loose-envify "^1.1.0" + scheduler@^0.23.0: version "0.23.0" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"