diff --git a/.circleci/config.yml b/.circleci/config.yml index a247a7aceff4..9a23d3dd3d73 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -386,13 +386,10 @@ jobs: docker: *docker environment: *environment parallelism: *TEST_PARALLELISM - parameters: - args: - type: string steps: - checkout - setup_node_modules - - run: yarn test <> --ci + - run: yarn jest --selectProjects=stable-production --maxWorkers=2 --ci --shard=$(expr $CIRCLE_NODE_INDEX + 1)/$CIRCLE_NODE_TOTAL yarn_test_build: docker: *docker @@ -481,28 +478,6 @@ workflows: branches: ignore: - builds/facebook-www - matrix: - parameters: - args: - # Intentionally passing these as strings instead of creating a - # separate parameter per CLI argument, since it's easier to - # control/see which combinations we want to run. - - "-r=stable --env=development" - - "-r=stable --env=production" - - "-r=experimental --env=development" - - "-r=experimental --env=production" - - "-r=www-classic --env=development --variant=false" - - "-r=www-classic --env=production --variant=false" - - "-r=www-classic --env=development --variant=true" - - "-r=www-classic --env=production --variant=true" - - "-r=www-modern --env=development --variant=false" - - "-r=www-modern --env=production --variant=false" - - "-r=www-modern --env=development --variant=true" - - "-r=www-modern --env=production --variant=true" - - # TODO: Test more persistent configurations? - - '-r=stable --env=development --persistent' - - '-r=experimental --env=development --persistent' - yarn_build: filters: branches: diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 000000000000..831e816e6925 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,171 @@ +/** + * 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. + */ + +'use strict'; + +function createConfig({releaseChannel, env, variant, persistent}) { + let displayName = `${releaseChannel}-${env}`; + if (variant === true) { + displayName += '-variant'; + } + if (persistent) { + displayName += '-persistent'; + } + + const setupFiles = []; + if (variant) { + setupFiles.push(require.resolve('./scripts/jest/setupEnvVariant.js')); + } + switch (env) { + case 'development': + setupFiles.push(require.resolve('./scripts/jest/setupEnvDevelopment.js')); + break; + case 'production': + setupFiles.push(require.resolve('./scripts/jest/setupEnvProduction.js')); + break; + default: + throw new Error(`Unexpected env: ${env}`); + } + setupFiles.push(require.resolve('./scripts/jest/setupEnvironment.js')); + if (persistent) { + setupFiles.push(require.resolve('./scripts/jest/setupTests.persistent.js')); + } else { + switch (releaseChannel) { + case 'www-classic': + case 'www-modern': + setupFiles.push(require.resolve('./scripts/jest/setupTests.www.js')); + break; + case 'experimental': + case 'stable': + // no special setup files + break; + default: + throw new Error(`Unexpected release channel: ${releaseChannel}`); + } + } + setupFiles.push(require.resolve('./scripts/jest/setupHostConfigs.js')); + + const modulePathIgnorePatterns = [ + '/scripts/rollup/shims/', + '/scripts/bench/', + 'packages/react-devtools-extensions', + 'packages/react-devtools-shared', + ]; + + if (persistent) { + modulePathIgnorePatterns.push( + 'ReactIncrementalPerf', + 'ReactIncrementalUpdatesMinimalism', + 'ReactIncrementalTriangle', + 'ReactIncrementalReflection', + 'forwardRef' + ); + } + + return { + displayName, + setupFiles, + modulePathIgnorePatterns, + transform: { + '.*': + env === 'development' + ? require.resolve('./scripts/jest/preprocessor.dev.js') + : require.resolve('./scripts/jest/preprocessor.prod.js'), + }, + prettierPath: require.resolve('prettier-2'), + setupFilesAfterEnv: [require.resolve('./scripts/jest/setupTests.js')], + // Only include files directly in __tests__, not in nested folders. + testRegex: '/__tests__/[^/]*(\\.js|\\.coffee|[^d]\\.ts)$', + moduleFileExtensions: ['js', 'json', 'node', 'coffee', 'ts'], + rootDir: process.cwd(), + roots: ['/packages', '/scripts'], + fakeTimers: { + enableGlobally: true, + legacyFakeTimers: true, + }, + snapshotSerializers: [require.resolve('jest-snapshot-serializer-raw')], + testEnvironment: 'jsdom', + testRunner: 'jest-circus/runner', + }; +} + +module.exports = { + globalSetup: require.resolve('./scripts/jest/setupGlobal.js'), + collectCoverageFrom: ['packages/**/*.js'], + + projects: [ + createConfig({ + releaseChannel: 'stable', + env: 'development', + }), + createConfig({ + releaseChannel: 'stable', + env: 'production', + }), + createConfig({ + releaseChannel: 'stable', + env: 'development', + persistent: true, + }), + + createConfig({ + releaseChannel: 'experimental', + env: 'development', + }), + createConfig({ + releaseChannel: 'experimental', + env: 'production', + }), + createConfig({ + releaseChannel: 'experimental', + env: 'development', + persistent: true, + }), + + createConfig({ + releaseChannel: 'www-classic', + env: 'development', + variant: false, + }), + createConfig({ + releaseChannel: 'www-classic', + env: 'production', + variant: false, + }), + createConfig({ + releaseChannel: 'www-classic', + env: 'development', + variant: true, + }), + createConfig({ + releaseChannel: 'www-classic', + env: 'production', + variant: true, + }), + + createConfig({ + releaseChannel: 'www-modern', + env: 'development', + variant: false, + }), + createConfig({ + releaseChannel: 'www-modern', + env: 'production', + variant: false, + }), + createConfig({ + releaseChannel: 'www-modern', + env: 'development', + variant: true, + }), + createConfig({ + releaseChannel: 'www-modern', + env: 'production', + variant: true, + }), + ], +}; diff --git a/package.json b/package.json index 785bbfe63113..d42ef33b3963 100644 --- a/package.json +++ b/package.json @@ -103,9 +103,6 @@ "devEngines": { "node": "16.x || 18.x || 20.x || 21.x" }, - "jest": { - "testRegex": "/scripts/jest/dont-run-jest-directly\\.js$" - }, "scripts": { "build": "node ./scripts/rollup/build-all-release-channels.js", "build-for-devtools": "cross-env RELEASE_CHANNEL=experimental yarn build react/index,react/jsx,react-dom/index,react-dom/unstable_testing,react-dom/test-utils,react-is,react-debug-tools,scheduler,react-test-renderer,react-refresh,react-art --type=NODE", @@ -116,10 +113,7 @@ "lint-build": "node ./scripts/rollup/validate/index.js", "extract-errors": "node scripts/error-codes/extract-errors.js", "postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json && node ./scripts/flow/createFlowConfigs.js", - "test": "node ./scripts/jest/jest-cli.js", - "test-stable": "node ./scripts/jest/jest-cli.js --release-channel=stable", - "test-www": "node ./scripts/jest/jest-cli.js --release-channel=www-modern", - "test-classic": "node ./scripts/jest/jest-cli.js --release-channel=www-classic", + "test": "jest", "test-build-devtools": "node ./scripts/jest/jest-cli.js --build --project devtools --release-channel=experimental", "test-dom-fixture": "cd fixtures/dom && yarn && yarn predev && yarn test", "flow": "node ./scripts/tasks/flow.js", diff --git a/packages/react/src/__tests__/ReactClassEquivalence-test.js b/packages/react/src/__tests__/ReactClassEquivalence-test.js index fa3d696e6a0d..92d28c287dbb 100644 --- a/packages/react/src/__tests__/ReactClassEquivalence-test.js +++ b/packages/react/src/__tests__/ReactClassEquivalence-test.js @@ -12,13 +12,13 @@ const spawnSync = require('child_process').spawnSync; describe('ReactClassEquivalence', () => { - it('tests the same thing for es6 classes and CoffeeScript', () => { + it.skip('tests the same thing for es6 classes and CoffeeScript', () => { const result1 = runJest('ReactCoffeeScriptClass-test.coffee'); const result2 = runJest('ReactES6Class-test.js'); compareResults(result1, result2); }); - it('tests the same thing for es6 classes and TypeScript', () => { + it.skip('tests the same thing for es6 classes and TypeScript', () => { const result1 = runJest('ReactTypeScriptClass-test.ts'); const result2 = runJest('ReactES6Class-test.js'); compareResults(result1, result2); diff --git a/scripts/jest/config.base.js b/scripts/jest/config.base.js deleted file mode 100644 index a887e66b7dd3..000000000000 --- a/scripts/jest/config.base.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; - -module.exports = { - globalSetup: require.resolve('./setupGlobal.js'), - modulePathIgnorePatterns: [ - '/scripts/rollup/shims/', - '/scripts/bench/', - ], - transform: { - '.*': require.resolve('./preprocessor.js'), - }, - prettierPath: require.resolve('prettier-2'), - setupFiles: [require.resolve('./setupEnvironment.js')], - setupFilesAfterEnv: [require.resolve('./setupTests.js')], - // Only include files directly in __tests__, not in nested folders. - testRegex: '/__tests__/[^/]*(\\.js|\\.coffee|[^d]\\.ts)$', - moduleFileExtensions: ['js', 'json', 'node', 'coffee', 'ts'], - rootDir: process.cwd(), - roots: ['/packages', '/scripts'], - collectCoverageFrom: ['packages/**/*.js'], - fakeTimers: { - enableGlobally: true, - legacyFakeTimers: true, - }, - snapshotSerializers: [require.resolve('jest-snapshot-serializer-raw')], - - testSequencer: require.resolve('./jestSequencer'), - - testEnvironment: 'jsdom', - - testRunner: 'jest-circus/runner', -}; diff --git a/scripts/jest/config.build-devtools.js b/scripts/jest/config.build-devtools.js deleted file mode 100644 index e6de2e4a3213..000000000000 --- a/scripts/jest/config.build-devtools.js +++ /dev/null @@ -1,110 +0,0 @@ -'use strict'; - -const {readdirSync, statSync} = require('fs'); -const {join} = require('path'); -const baseConfig = require('./config.base'); -const devtoolsRegressionConfig = require('./devtools/config.build-devtools-regression'); - -const NODE_MODULES_DIR = - process.env.RELEASE_CHANNEL === 'stable' ? 'oss-stable' : 'oss-experimental'; - -// Find all folders in packages/* with package.json -const packagesRoot = join(__dirname, '..', '..', 'packages'); -const packages = readdirSync(packagesRoot).filter(dir => { - if (dir.charAt(0) === '.') { - return false; - } - if (dir.includes('react-devtools')) { - return false; - } - if (dir === 'internal-test-utils') { - // This is an internal package used only for testing. It's OK to read - // from source. - // TODO: Maybe let's have some convention for this? - return false; - } - const packagePath = join(packagesRoot, dir, 'package.json'); - let stat; - try { - stat = statSync(packagePath); - } catch (err) { - return false; - } - return stat.isFile(); -}); - -// Create a module map to point React packages to the build output -const moduleNameMapper = {}; - -moduleNameMapper['react-devtools-feature-flags'] = - '/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.default'; - -// Map packages to bundles -packages.forEach(name => { - // Root entry point - moduleNameMapper[`^${name}$`] = `/build/${NODE_MODULES_DIR}/${name}`; - // Named entry points - moduleNameMapper[ - `^${name}\/([^\/]+)$` - ] = `/build/${NODE_MODULES_DIR}/${name}/$1`; -}); - -// Allow tests to import shared code (e.g. feature flags, getStackByFiberInDevAndProd) -moduleNameMapper['^shared/([^/]+)$'] = '/packages/shared/$1'; -moduleNameMapper['^react-reconciler/([^/]+)$'] = - '/packages/react-reconciler/$1'; - -module.exports = Object.assign({}, baseConfig, { - // Redirect imports to the compiled bundles - moduleNameMapper: { - ...devtoolsRegressionConfig.moduleNameMapper, - ...moduleNameMapper, - }, - // Don't run bundle tests on -test.internal.* files - testPathIgnorePatterns: ['/node_modules/', '-test.internal.js$'], - // Exclude the build output from transforms - transformIgnorePatterns: [ - '/node_modules/', - '/build/', - '/__compiled__/', - '/__untransformed__/', - ], - testRegex: 'packages/react-devtools(-(.+))?/.+/__tests__/[^]+.test.js$', - snapshotSerializers: [ - require.resolve( - '../../packages/react-devtools-shared/src/__tests__/__serializers__/dehydratedValueSerializer.js' - ), - require.resolve( - '../../packages/react-devtools-shared/src/__tests__/__serializers__/hookSerializer.js' - ), - require.resolve( - '../../packages/react-devtools-shared/src/__tests__/__serializers__/inspectedElementSerializer.js' - ), - require.resolve( - '../../packages/react-devtools-shared/src/__tests__/__serializers__/profilingSerializer.js' - ), - require.resolve( - '../../packages/react-devtools-shared/src/__tests__/__serializers__/storeSerializer.js' - ), - require.resolve( - '../../packages/react-devtools-shared/src/__tests__/__serializers__/timelineDataSerializer.js' - ), - require.resolve( - '../../packages/react-devtools-shared/src/__tests__/__serializers__/treeContextStateSerializer.js' - ), - require.resolve( - '../../packages/react-devtools-shared/src/__tests__/__serializers__/numberToFixedSerializer.js' - ), - ], - setupFiles: [ - ...baseConfig.setupFiles, - ...devtoolsRegressionConfig.setupFiles, - require.resolve('./setupTests.build.js'), - require.resolve('./devtools/setupEnv.js'), - ], - setupFilesAfterEnv: [ - require.resolve( - '../../packages/react-devtools-shared/src/__tests__/setupTests.js' - ), - ], -}); diff --git a/scripts/jest/config.build.js b/scripts/jest/config.build.js deleted file mode 100644 index 9b8d328a509e..000000000000 --- a/scripts/jest/config.build.js +++ /dev/null @@ -1,77 +0,0 @@ -'use strict'; - -const {readdirSync, statSync} = require('fs'); -const {join} = require('path'); -const baseConfig = require('./config.base'); - -process.env.IS_BUILD = true; - -const NODE_MODULES_DIR = - process.env.RELEASE_CHANNEL === 'stable' ? 'oss-stable' : 'oss-experimental'; - -// Find all folders in packages/* with package.json -const packagesRoot = join(__dirname, '..', '..', 'packages'); -const packages = readdirSync(packagesRoot).filter(dir => { - if (dir === 'internal-test-utils') { - // This is an internal package used only for testing. It's OK to read - // from source. - // TODO: Maybe let's have some convention for this? - return false; - } - if (dir.charAt(0) === '.') { - return false; - } - const packagePath = join(packagesRoot, dir, 'package.json'); - let stat; - try { - stat = statSync(packagePath); - } catch (err) { - return false; - } - return stat.isFile(); -}); - -// Create a module map to point React packages to the build output -const moduleNameMapper = {}; - -// Allow bundle tests to read (but not write!) default feature flags. -// This lets us determine whether we're running in different modes -// without making relevant tests internal-only. -moduleNameMapper[ - '^shared/ReactFeatureFlags' -] = `/packages/shared/forks/ReactFeatureFlags.readonly`; - -// Map packages to bundles -packages.forEach(name => { - // Root entry point - moduleNameMapper[`^${name}$`] = `/build/${NODE_MODULES_DIR}/${name}`; - // Named entry points - moduleNameMapper[ - `^${name}\/([^\/]+)$` - ] = `/build/${NODE_MODULES_DIR}/${name}/$1`; -}); - -moduleNameMapper[ - 'use-sync-external-store/shim/with-selector' -] = `/build/${NODE_MODULES_DIR}/use-sync-external-store/shim/with-selector`; -moduleNameMapper[ - 'use-sync-external-store/shim/index.native' -] = `/build/${NODE_MODULES_DIR}/use-sync-external-store/shim/index.native`; - -module.exports = Object.assign({}, baseConfig, { - // Redirect imports to the compiled bundles - moduleNameMapper, - modulePathIgnorePatterns: [ - ...baseConfig.modulePathIgnorePatterns, - 'packages/react-devtools-extensions', - 'packages/react-devtools-shared', - ], - // Don't run bundle tests on -test.internal.* files - testPathIgnorePatterns: ['/node_modules/', '-test.internal.js$'], - // Exclude the build output from transforms - transformIgnorePatterns: ['/node_modules/', '/build/'], - setupFiles: [ - ...baseConfig.setupFiles, - require.resolve('./setupTests.build.js'), - ], -}); diff --git a/scripts/jest/config.source-persistent.js b/scripts/jest/config.source-persistent.js deleted file mode 100644 index 80bec669b643..000000000000 --- a/scripts/jest/config.source-persistent.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -const baseConfig = require('./config.base'); - -module.exports = Object.assign({}, baseConfig, { - modulePathIgnorePatterns: [ - ...baseConfig.modulePathIgnorePatterns, - 'packages/react-devtools-extensions', - 'packages/react-devtools-shared', - 'ReactIncrementalPerf', - 'ReactIncrementalUpdatesMinimalism', - 'ReactIncrementalTriangle', - 'ReactIncrementalReflection', - 'forwardRef', - ], - setupFiles: [ - ...baseConfig.setupFiles, - require.resolve('./setupTests.persistent.js'), - require.resolve('./setupHostConfigs.js'), - ], -}); diff --git a/scripts/jest/config.source-www.js b/scripts/jest/config.source-www.js deleted file mode 100644 index 31e8841ac363..000000000000 --- a/scripts/jest/config.source-www.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -const baseConfig = require('./config.base'); - -module.exports = Object.assign({}, baseConfig, { - modulePathIgnorePatterns: [ - ...baseConfig.modulePathIgnorePatterns, - 'packages/react-devtools-extensions', - 'packages/react-devtools-shared', - ], - setupFiles: [ - ...baseConfig.setupFiles, - require.resolve('./setupTests.www.js'), - require.resolve('./setupHostConfigs.js'), - ], -}); diff --git a/scripts/jest/config.source.js b/scripts/jest/config.source.js deleted file mode 100644 index 710df337b5ab..000000000000 --- a/scripts/jest/config.source.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -const baseConfig = require('./config.base'); - -module.exports = Object.assign({}, baseConfig, { - modulePathIgnorePatterns: [ - ...baseConfig.modulePathIgnorePatterns, - 'packages/react-devtools-extensions', - 'packages/react-devtools-shared', - ], - setupFiles: [ - ...baseConfig.setupFiles, - require.resolve('./setupHostConfigs.js'), - ], -}); diff --git a/scripts/jest/dont-run-jest-directly.js b/scripts/jest/dont-run-jest-directly.js deleted file mode 100644 index 672d07caec57..000000000000 --- a/scripts/jest/dont-run-jest-directly.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -throw new Error("Don't run `jest` directly. Run `yarn test` instead."); diff --git a/scripts/jest/jest-cli.js b/scripts/jest/jest-cli.js deleted file mode 100644 index 22098a190561..000000000000 --- a/scripts/jest/jest-cli.js +++ /dev/null @@ -1,386 +0,0 @@ -'use strict'; - -const {spawn} = require('child_process'); -const chalk = require('chalk'); -const yargs = require('yargs'); -const fs = require('fs'); -const path = require('path'); -const semver = require('semver'); - -const ossConfig = './scripts/jest/config.source.js'; -const wwwConfig = './scripts/jest/config.source-www.js'; -const devToolsConfig = './scripts/jest/config.build-devtools.js'; - -// TODO: These configs are separate but should be rolled into the configs above -// so that the CLI can provide them as options for any of the configs. -const persistentConfig = './scripts/jest/config.source-persistent.js'; -const buildConfig = './scripts/jest/config.build.js'; - -const argv = yargs - .parserConfiguration({ - // Important: This option tells yargs to move all other options not - // specified here into the `_` key. We use this to send all of the - // Jest options that we don't use through to Jest (like --watch). - 'unknown-options-as-args': true, - }) - .wrap(yargs.terminalWidth()) - .options({ - debug: { - alias: 'd', - describe: 'Run with node debugger attached.', - requiresArg: false, - type: 'boolean', - default: false, - }, - project: { - alias: 'p', - describe: 'Run the given project.', - requiresArg: true, - type: 'string', - default: 'default', - choices: ['default', 'devtools'], - }, - releaseChannel: { - alias: 'r', - describe: 'Run with the given release channel.', - requiresArg: true, - type: 'string', - default: 'experimental', - choices: ['experimental', 'stable', 'www-classic', 'www-modern'], - }, - env: { - alias: 'e', - describe: 'Run with the given node environment.', - requiresArg: true, - type: 'string', - choices: ['development', 'production'], - }, - prod: { - describe: 'Run with NODE_ENV=production.', - requiresArg: false, - type: 'boolean', - default: false, - }, - dev: { - describe: 'Run with NODE_ENV=development.', - requiresArg: false, - type: 'boolean', - default: false, - }, - variant: { - alias: 'v', - describe: 'Run with www variant set to true.', - requiresArg: false, - type: 'boolean', - }, - build: { - alias: 'b', - describe: 'Run tests on builds.', - requiresArg: false, - type: 'boolean', - default: false, - }, - persistent: { - alias: 'n', - describe: 'Run with persistence.', - requiresArg: false, - type: 'boolean', - default: false, - }, - ci: { - describe: 'Run tests in CI', - requiresArg: false, - type: 'boolean', - default: false, - }, - compactConsole: { - alias: 'c', - describe: 'Compact console output (hide file locations).', - requiresArg: false, - type: 'boolean', - default: false, - }, - reactVersion: { - describe: 'DevTools testing for specific version of React', - requiresArg: true, - type: 'string', - }, - sourceMaps: { - describe: - 'Enable inline source maps when transforming source files with Jest. Useful for debugging, but makes it slower.', - type: 'boolean', - default: false, - }, - }).argv; - -function logError(message) { - console.error(chalk.red(`\n${message}`)); -} -function isWWWConfig() { - return ( - (argv.releaseChannel === 'www-classic' || - argv.releaseChannel === 'www-modern') && - argv.project !== 'devtools' - ); -} - -function isOSSConfig() { - return ( - argv.releaseChannel === 'stable' || argv.releaseChannel === 'experimental' - ); -} - -function validateOptions() { - let success = true; - - if (argv.project === 'devtools') { - if (argv.prod) { - logError( - 'DevTool tests do not support --prod. Remove this option to continue.' - ); - success = false; - } - - if (argv.dev) { - logError( - 'DevTool tests do not support --dev. Remove this option to continue.' - ); - success = false; - } - - if (argv.env) { - logError( - 'DevTool tests do not support --env. Remove this option to continue.' - ); - success = false; - } - - if (argv.persistent) { - logError( - 'DevTool tests do not support --persistent. Remove this option to continue.' - ); - success = false; - } - - if (argv.variant) { - logError( - 'DevTool tests do not support --variant. Remove this option to continue.' - ); - success = false; - } - - if (!argv.build) { - logError('DevTool tests require --build.'); - success = false; - } - - if (argv.reactVersion && !semver.validRange(argv.reactVersion)) { - success = false; - logError('please specify a valid version range for --reactVersion'); - } - } else { - if (argv.compactConsole) { - logError('Only DevTool tests support compactConsole flag.'); - success = false; - } - if (argv.reactVersion) { - logError('Only DevTools tests supports the --reactVersion flag.'); - success = false; - } - } - - if (isWWWConfig()) { - if (argv.variant === undefined) { - // Turn internal experiments on by default - argv.variant = true; - } - } else { - if (argv.variant) { - logError( - 'Variant is only supported for the www release channels. Update these options to continue.' - ); - success = false; - } - } - - if (argv.build && argv.persistent) { - logError( - 'Persistence is not supported for build targets. Update these options to continue.' - ); - success = false; - } - - if (!isOSSConfig() && argv.persistent) { - logError( - 'Persistence only supported for oss release channels. Update these options to continue.' - ); - success = false; - } - - if (argv.build && isWWWConfig()) { - logError( - 'Build targets are only not supported for www release channels. Update these options to continue.' - ); - success = false; - } - - if (argv.env && argv.env !== 'production' && argv.prod) { - logError( - 'Build type does not match --prod. Update these options to continue.' - ); - success = false; - } - - if (argv.env && argv.env !== 'development' && argv.dev) { - logError( - 'Build type does not match --dev. Update these options to continue.' - ); - success = false; - } - - if (argv.prod && argv.dev) { - logError( - 'Cannot supply both --prod and --dev. Remove one of these options to continue.' - ); - success = false; - } - - if (argv.build) { - // TODO: We could build this if it hasn't been built yet. - const buildDir = path.resolve('./build'); - if (!fs.existsSync(buildDir)) { - logError( - 'Build directory does not exist, please run `yarn build` or remove the --build option.' - ); - success = false; - } else if (Date.now() - fs.statSync(buildDir).mtimeMs > 1000 * 60 * 15) { - logError( - 'Warning: Running a build test with a build directory older than 15 minutes.\nPlease remember to run `yarn build` when using --build.' - ); - } - } - - if (!success) { - console.log(''); // Extra newline. - process.exit(1); - } -} - -function getCommandArgs() { - // Add the correct Jest config. - const args = ['./scripts/jest/jest.js', '--config']; - if (argv.project === 'devtools') { - args.push(devToolsConfig); - } else if (argv.build) { - args.push(buildConfig); - } else if (argv.persistent) { - args.push(persistentConfig); - } else if (isWWWConfig()) { - args.push(wwwConfig); - } else if (isOSSConfig()) { - args.push(ossConfig); - } else { - // We should not get here. - logError('Unrecognized release channel'); - process.exit(1); - } - - // Set the debug options, if necessary. - if (argv.debug) { - args.unshift('--inspect-brk'); - args.push('--runInBand'); - - // Prevent console logs from being hidden until test completes. - args.push('--useStderr'); - } - - // CI Environments have limited workers. - if (argv.ci) { - args.push('--maxWorkers=2'); - } - - // Push the remaining args onto the command. - // This will send args like `--watch` to Jest. - args.push(...argv._); - - return args; -} - -function getEnvars() { - const envars = { - NODE_ENV: argv.env || 'development', - RELEASE_CHANNEL: argv.releaseChannel.match(/modern|experimental/) - ? 'experimental' - : 'stable', - - // Pass this flag through to the config environment - // so the base config can conditionally load the console setup file. - compactConsole: argv.compactConsole, - }; - - if (argv.prod) { - envars.NODE_ENV = 'production'; - } - - if (argv.dev) { - envars.NODE_ENV = 'development'; - } - - if (argv.variant) { - envars.VARIANT = true; - } - - if (argv.reactVersion) { - envars.REACT_VERSION = semver.coerce(argv.reactVersion); - } - - if (argv.sourceMaps) { - // This is off by default because it slows down the test runner, but it's - // super useful when running the debugger. - envars.JEST_ENABLE_SOURCE_MAPS = 'inline'; - } - - return envars; -} - -function main() { - validateOptions(); - - const args = getCommandArgs(); - const envars = getEnvars(); - const env = Object.entries(envars).map(([k, v]) => `${k}=${v}`); - - // Print the full command we're actually running. - const command = `$ ${env.join(' ')} node ${args.join(' ')}`; - console.log(chalk.dim(command)); - - // Print the release channel and project we're running for quick confirmation. - console.log( - chalk.blue( - `\nRunning tests for ${argv.project} (${argv.releaseChannel})...` - ) - ); - - // Print a message that the debugger is starting just - // for some extra feedback when running the debugger. - if (argv.debug) { - console.log(chalk.green('\nStarting debugger...')); - console.log(chalk.green('Open chrome://inspect and press "inspect"\n')); - } - - // Run Jest. - const jest = spawn('node', args, { - stdio: 'inherit', - env: {...envars, ...process.env}, - }); - - // Ensure we close our process when we get a failure case. - jest.on('close', code => { - // Forward the exit code from the Jest process. - if (code === 1) { - process.exit(1); - } - }); -} - -main(); diff --git a/scripts/jest/jestSequencer.js b/scripts/jest/jestSequencer.js deleted file mode 100644 index b21cbbfc0ccb..000000000000 --- a/scripts/jest/jestSequencer.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -const Sequencer = require('@jest/test-sequencer').default; - -class CustomSequencer extends Sequencer { - sort(tests) { - if (process.env.CIRCLE_NODE_TOTAL) { - // In CI, parallelize tests across multiple tasks. - const nodeTotal = parseInt(process.env.CIRCLE_NODE_TOTAL, 10); - const nodeIndex = parseInt(process.env.CIRCLE_NODE_INDEX, 10); - tests = tests - .sort((a, b) => (a.path < b.path ? -1 : 1)) - .filter((_, i) => i % nodeTotal === nodeIndex); - } - return tests; - } -} - -module.exports = CustomSequencer; diff --git a/scripts/jest/preprocessor.base.js b/scripts/jest/preprocessor.base.js new file mode 100644 index 000000000000..5d3b27af8795 --- /dev/null +++ b/scripts/jest/preprocessor.base.js @@ -0,0 +1,150 @@ +/** + * 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. + */ + +'use strict'; + +function createPreprocessor(isDev) { + const path = require('path'); + + const babel = require('@babel/core'); + const coffee = require('coffee-script'); + const hermesParser = require('hermes-parser'); + + const tsPreprocessor = require('./typescript/preprocessor'); + const createCacheKeyFunction = require('fbjs-scripts/jest/createCacheKeyFunction'); + const {ReactVersion} = require('../../ReactVersions'); + const semver = require('semver'); + + const pathToBabel = path.join( + require.resolve('@babel/core'), + '../..', + 'package.json' + ); + const pathToBabelPluginReplaceConsoleCalls = require.resolve( + '../babel/transform-replace-console-calls' + ); + const pathToTransformInfiniteLoops = require.resolve( + '../babel/transform-prevent-infinite-loops' + ); + const pathToTransformTestGatePragma = require.resolve( + '../babel/transform-test-gate-pragma' + ); + const pathToTransformReactVersionPragma = require.resolve( + '../babel/transform-react-version-pragma' + ); + const pathToBabelrc = path.join(__dirname, '..', '..', 'babel.config.js'); + const pathToErrorCodes = require.resolve('../error-codes/codes.json'); + + const ReactVersionTestingAgainst = process.env.REACT_VERSION || ReactVersion; + + const babelOptions = { + plugins: [ + // For Node environment only. For builds, Rollup takes care of ESM. + require.resolve('@babel/plugin-transform-modules-commonjs'), + + pathToTransformInfiniteLoops, + pathToTransformTestGatePragma, + + // This optimization is important for extremely performance-sensitive (e.g. React source). + // It's okay to disable it for tests. + [ + require.resolve('@babel/plugin-transform-block-scoping'), + {throwIfClosureRequired: false}, + ], + ], + retainLines: true, + }; + + return { + process: function (src, filePath) { + if (filePath.match(/\.css$/)) { + // Don't try to parse CSS modules; they aren't needed for tests anyway. + return {code: ''}; + } + if (filePath.match(/\.coffee$/)) { + return {code: coffee.compile(src, {bare: true})}; + } + if (filePath.match(/\.ts$/) && !filePath.match(/\.d\.ts$/)) { + return {code: tsPreprocessor.compile(src, filePath)}; + } + if (filePath.match(/\.json$/)) { + return {code: src}; + } + if (!filePath.match(/\/third_party\//)) { + // for test files, we also apply the async-await transform, but we want to + // make sure we don't accidentally apply that transform to product code. + const isTestFile = !!filePath.match(/\/__tests__\//); + const isInDevToolsPackages = !!filePath.match( + /\/packages\/react-devtools.*\// + ); + const testOnlyPlugins = []; + const sourceOnlyPlugins = []; + if (isDev && !isInDevToolsPackages) { + sourceOnlyPlugins.push(pathToBabelPluginReplaceConsoleCalls); + } + const plugins = ( + isTestFile ? testOnlyPlugins : sourceOnlyPlugins + ).concat(babelOptions.plugins); + if (isTestFile && isInDevToolsPackages) { + plugins.push(pathToTransformReactVersionPragma); + } + + // This is only for React DevTools tests with React 16.x + // `react/jsx-dev-runtime` and `react/jsx-runtime` are included in the package starting from v17 + if (semver.gte(ReactVersionTestingAgainst, '17.0.0')) { + plugins.push([ + process.env.NODE_ENV === 'development' + ? require.resolve('@babel/plugin-transform-react-jsx-development') + : require.resolve('@babel/plugin-transform-react-jsx'), + // The "automatic" runtime corresponds to react/jsx-runtime. "classic" + // would be React.createElement. + {runtime: 'automatic'}, + ]); + } else { + plugins.push( + require.resolve('@babel/plugin-transform-react-jsx'), + require.resolve('@babel/plugin-transform-react-jsx-source') + ); + } + + let sourceAst = hermesParser.parse(src, {babel: true}); + return { + code: babel.transformFromAstSync( + sourceAst, + src, + Object.assign( + {filename: path.relative(process.cwd(), filePath)}, + babelOptions, + { + plugins, + sourceMaps: process.env.JEST_ENABLE_SOURCE_MAPS + ? process.env.JEST_ENABLE_SOURCE_MAPS + : false, + } + ) + ).code, + }; + } + return {code: src}; + }, + + getCacheKey: createCacheKeyFunction( + [ + __filename, + pathToBabel, + pathToBabelrc, + pathToTransformInfiniteLoops, + pathToTransformTestGatePragma, + pathToTransformReactVersionPragma, + pathToErrorCodes, + ], + [(process.env.REACT_VERSION != null).toString(), isDev.toString()] + ), + }; +} + +module.exports = createPreprocessor; diff --git a/scripts/jest/preprocessor.dev.js b/scripts/jest/preprocessor.dev.js new file mode 100644 index 000000000000..fd2c84a5c8dd --- /dev/null +++ b/scripts/jest/preprocessor.dev.js @@ -0,0 +1,10 @@ +/** + * 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. + */ + +'use strict'; + +module.exports = require('./preprocessor.base')(true); diff --git a/scripts/jest/preprocessor.js b/scripts/jest/preprocessor.js deleted file mode 100644 index f04cd2c3cfc8..000000000000 --- a/scripts/jest/preprocessor.js +++ /dev/null @@ -1,142 +0,0 @@ -'use strict'; - -const path = require('path'); - -const babel = require('@babel/core'); -const coffee = require('coffee-script'); -const hermesParser = require('hermes-parser'); - -const tsPreprocessor = require('./typescript/preprocessor'); -const createCacheKeyFunction = require('fbjs-scripts/jest/createCacheKeyFunction'); -const {ReactVersion} = require('../../ReactVersions'); -const semver = require('semver'); - -const pathToBabel = path.join( - require.resolve('@babel/core'), - '../..', - 'package.json' -); -const pathToBabelPluginReplaceConsoleCalls = require.resolve( - '../babel/transform-replace-console-calls' -); -const pathToTransformInfiniteLoops = require.resolve( - '../babel/transform-prevent-infinite-loops' -); -const pathToTransformTestGatePragma = require.resolve( - '../babel/transform-test-gate-pragma' -); -const pathToTransformReactVersionPragma = require.resolve( - '../babel/transform-react-version-pragma' -); -const pathToBabelrc = path.join(__dirname, '..', '..', 'babel.config.js'); -const pathToErrorCodes = require.resolve('../error-codes/codes.json'); - -const ReactVersionTestingAgainst = process.env.REACT_VERSION || ReactVersion; - -const babelOptions = { - plugins: [ - // For Node environment only. For builds, Rollup takes care of ESM. - require.resolve('@babel/plugin-transform-modules-commonjs'), - - pathToTransformInfiniteLoops, - pathToTransformTestGatePragma, - - // This optimization is important for extremely performance-sensitive (e.g. React source). - // It's okay to disable it for tests. - [ - require.resolve('@babel/plugin-transform-block-scoping'), - {throwIfClosureRequired: false}, - ], - ], - retainLines: true, -}; - -module.exports = { - process: function (src, filePath) { - if (filePath.match(/\.css$/)) { - // Don't try to parse CSS modules; they aren't needed for tests anyway. - return {code: ''}; - } - if (filePath.match(/\.coffee$/)) { - return {code: coffee.compile(src, {bare: true})}; - } - if (filePath.match(/\.ts$/) && !filePath.match(/\.d\.ts$/)) { - return {code: tsPreprocessor.compile(src, filePath)}; - } - if (filePath.match(/\.json$/)) { - return {code: src}; - } - if (!filePath.match(/\/third_party\//)) { - // for test files, we also apply the async-await transform, but we want to - // make sure we don't accidentally apply that transform to product code. - const isTestFile = !!filePath.match(/\/__tests__\//); - const isInDevToolsPackages = !!filePath.match( - /\/packages\/react-devtools.*\// - ); - const testOnlyPlugins = []; - const sourceOnlyPlugins = []; - if (process.env.NODE_ENV === 'development' && !isInDevToolsPackages) { - sourceOnlyPlugins.push(pathToBabelPluginReplaceConsoleCalls); - } - const plugins = (isTestFile ? testOnlyPlugins : sourceOnlyPlugins).concat( - babelOptions.plugins - ); - if (isTestFile && isInDevToolsPackages) { - plugins.push(pathToTransformReactVersionPragma); - } - - // This is only for React DevTools tests with React 16.x - // `react/jsx-dev-runtime` and `react/jsx-runtime` are included in the package starting from v17 - if (semver.gte(ReactVersionTestingAgainst, '17.0.0')) { - plugins.push([ - process.env.NODE_ENV === 'development' - ? require.resolve('@babel/plugin-transform-react-jsx-development') - : require.resolve('@babel/plugin-transform-react-jsx'), - // The "automatic" runtime corresponds to react/jsx-runtime. "classic" - // would be React.createElement. - {runtime: 'automatic'}, - ]); - } else { - plugins.push( - require.resolve('@babel/plugin-transform-react-jsx'), - require.resolve('@babel/plugin-transform-react-jsx-source') - ); - } - - let sourceAst = hermesParser.parse(src, {babel: true}); - return { - code: babel.transformFromAstSync( - sourceAst, - src, - Object.assign( - {filename: path.relative(process.cwd(), filePath)}, - babelOptions, - { - plugins, - sourceMaps: process.env.JEST_ENABLE_SOURCE_MAPS - ? process.env.JEST_ENABLE_SOURCE_MAPS - : false, - } - ) - ).code, - }; - } - return {code: src}; - }, - - getCacheKey: createCacheKeyFunction( - [ - __filename, - pathToBabel, - pathToBabelrc, - pathToTransformInfiniteLoops, - pathToTransformTestGatePragma, - pathToTransformReactVersionPragma, - pathToErrorCodes, - ], - [ - (process.env.REACT_VERSION != null).toString(), - (process.env.NODE_ENV === 'development').toString(), - ] - ), -}; diff --git a/scripts/jest/preprocessor.prod.js b/scripts/jest/preprocessor.prod.js new file mode 100644 index 000000000000..1b34ff7b41c5 --- /dev/null +++ b/scripts/jest/preprocessor.prod.js @@ -0,0 +1,10 @@ +/** + * 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. + */ + +'use strict'; + +module.exports = require('./preprocessor.base')(false); diff --git a/scripts/jest/setupEnvDevelopment.js b/scripts/jest/setupEnvDevelopment.js new file mode 100644 index 000000000000..33a2d6ed95d7 --- /dev/null +++ b/scripts/jest/setupEnvDevelopment.js @@ -0,0 +1,10 @@ +/** + * 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. + */ + +'use strict'; + +process.env.NODE_ENV = 'development'; diff --git a/scripts/jest/setupEnvProduction.js b/scripts/jest/setupEnvProduction.js new file mode 100644 index 000000000000..fe43f4885780 --- /dev/null +++ b/scripts/jest/setupEnvProduction.js @@ -0,0 +1,10 @@ +/** + * 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. + */ + +'use strict'; + +process.env.NODE_ENV = 'production'; diff --git a/scripts/jest/setupEnvVariant.js b/scripts/jest/setupEnvVariant.js new file mode 100644 index 000000000000..6e00cb70bfd3 --- /dev/null +++ b/scripts/jest/setupEnvVariant.js @@ -0,0 +1,10 @@ +/** + * 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. + */ + +'use strict'; + +process.env.VARIANT = 'true';