diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 4851a71868561..63e087dcea4a1 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -7,6 +7,7 @@ - New `--webpack-no-externals` flag added to `build` and `start` scripts. It disables scripts' assets generation, and omits the list of default externals ([#22310](https://github.com/WordPress/gutenberg/pull/22310)). - New `--webpack-bundle-analyzer` flag added to `build` and `start` scripts. It enables visualization for the size of webpack output files with an interactive zoomable treemap ([#22310](https://github.com/WordPress/gutenberg/pull/22310)). - New `--webpack--devtool` flag added to `start` script. It controls how source maps are generated. See options at https://webpack.js.org/configuration/devtool/#devtool ([#22310](https://github.com/WordPress/gutenberg/pull/22310)). +- The `test-e2e` and `test-unit` scripts will now disambiguate custom configurations, preferring a `jest-e2e.config.js`, `jest-e2e.config.json`, `jest-unit.config.js`, or `jest-unit.config.json` Jest configuration file if present, falling back to `jest.config.js` or `jest.config.json`. This allows for configurations which should only apply to one or the other test variant. ### Breaking Changes diff --git a/packages/scripts/README.md b/packages/scripts/README.md index ed7392db82b05..e9005dbeb8a7a 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -425,6 +425,12 @@ We enforce that all tests run serially in the current process using [--runInBand It uses [Jest](https://jestjs.io/) behind the scenes and you are able to use all of its [CLI options](https://jestjs.io/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test:e2e --help` or `npm run test:e2e:help` (as mentioned above) to view all of the available options. Learn more in the [Advanced Usage](#advanced-usage) section. +Should there be any situation where you want to provide your own Jest config, you can do so. + +* the command receives a `--config` argument. Example: `wp-scripts test-e2e --config my-jest-config.js`. +* there is a file called `jest-e2e.config.js`, `jest-e2e.config.json`, `jest.config.js`, or `jest.config.json` in the top-level directory of your package (at the same level than your `package.json`). +* a `jest` object can be provided in the `package.json` file with the test configuration. + ### `test-unit-js` _Alias_: `test-unit-jest` @@ -461,6 +467,12 @@ Jest will look for test files with any of the following popular naming conventio It uses [Jest](https://jestjs.io/) behind the scenes and you are able to use all of its [CLI options](https://jestjs.io/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test:unit --help` or `npm run test:unit:help` (as mentioned above) to view all of the available options. By default, it uses the set of recommended options defined in [@wordpress/jest-preset-default](https://www.npmjs.com/package/@wordpress/jest-preset-default) npm package. You can override them with your own options as described in [Jest documentation](https://jestjs.io/docs/en/configuration). Learn more in the [Advanced Usage](#advanced-usage) section. +Should there be any situation where you want to provide your own Jest config, you can do so. + +* the command receives a `--config` argument. Example: `wp-scripts test-unit --config my-jest-config.js`. +* there is a file called `jest-unit.config.js`, `jest-unit.config.json`, `jest.config.js`, or `jest.config.json` in the top-level directory of your package (at the same level than your `package.json`). +* a `jest` object can be provided in the `package.json` file with the test configuration. + ## Passing Node.js options `wp-scripts` supports the full array of [Node.js CLI options](https://nodejs.org/api/cli.html). They can be passed after the `wp-scripts` command and before the script name. diff --git a/packages/scripts/scripts/test-e2e.js b/packages/scripts/scripts/test-e2e.js index 7d2fac76665e1..474608e8487b4 100644 --- a/packages/scripts/scripts/test-e2e.js +++ b/packages/scripts/scripts/test-e2e.js @@ -20,12 +20,12 @@ const { sync: spawn } = require( 'cross-spawn' ); * Internal dependencies */ const { + getJestOverrideConfigFile, fromConfigRoot, getArgFromCLI, getArgsFromCLI, hasArgInCLI, hasProjectFile, - hasJestConfig, } = require( '../utils' ); const result = spawn( 'node', [ require.resolve( 'puppeteer/install' ) ], { @@ -46,11 +46,10 @@ if ( process.env.JEST_PUPPETEER_CONFIG = fromConfigRoot( 'puppeteer.config.js' ); } -const config = ! hasJestConfig() - ? [ - '--config', - JSON.stringify( require( fromConfigRoot( 'jest-e2e.config.js' ) ) ), - ] +const configFile = getJestOverrideConfigFile( 'e2e' ); + +const config = configFile + ? [ '--config', JSON.stringify( require( configFile ) ) ] : []; const hasRunInBand = hasArgInCLI( '--runInBand' ) || hasArgInCLI( '-i' ); diff --git a/packages/scripts/scripts/test-unit-jest.js b/packages/scripts/scripts/test-unit-jest.js index 69ea51c6a0a90..35ecd68d1a874 100644 --- a/packages/scripts/scripts/test-unit-jest.js +++ b/packages/scripts/scripts/test-unit-jest.js @@ -18,15 +18,12 @@ const jest = require( 'jest' ); /** * Internal dependencies */ -const { fromConfigRoot, getArgsFromCLI, hasJestConfig } = require( '../utils' ); +const { getJestOverrideConfigFile, getArgsFromCLI } = require( '../utils' ); -const config = ! hasJestConfig() - ? [ - '--config', - JSON.stringify( - require( fromConfigRoot( 'jest-unit.config.js' ) ) - ), - ] +const configFile = getJestOverrideConfigFile( 'unit' ); + +const config = configFile + ? [ '--config', JSON.stringify( require( configFile ) ) ] : []; jest.run( [ ...config, ...getArgsFromCLI() ] ); diff --git a/packages/scripts/utils/config.js b/packages/scripts/utils/config.js index d237c1d64b4ca..d38a6543cd5f5 100644 --- a/packages/scripts/utils/config.js +++ b/packages/scripts/utils/config.js @@ -12,7 +12,7 @@ const { hasArgInCLI, hasFileArgInCLI, } = require( './cli' ); -const { fromConfigRoot, hasProjectFile } = require( './file' ); +const { fromConfigRoot, fromProjectRoot, hasProjectFile } = require( './file' ); const { hasPackageProp } = require( './package' ); // See https://babeljs.io/docs/en/config-files#configuration-file-types @@ -24,9 +24,35 @@ const hasBabelConfig = () => hasProjectFile( '.babelrc' ) || hasPackageProp( 'babel' ); +/** + * Returns path to a Jest configuration which should be provided as the explicit + * configuration when there is none available for discovery by Jest in the + * project environment. Returns undefined if Jest should be allowed to discover + * an available configuration. + * + * This can be used in cases where multiple possible configurations are + * supported. Since Jest will only discover `jest.config.js`, or `jest` package + * directive, such custom configurations must be specified explicitly. + * + * @param {"e2e"|"unit"} suffix Suffix of configuration file to accept. + * + * @return {string=} Override or fallback configuration file path. + */ +function getJestOverrideConfigFile( suffix ) { + if ( hasArgInCLI( '-c' ) || hasArgInCLI( '--config' ) ) { + return; + } + + if ( hasProjectFile( `jest-${ suffix }.config.js` ) ) { + return fromProjectRoot( `jest-${ suffix }.config.js` ); + } + + if ( ! hasJestConfig() ) { + return fromConfigRoot( `jest-${ suffix }.config.js` ); + } +} + const hasJestConfig = () => - hasArgInCLI( '-c' ) || - hasArgInCLI( '--config' ) || hasProjectFile( 'jest.config.js' ) || hasProjectFile( 'jest.config.json' ) || hasPackageProp( 'jest' ); @@ -103,6 +129,7 @@ const getWebpackArgs = () => { module.exports = { getWebpackArgs, hasBabelConfig, + getJestOverrideConfigFile, hasJestConfig, hasPrettierConfig, }; diff --git a/packages/scripts/utils/index.js b/packages/scripts/utils/index.js index 5b0e62569108e..3768c9f8c1319 100644 --- a/packages/scripts/utils/index.js +++ b/packages/scripts/utils/index.js @@ -13,6 +13,7 @@ const { const { getWebpackArgs, hasBabelConfig, + getJestOverrideConfigFile, hasJestConfig, hasPrettierConfig, } = require( './config' ); @@ -36,6 +37,7 @@ module.exports = { hasBabelConfig, hasArgInCLI, hasFileArgInCLI, + getJestOverrideConfigFile, hasJestConfig, hasPackageProp, hasPrettierConfig, diff --git a/packages/scripts/utils/test/index.js b/packages/scripts/utils/test/index.js index ce2050b36220a..d1da2b0fa7229 100644 --- a/packages/scripts/utils/test/index.js +++ b/packages/scripts/utils/test/index.js @@ -6,17 +6,31 @@ import crossSpawn from 'cross-spawn'; /** * Internal dependencies */ -import { hasArgInCLI, hasProjectFile, spawnScript } from '../'; -import { getPackagePath as getPackagePathMock } from '../package'; +import { + hasArgInCLI, + hasProjectFile, + getJestOverrideConfigFile, + spawnScript, +} from '../'; +import { + getPackagePath as getPackagePathMock, + hasPackageProp as hasPackagePropMock, +} from '../package'; import { exit as exitMock, getArgsFromCLI as getArgsFromCLIMock, } from '../process'; +import { + hasProjectFile as hasProjectFileMock, + fromProjectRoot as fromProjectRootMock, + fromConfigRoot as fromConfigRootMock, +} from '../file'; jest.mock( '../package', () => { const module = jest.requireActual( '../package' ); jest.spyOn( module, 'getPackagePath' ); + jest.spyOn( module, 'hasPackageProp' ); return module; } ); @@ -28,6 +42,15 @@ jest.mock( '../process', () => { return module; } ); +jest.mock( '../file', () => { + const module = jest.requireActual( '../file' ); + + jest.spyOn( module, 'hasProjectFile' ); + jest.spyOn( module, 'fromProjectRoot' ); + jest.spyOn( module, 'fromConfigRoot' ); + + return module; +} ); describe( 'utils', () => { const crossSpawnMock = jest.spyOn( crossSpawn, 'sync' ); @@ -76,6 +99,80 @@ describe( 'utils', () => { } ); } ); + describe( 'getJestOverrideConfigFile', () => { + beforeEach( () => { + getArgsFromCLIMock.mockReturnValue( [] ); + hasPackagePropMock.mockReturnValue( false ); + hasProjectFileMock.mockReturnValue( false ); + fromProjectRootMock.mockImplementation( ( path ) => '/p/' + path ); + fromConfigRootMock.mockImplementation( ( path ) => '/c/' + path ); + } ); + + afterEach( () => { + getArgsFromCLIMock.mockReset(); + hasPackagePropMock.mockReset(); + hasProjectFileMock.mockReset(); + fromProjectRootMock.mockReset(); + fromConfigRootMock.mockReset(); + } ); + + it( 'should return undefined if --config flag is present', () => { + getArgsFromCLIMock.mockReturnValue( [ '--config=test' ] ); + + expect( getJestOverrideConfigFile( 'e2e' ) ).toBe( undefined ); + } ); + + it( 'should return undefined if -c flag is present', () => { + getArgsFromCLIMock.mockReturnValue( [ '-c=test' ] ); + + expect( getJestOverrideConfigFile( 'e2e' ) ).toBe( undefined ); + } ); + + it( 'should return variant project configuration if present', () => { + hasProjectFileMock.mockImplementation( + ( file ) => file === 'jest-e2e.config.js' + ); + + expect( getJestOverrideConfigFile( 'e2e' ) ).toBe( + '/p/jest-e2e.config.js' + ); + } ); + + it( 'should return undefined if jest.config.js available', () => { + hasProjectFileMock.mockImplementation( + ( file ) => file === 'jest.config.js' + ); + + expect( getJestOverrideConfigFile( 'e2e' ) ).toBe( undefined ); + } ); + + it( 'should return undefined if jest.config.json available', () => { + hasProjectFileMock.mockImplementation( + ( file ) => file === 'jest.config.json' + ); + + expect( getJestOverrideConfigFile( 'e2e' ) ).toBe( undefined ); + } ); + + it( 'should return undefined if jest package directive specified', () => { + hasPackagePropMock.mockImplementation( + ( prop ) => prop === 'jest' + ); + + expect( getJestOverrideConfigFile( 'e2e' ) ).toBe( undefined ); + } ); + + it( 'should return default configuration if nothing available', () => { + expect( getJestOverrideConfigFile( 'e2e' ) ).toBe( + '/c/jest-e2e.config.js' + ); + + expect( getJestOverrideConfigFile( 'unit' ) ).toBe( + '/c/jest-unit.config.js' + ); + } ); + } ); + describe( 'spawnScript', () => { const scriptName = 'test-unit-js';