diff --git a/CHANGELOG.md b/CHANGELOG.md index da05c9928cf4..62bb30830e01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Fixes +- `[jest-config]` Add config validation for `projects` option ([#13565](https://github.com/facebook/jest/pull/13565)) - `[jest-mock]` Treat cjs modules as objects so they can be mocked ([#13513](https://github.com/facebook/jest/pull/13513)) - `[jest-worker]` Throw an error instead of hanging when jest workers terminate unexpectedly ([#13566](https://github.com/facebook/jest/pull/13566)) diff --git a/packages/jest-config/src/ValidConfig.ts b/packages/jest-config/src/ValidConfig.ts index 5239ffd8709d..a5b9ebbf4386 100644 --- a/packages/jest-config/src/ValidConfig.ts +++ b/packages/jest-config/src/ValidConfig.ts @@ -13,7 +13,7 @@ import {NODE_MODULES} from './constants'; const NODE_MODULES_REGEXP = replacePathSepForRegex(NODE_MODULES); -const initialOptions: Config.InitialOptions = { +export const initialOptions: Config.InitialOptions = { automock: false, bail: multipleValidOptions(false, 0), cache: true, @@ -188,4 +188,115 @@ const initialOptions: Config.InitialOptions = { workerIdleMemoryLimit: multipleValidOptions(0.2, '50%'), }; -export default initialOptions; +export const initialProjectOptions: Config.InitialProjectOptions = { + automock: false, + cache: true, + cacheDirectory: '/tmp/user/jest', + clearMocks: false, + coveragePathIgnorePatterns: [NODE_MODULES_REGEXP], + dependencyExtractor: '/dependencyExtractor.js', + detectLeaks: false, + detectOpenHandles: false, + displayName: multipleValidOptions('test-config', { + color: 'blue', + name: 'test-config', + } as const), + errorOnDeprecated: false, + extensionsToTreatAsEsm: [], + fakeTimers: { + advanceTimers: multipleValidOptions(40, true), + doNotFake: [ + 'Date', + 'hrtime', + 'nextTick', + 'performance', + 'queueMicrotask', + 'requestAnimationFrame', + 'cancelAnimationFrame', + 'requestIdleCallback', + 'cancelIdleCallback', + 'setImmediate', + 'clearImmediate', + 'setInterval', + 'clearInterval', + 'setTimeout', + 'clearTimeout', + ], + enableGlobally: true, + legacyFakeTimers: false, + now: 1483228800000, + timerLimit: 1000, + }, + filter: '/filter.js', + forceCoverageMatch: ['**/*.t.js'], + globalSetup: 'setup.js', + globalTeardown: 'teardown.js', + globals: {__DEV__: true}, + haste: { + computeSha1: true, + defaultPlatform: 'ios', + enableSymlinks: false, + forceNodeFilesystemAPI: true, + hasteImplModulePath: '/haste_impl.js', + hasteMapModulePath: '', + platforms: ['ios', 'android'], + retainAllFiles: false, + throwOnModuleCollision: false, + }, + id: 'string', + injectGlobals: true, + moduleDirectories: ['node_modules'], + moduleFileExtensions: [ + 'js', + 'mjs', + 'cjs', + 'json', + 'jsx', + 'ts', + 'tsx', + 'node', + ], + moduleNameMapper: { + '^React$': '/node_modules/react', + }, + modulePathIgnorePatterns: ['/build/'], + modulePaths: ['/shared/vendor/modules'], + prettierPath: '/node_modules/prettier', + resetMocks: false, + resetModules: false, + resolver: '/resolver.js', + restoreMocks: false, + rootDir: '/', + roots: [''], + runner: 'jest-runner', + runtime: '', + sandboxInjectedGlobals: [], + setupFiles: ['/setup.js'], + setupFilesAfterEnv: ['/testSetupFile.js'], + skipFilter: false, + skipNodeResolution: false, + slowTestThreshold: 5, + snapshotFormat: PRETTY_FORMAT_DEFAULTS, + snapshotResolver: '/snapshotResolver.js', + snapshotSerializers: ['my-serializer-module'], + testEnvironment: 'jest-environment-node', + testEnvironmentOptions: { + url: 'http://localhost', + userAgent: 'Agent/007', + }, + testLocationInResults: false, + testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'], + testPathIgnorePatterns: [NODE_MODULES_REGEXP], + testRegex: multipleValidOptions( + '(/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx?$', + ['/__tests__/\\.test\\.[jt]sx?$', '/__tests__/\\.spec\\.[jt]sx?$'], + ), + testRunner: 'circus', + transform: { + '\\.js$': '/preprocessor.js', + }, + transformIgnorePatterns: [NODE_MODULES_REGEXP], + unmockedModulePathPatterns: ['mock'], + watchPathIgnorePatterns: ['/e2e/'], + workerIdleMemoryLimit: multipleValidOptions(0.2, '50%'), +}; diff --git a/packages/jest-config/src/__tests__/normalize.test.ts b/packages/jest-config/src/__tests__/normalize.test.ts index 9e70d96dd220..31a067d6e181 100644 --- a/packages/jest-config/src/__tests__/normalize.test.ts +++ b/packages/jest-config/src/__tests__/normalize.test.ts @@ -101,6 +101,23 @@ it('keeps custom project id based on the projects rootDir', async () => { ); }); +it('validation warning occurs when options not for projects is set', async () => { + const mockWarn = jest.mocked(console.warn).mockImplementation(() => {}); + const rootDir = '/root/path/foo'; + await normalize( + { + bail: true, // an option not for projects + rootDir, + }, + {} as Config.Argv, + rootDir, + 1, + true, // isProjectOptions + ); + + expect(mockWarn).toHaveBeenCalledTimes(1); +}); + it('keeps custom ids based on the rootDir', async () => { const {options} = await normalize( { diff --git a/packages/jest-config/src/index.ts b/packages/jest-config/src/index.ts index 8a8142c502be..74585bad5d9b 100644 --- a/packages/jest-config/src/index.ts +++ b/packages/jest-config/src/index.ts @@ -53,11 +53,16 @@ export async function readConfig( }, ); + const packageRoot = + typeof packageRootOrConfig === 'string' + ? path.resolve(packageRootOrConfig) + : undefined; const {options, hasDeprecationWarnings} = await normalize( initialOptions, argv, configPath, projectIndex, + skipArgvConfigOption && !(packageRoot === parentConfigDirname), ); const {globalConfig, projectConfig} = groupOptions(options); diff --git a/packages/jest-config/src/normalize.ts b/packages/jest-config/src/normalize.ts index c69dfac5c61c..74d37be0cff0 100644 --- a/packages/jest-config/src/normalize.ts +++ b/packages/jest-config/src/normalize.ts @@ -31,7 +31,10 @@ import {ValidationError, validate} from 'jest-validate'; import DEFAULT_CONFIG from './Defaults'; import DEPRECATED_CONFIG from './Deprecated'; import {validateReporters} from './ReporterValidationErrors'; -import VALID_CONFIG from './ValidConfig'; +import { + initialOptions as VALID_CONFIG, + initialProjectOptions as VALID_PROJECT_CONFIG, +} from './ValidConfig'; import {getDisplayNameColor} from './color'; import {DEFAULT_JS_PATTERN} from './constants'; import getMaxWorkers from './getMaxWorkers'; @@ -487,6 +490,7 @@ export default async function normalize( argv: Config.Argv, configPath?: string | null, projectIndex = Infinity, + isProjectOptions?: boolean, ): Promise<{ hasDeprecationWarnings: boolean; options: AllOptions; @@ -494,7 +498,7 @@ export default async function normalize( const {hasDeprecationWarnings} = validate(initialOptions, { comment: DOCUMENTATION_NOTE, deprecatedConfig: DEPRECATED_CONFIG, - exampleConfig: VALID_CONFIG, + exampleConfig: isProjectOptions ? VALID_PROJECT_CONFIG : VALID_CONFIG, recursiveDenylist: [ // 'coverageThreshold' allows to use 'global' and glob strings on the same // level, there's currently no way we can deal with such config