diff --git a/.vscode/launch.json b/.vscode/launch.json index 250fcfecad..867396e489 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -71,7 +71,10 @@ "request": "attach", "name": "Attach to Port", "address": "localhost", - "port": 9229 + "port": 9229, + "skipFiles": [ + "/**" + ] } ] -} \ No newline at end of file +} diff --git a/packages/jest-runner/README.md b/packages/jest-runner/README.md index 21b6887d2e..6e525886a1 100644 --- a/packages/jest-runner/README.md +++ b/packages/jest-runner/README.md @@ -22,25 +22,26 @@ The @stryker-mutator/jest-runner is a plugin for Stryker to enable Jest as a tes For the minimum supported versions, see the peerDependencies section in package.json. ## Configuration - -### Configuring Stryker Make sure you set the `testRunner` option to "jest" and set `coverageAnalysis` to "off" in your Stryker configuration. ```javascript { - testRunner: 'jest' + testRunner: 'jest', coverageAnalysis: 'off' } ``` -### Configuring Jest +### Advanced configuration The @stryker-mutator/jest-runner also provides a couple of configurable options using the `jest` property in your Stryker config: ```javascript { jest: { projectType: 'custom', - config: require('path/to/your/custom/jestConfig.js'), + configFile: 'path/to/your/custom/jestConfig.js', + config: { + testEnvironment: 'jest-environment-jsdom-sixteen' + }, enableFindRelatedTests: true, } } @@ -51,11 +52,12 @@ The @stryker-mutator/jest-runner also provides a couple of configurable options | projectType (optional) | The type of project you are working on. | `custom` | `custom` uses the `config` option (see below)| | | | | `create-react-app` when you are using [create-react-app](https://github.com/facebook/create-react-app) | | | | | `create-react-app-ts` when you are using [create-react-app-typescript](https://github.com/wmonk/create-react-app-typescript) | -| config (optional) | A custom Jest configuration object. You could also use `require` to load it here. | undefined | | +| configFile (optional) | The path to your Jest config file. | undefined | | +| config (optional) | Custom Jest config. This will override file-based config. | undefined | | | enableFindRelatedTests (optional) | Whether to run jest with the `--findRelatedTests` flag. When `true`, Jest will only run tests related to the mutated file per test. (See [_--findRelatedTests_](https://jestjs.io/docs/en/cli.html#findrelatedtests-spaceseparatedlistofsourcefiles)) | true | false | -**Note:** When neither of the options are specified it will use the Jest configuration in your "package.json". \ -**Note:** the `projectType` option is ignored when the `config` option is specified. +**Note:** When the projectType is `custom` and no `configFile` is specified, your `jest.config.js` or `package.json` will be loaded. \ +**Note:** The `configFile` setting is **not** supported for `create-react-app` and `create-react-app-ts`. \ **Note:** Stryker currently only works for CRA-projects that have not been [_ejected_](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#npm-run-eject). The following is an example stryker.conf.js file: diff --git a/packages/jest-runner/schema/jest-runner-options.json b/packages/jest-runner/schema/jest-runner-options.json index 897e8b3152..eb184430ed 100644 --- a/packages/jest-runner/schema/jest-runner-options.json +++ b/packages/jest-runner/schema/jest-runner-options.json @@ -14,8 +14,12 @@ "$ref": "#/definitions/jestProjectType", "default": "custom" }, + "configFile": { + "description": "Path to your Jest config file. Please leave it empty if you want jest configuration to be loaded from package.json or a standard jest configuration file.", + "type": "string" + }, "config": { - "description": "A custom Jest configuration object. You could also use `require` to load it here. Please leave it empty if you want jest configuration to be loaded from package.json or a standard jest configuration file.", + "description": "A custom Jest configuration object. You could also use `require` to load it here.", "type": "object" }, "enableFindRelatedTests": { diff --git a/packages/jest-runner/src/JestOptionsEditor.ts b/packages/jest-runner/src/JestOptionsEditor.ts deleted file mode 100644 index a6596d29f0..0000000000 --- a/packages/jest-runner/src/JestOptionsEditor.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Logger } from '@stryker-mutator/api/logging'; -import { commonTokens, tokens } from '@stryker-mutator/api/plugin'; -import { OptionsEditor } from '@stryker-mutator/api/core'; - -import CustomJestConfigLoader from './configLoaders/CustomJestConfigLoader'; -import JestConfigLoader from './configLoaders/JestConfigLoader'; -import ReactScriptsJestConfigLoader from './configLoaders/ReactScriptsJestConfigLoader'; -import ReactScriptsTSJestConfigLoader from './configLoaders/ReactScriptsTSJestConfigLoader'; -import JEST_OVERRIDE_OPTIONS from './jestOverrideOptions'; -import { JestRunnerOptionsWithStrykerOptions } from './JestRunnerOptionsWithStrykerOptions'; - -const DEFAULT_PROJECT_NAME = 'custom'; - -export default class JestOptionsEditor implements OptionsEditor { - public static inject = tokens(commonTokens.logger); - - constructor(private readonly log: Logger) {} - - public edit(options: JestRunnerOptionsWithStrykerOptions): void { - // When no config property is set, load the configuration with the project type - options.jest.config = options.jest.config || (this.getConfigLoader(options.jest.projectType).loadConfig() as any); - - // Override some of the config properties to optimise Jest for Stryker - options.jest.config = this.overrideProperties((options.jest.config as unknown) as Jest.Configuration); - } - - private getConfigLoader(projectType: string): JestConfigLoader { - switch (projectType.toLowerCase()) { - case DEFAULT_PROJECT_NAME: - return new CustomJestConfigLoader(process.cwd()); - case 'create-react-app': - return new ReactScriptsJestConfigLoader(process.cwd()); - case 'create-react-app-ts': - return new ReactScriptsTSJestConfigLoader(process.cwd()); - case 'react': - this.log.warn( - 'DEPRECATED: The projectType "react" is deprecated. Use projectType "create-react-app" for react projects created by "create-react-app" or use "custom" for other react projects.' - ); - return new ReactScriptsJestConfigLoader(process.cwd()); - case 'react-ts': - this.log.warn( - 'DEPRECATED: The projectType "react-ts" is deprecated. Use projectType "create-react-app-ts" for react projects created by "create-react-app" or use "custom" for other react projects.' - ); - return new ReactScriptsTSJestConfigLoader(process.cwd()); - default: - throw new Error(`No configLoader available for ${projectType}`); - } - } - - private overrideProperties(config: Jest.Configuration) { - return { ...config, ...JEST_OVERRIDE_OPTIONS }; - } -} diff --git a/packages/jest-runner/src/JestTestRunner.ts b/packages/jest-runner/src/JestTestRunner.ts index d7a236d633..5411270b77 100644 --- a/packages/jest-runner/src/JestTestRunner.ts +++ b/packages/jest-runner/src/JestTestRunner.ts @@ -3,37 +3,41 @@ import { Logger } from '@stryker-mutator/api/logging'; import { commonTokens, Injector, OptionsContext, tokens } from '@stryker-mutator/api/plugin'; import { RunOptions, RunResult, RunStatus, TestResult, TestRunner, TestStatus } from '@stryker-mutator/api/test_runner'; -import { JEST_VERSION_TOKEN, jestTestAdapterFactory } from './jestTestAdapters'; +import { jestTestAdapterFactory } from './jestTestAdapters'; import JestTestAdapter from './jestTestAdapters/JestTestAdapter'; import { JestRunnerOptionsWithStrykerOptions } from './JestRunnerOptionsWithStrykerOptions'; +import JestConfigLoader from './configLoaders/JestConfigLoader'; +import { configLoaderToken, processEnvToken, jestTestAdapterToken, jestVersionToken } from './pluginTokens'; +import { configLoaderFactory } from './configLoaders'; +import JEST_OVERRIDE_OPTIONS from './jestOverrideOptions'; export function jestTestRunnerFactory(injector: Injector) { return injector - .provideValue(PROCESS_ENV_TOKEN, process.env) - .provideValue(JEST_VERSION_TOKEN, require('jest/package.json').version as string) - .provideFactory(JEST_TEST_ADAPTER_TOKEN, jestTestAdapterFactory) + .provideValue(processEnvToken, process.env) + .provideValue(jestVersionToken, require('jest/package.json').version as string) + .provideFactory(jestTestAdapterToken, jestTestAdapterFactory) + .provideFactory(configLoaderToken, configLoaderFactory) .injectClass(JestTestRunner); } jestTestRunnerFactory.inject = tokens(commonTokens.injector); -export const PROCESS_ENV_TOKEN = 'PROCESS_ENV_TOKEN'; -export const JEST_TEST_ADAPTER_TOKEN = 'jestTestAdapter'; - export default class JestTestRunner implements TestRunner { private readonly jestConfig: Jest.Configuration; private readonly enableFindRelatedTests: boolean; - public static inject = tokens(commonTokens.logger, commonTokens.options, PROCESS_ENV_TOKEN, JEST_TEST_ADAPTER_TOKEN); + public static inject = tokens(commonTokens.logger, commonTokens.options, processEnvToken, jestTestAdapterToken, configLoaderToken); constructor( private readonly log: Logger, options: StrykerOptions, private readonly processEnvRef: NodeJS.ProcessEnv, - private readonly jestTestAdapter: JestTestAdapter + private readonly jestTestAdapter: JestTestAdapter, + configLoader: JestConfigLoader ) { const jestOptions = options as JestRunnerOptionsWithStrykerOptions; // Get jest configuration from stryker options and assign it to jestConfig - this.jestConfig = (jestOptions.jest.config as unknown) as Jest.Configuration; + const configFromFile = configLoader.loadConfig(); + this.jestConfig = this.mergeConfigSettings(configFromFile, (jestOptions.jest.config as any) || {}); // Get enableFindRelatedTests from stryker jest options or default to true this.enableFindRelatedTests = jestOptions.jest.enableFindRelatedTests; @@ -111,4 +115,14 @@ export default class JestTestRunner implements TestRunner { return TestStatus.Skipped; } } + + private mergeConfigSettings(configFromFile: Jest.Configuration, config: Jest.Configuration) { + const stringify = (obj: object) => JSON.stringify(obj, null, 2); + this.log.trace( + `Merging file-based config ${stringify(configFromFile)} + with custom config ${stringify(config)} + and default (internal) stryker config ${JEST_OVERRIDE_OPTIONS}` + ); + return Object.assign(configFromFile, config, JEST_OVERRIDE_OPTIONS); + } } diff --git a/packages/jest-runner/src/configLoaders/CustomJestConfigLoader.ts b/packages/jest-runner/src/configLoaders/CustomJestConfigLoader.ts index 4e766f7661..889110c368 100644 --- a/packages/jest-runner/src/configLoaders/CustomJestConfigLoader.ts +++ b/packages/jest-runner/src/configLoaders/CustomJestConfigLoader.ts @@ -1,6 +1,13 @@ import fs = require('fs'); import path from 'path'; +import { Logger } from '@stryker-mutator/api/logging'; +import { tokens, commonTokens } from '@stryker-mutator/api/plugin'; +import { StrykerOptions } from '@stryker-mutator/api/core'; + +import { loaderToken, projectRootToken } from '../pluginTokens'; +import { JestRunnerOptionsWithStrykerOptions } from '../JestRunnerOptionsWithStrykerOptions'; + import JestConfigLoader from './JestConfigLoader'; import { NodeRequireFunction } from './NodeRequireFunction'; @@ -8,11 +15,14 @@ import { NodeRequireFunction } from './NodeRequireFunction'; * The Default config loader will load the Jest configuration using the package.json in the package root */ export default class CustomJestConfigLoader implements JestConfigLoader { - private readonly _projectRoot: string; + public static inject = tokens(commonTokens.logger, commonTokens.options, loaderToken, projectRootToken); - constructor(projectRoot: string, private readonly _loader: NodeRequireFunction = require) { - this._projectRoot = projectRoot; - } + constructor( + private readonly log: Logger, + private readonly options: StrykerOptions, + private readonly require: NodeRequireFunction, + private readonly projectRoot: string + ) {} public loadConfig(): Jest.Configuration { const jestConfig = this.readConfigFromJestConfigFile() || this.readConfigFromPackageJson() || {}; @@ -21,7 +31,11 @@ export default class CustomJestConfigLoader implements JestConfigLoader { private readConfigFromJestConfigFile() { try { - return this._loader(path.join(this._projectRoot, 'jest.config.js')); + const jestOptions = this.options as JestRunnerOptionsWithStrykerOptions; + const configFilePath = path.join(this.projectRoot, jestOptions.jest?.configFile || 'jest.config.js'); + const config = this.require(configFilePath); + this.log.debug(`Read Jest config from ${configFilePath}`); + return config; } catch { /* Don't return anything (implicitly return undefined) */ } @@ -29,7 +43,10 @@ export default class CustomJestConfigLoader implements JestConfigLoader { private readConfigFromPackageJson() { try { - return JSON.parse(fs.readFileSync(path.join(this._projectRoot, 'package.json'), 'utf8')).jest; + const configFilePath = path.join(this.projectRoot, 'package.json'); + const config = JSON.parse(fs.readFileSync(configFilePath, 'utf8')).jest; + this.log.debug(`Read Jest config from ${configFilePath}`); + return config; } catch { /* Don't return anything (implicitly return undefined) */ } diff --git a/packages/jest-runner/src/configLoaders/ReactScriptsJestConfigLoader.ts b/packages/jest-runner/src/configLoaders/ReactScriptsJestConfigLoader.ts index aed7428461..6c65db7c46 100644 --- a/packages/jest-runner/src/configLoaders/ReactScriptsJestConfigLoader.ts +++ b/packages/jest-runner/src/configLoaders/ReactScriptsJestConfigLoader.ts @@ -1,15 +1,16 @@ import path from 'path'; +import { tokens } from '@stryker-mutator/api/plugin'; + import { createReactJestConfig } from '../utils/createReactJestConfig'; +import { projectRootToken, resolveToken } from '../pluginTokens'; import JestConfigLoader from './JestConfigLoader'; export default class ReactScriptsJestConfigLoader implements JestConfigLoader { - private readonly projectRoot: string; + public static inject = tokens(resolveToken, projectRootToken); - constructor(projectRoot: string, private readonly resolve: RequireResolve = require.resolve) { - this.projectRoot = projectRoot; - } + constructor(private readonly resolve: RequireResolve, private readonly projectRoot: string) {} public loadConfig(): Jest.Configuration { try { @@ -19,9 +20,6 @@ export default class ReactScriptsJestConfigLoader implements JestConfigLoader { // Create the React configuration for Jest const jestConfiguration = this.createJestConfig(reactScriptsLocation); - // Set test environment to jsdom (otherwise Jest won't run) - jestConfiguration.testEnvironment = 'jsdom'; - return jestConfiguration; } catch (e) { if (this.isNodeErrnoException(e) && e.code === 'MODULE_NOT_FOUND') { diff --git a/packages/jest-runner/src/configLoaders/ReactScriptsTSJestConfigLoader.ts b/packages/jest-runner/src/configLoaders/ReactScriptsTSJestConfigLoader.ts index 22b03908dd..512d4e99ba 100644 --- a/packages/jest-runner/src/configLoaders/ReactScriptsTSJestConfigLoader.ts +++ b/packages/jest-runner/src/configLoaders/ReactScriptsTSJestConfigLoader.ts @@ -1,15 +1,16 @@ import * as path from 'path'; +import { tokens } from '@stryker-mutator/api/plugin'; + import { createReactTsJestConfig } from '../utils/createReactJestConfig'; +import { projectRootToken, resolveToken } from '../pluginTokens'; import JestConfigLoader from './JestConfigLoader'; export default class ReactScriptsTSJestConfigLoader implements JestConfigLoader { - private readonly projectRoot: string; + public static inject = tokens(resolveToken, projectRootToken); - constructor(projectRoot: string, private readonly resolve = require.resolve) { - this.projectRoot = projectRoot; - } + constructor(private readonly resolve: RequireResolve, private readonly projectRoot: string) {} public loadConfig(): Jest.Configuration { try { @@ -18,10 +19,7 @@ export default class ReactScriptsTSJestConfigLoader implements JestConfigLoader // Create the React configuration for Jest const jestConfiguration = this.createJestConfig(reactScriptsTsLocation); - - // Set test environment to jsdom (otherwise Jest won't run) jestConfiguration.testEnvironment = 'jsdom'; - return jestConfiguration; } catch (e) { if (this.isNodeErrnoException(e) && e.code === 'MODULE_NOT_FOUND') { diff --git a/packages/jest-runner/src/configLoaders/index.ts b/packages/jest-runner/src/configLoaders/index.ts new file mode 100644 index 0000000000..90c28deb5e --- /dev/null +++ b/packages/jest-runner/src/configLoaders/index.ts @@ -0,0 +1,50 @@ +import { tokens, commonTokens, Injector, OptionsContext } from '@stryker-mutator/api/plugin'; +import { StrykerOptions } from '@stryker-mutator/api/core'; +import { Logger } from '@stryker-mutator/api/logging'; + +import { JestRunnerOptionsWithStrykerOptions } from '../JestRunnerOptionsWithStrykerOptions'; +import { loaderToken, resolveToken, projectRootToken } from '../pluginTokens'; + +import CustomJestConfigLoader from './CustomJestConfigLoader'; +import ReactScriptsJestConfigLoader from './ReactScriptsJestConfigLoader'; +import ReactScriptsTSJestConfigLoader from './ReactScriptsTSJestConfigLoader'; + +configLoaderFactory.inject = tokens(commonTokens.options, commonTokens.injector, commonTokens.logger); +export function configLoaderFactory(options: StrykerOptions, injector: Injector, log: Logger) { + const warnAboutConfigFile = (projectType: string, configFile: string | undefined) => { + if (configFile) { + log.warn(`Config setting "configFile" is not supported for projectType "${projectType}"`); + } + }; + const optionsWithJest: JestRunnerOptionsWithStrykerOptions = options as JestRunnerOptionsWithStrykerOptions; + + const configLoaderInjector = injector + .provideValue(loaderToken, require) + .provideValue(resolveToken, require.resolve) + .provideValue(projectRootToken, process.cwd()); + + switch (optionsWithJest.jest.projectType) { + case 'custom': + return configLoaderInjector.injectClass(CustomJestConfigLoader); + case 'create-react-app': + warnAboutConfigFile(optionsWithJest.jest.projectType, optionsWithJest.jest.configFile); + return configLoaderInjector.injectClass(ReactScriptsJestConfigLoader); + case 'create-react-app-ts': + warnAboutConfigFile(optionsWithJest.jest.projectType, optionsWithJest.jest.configFile); + return configLoaderInjector.injectClass(ReactScriptsTSJestConfigLoader); + case 'react': + log.warn( + 'DEPRECATED: The projectType "react" is deprecated. Use projectType "create-react-app" for react projects created by "create-react-app" or use "custom" for other react projects.' + ); + warnAboutConfigFile(optionsWithJest.jest.projectType, optionsWithJest.jest.configFile); + return configLoaderInjector.injectClass(ReactScriptsJestConfigLoader); + case 'react-ts': + log.warn( + 'DEPRECATED: The projectType "react-ts" is deprecated. Use projectType "create-react-app-ts" for react projects created by "create-react-app" or use "custom" for other react projects.' + ); + warnAboutConfigFile(optionsWithJest.jest.projectType, optionsWithJest.jest.configFile); + return configLoaderInjector.injectClass(ReactScriptsTSJestConfigLoader); + default: + throw new Error(`No configLoader available for ${optionsWithJest.jest.projectType}`); + } +} diff --git a/packages/jest-runner/src/index.ts b/packages/jest-runner/src/index.ts index cdb05a48bb..c38a46b7d9 100644 --- a/packages/jest-runner/src/index.ts +++ b/packages/jest-runner/src/index.ts @@ -1,12 +1,11 @@ -import { declareClassPlugin, declareFactoryPlugin, PluginKind } from '@stryker-mutator/api/plugin'; +import { declareFactoryPlugin, PluginKind } from '@stryker-mutator/api/plugin'; + +import strykerValidationSchema from '../schema/jest-runner-options.json'; -import JestOptionsEditor from './JestOptionsEditor'; import { jestTestRunnerFactory } from './JestTestRunner'; process.env.BABEL_ENV = 'test'; -export const strykerPlugins = [ - declareClassPlugin(PluginKind.OptionsEditor, 'jest', JestOptionsEditor), - declareFactoryPlugin(PluginKind.TestRunner, 'jest', jestTestRunnerFactory), -]; -export * as strykerValidationSchema from '../schema/jest-runner-options.json'; +export const strykerPlugins = [declareFactoryPlugin(PluginKind.TestRunner, 'jest', jestTestRunnerFactory)]; + +export { strykerValidationSchema }; diff --git a/packages/jest-runner/src/jestTestAdapters/index.ts b/packages/jest-runner/src/jestTestAdapters/index.ts index 4adb9d4a94..0b3acab688 100644 --- a/packages/jest-runner/src/jestTestAdapters/index.ts +++ b/packages/jest-runner/src/jestTestAdapters/index.ts @@ -2,12 +2,12 @@ import { Logger } from '@stryker-mutator/api/logging'; import { BaseContext, commonTokens, Injector, tokens } from '@stryker-mutator/api/plugin'; import semver from 'semver'; +import { jestVersionToken } from '../pluginTokens'; + import JestLessThan25TestAdapter from './JestLessThan25Adapter'; import JestTestAdapter from './JestTestAdapter'; import JestGreaterThan25TestAdapter from './JestGreaterThan25Adapter'; -export const JEST_VERSION_TOKEN = 'jestVersion'; - export function jestTestAdapterFactory(log: Logger, jestVersion: string, injector: Injector) { if (semver.satisfies(jestVersion, '<22.0.0')) { log.debug('Detected Jest below 22.0.0: %s', jestVersion); @@ -19,5 +19,5 @@ export function jestTestAdapterFactory(log: Logger, jestVersion: string, injecto } } -jestTestAdapterFactory.inject = tokens(commonTokens.logger, JEST_VERSION_TOKEN, commonTokens.injector); +jestTestAdapterFactory.inject = tokens(commonTokens.logger, jestVersionToken, commonTokens.injector); export { JestTestAdapter }; diff --git a/packages/jest-runner/src/pluginTokens.ts b/packages/jest-runner/src/pluginTokens.ts new file mode 100644 index 0000000000..519b66b64c --- /dev/null +++ b/packages/jest-runner/src/pluginTokens.ts @@ -0,0 +1,7 @@ +export const projectRootToken = 'projectRoot'; +export const loaderToken = 'loader'; +export const resolveToken = 'resolve'; +export const configLoaderToken = 'configLoader'; +export const processEnvToken = 'PROCESS_ENV_TOKEN'; +export const jestTestAdapterToken = 'jestTestAdapter'; +export const jestVersionToken = 'jestVersion'; diff --git a/packages/jest-runner/test/integration/JestOptionsEditor.it.spec.ts b/packages/jest-runner/test/integration/JestOptionsEditor.it.spec.ts deleted file mode 100644 index 6595dd4184..0000000000 --- a/packages/jest-runner/test/integration/JestOptionsEditor.it.spec.ts +++ /dev/null @@ -1,256 +0,0 @@ -import * as path from 'path'; - -import { testInjector, factory } from '@stryker-mutator/test-helpers'; -import { expect } from 'chai'; -import * as sinon from 'sinon'; - -import JestOptionsEditor from '../../src/JestOptionsEditor'; -import { JestRunnerOptionsWithStrykerOptions } from '../../src/JestRunnerOptionsWithStrykerOptions'; - -describe('Integration test for Jest OptionsEditor', () => { - let jestConfigEditor: JestOptionsEditor; - let getProjectRootStub: sinon.SinonStub; - - const projectRoot: string = process.cwd(); - let options: JestRunnerOptionsWithStrykerOptions; - - beforeEach(() => { - getProjectRootStub = sinon.stub(process, 'cwd'); - getProjectRootStub.returns(projectRoot); - - jestConfigEditor = testInjector.injector.injectClass(JestOptionsEditor); - - options = factory.strykerOptions() as JestRunnerOptionsWithStrykerOptions; - options.jest = { - enableFindRelatedTests: true, - projectType: 'custom', - }; - }); - - it('should create a Jest configuration for a create-react-app project', () => { - options.jest.projectType = 'create-react-app'; - - jestConfigEditor.edit(options); - - const expectedResult = { - bail: false, - collectCoverage: false, - collectCoverageFrom: ['!src/**/*.d.ts', 'src/**/*.{js,jsx,ts,tsx}'], - moduleFileExtensions: ['js', 'json', 'jsx', 'node', 'ts', 'tsx', 'web.js', 'web.jsx', 'web.ts', 'web.tsx'], - moduleNameMapper: { - '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy', - '^react-native$': 'react-native-web', - }, - notify: false, - rootDir: projectRoot, - setupFiles: [path.join(projectRoot, 'node_modules', 'react-app-polyfill', 'jsdom.js')], - setupTestFrameworkScriptFile: undefined, - testEnvironment: 'jsdom', - testMatch: ['/src/**/__tests__/**/*.{js,jsx,ts,tsx}', '/src/**/*.{spec,test}.{js,jsx,ts,tsx}'], - testResultsProcessor: undefined, - transform: { - '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': path.join(projectRoot, 'node_modules', 'react-scripts', 'config', 'jest', 'fileTransform.js'), - '^.+\\.(js|jsx|ts|tsx)$': path.join(projectRoot, 'node_modules', 'react-scripts', 'config', 'jest', 'babelTransform.js'), - '^.+\\.css$': path.join(projectRoot, 'node_modules', 'react-scripts', 'config', 'jest', 'cssTransform.js'), - }, - transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$', '^.+\\.module\\.(css|sass|scss)$'], - verbose: false, - }; - - assertJestConfig(expectedResult, options.jest.config); - }); - - it('should create a Jest configuration for a React project', () => { - options.jest.projectType = 'react'; - - jestConfigEditor.edit(options); - - const expectedResult = { - bail: false, - collectCoverage: false, - collectCoverageFrom: ['!src/**/*.d.ts', 'src/**/*.{js,jsx,ts,tsx}'], - moduleFileExtensions: ['js', 'json', 'jsx', 'node', 'ts', 'tsx', 'web.js', 'web.jsx', 'web.ts', 'web.tsx'], - moduleNameMapper: { - '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy', - '^react-native$': 'react-native-web', - }, - notify: false, - rootDir: projectRoot, - setupFiles: [path.join(projectRoot, 'node_modules', 'react-app-polyfill', 'jsdom.js')], - setupTestFrameworkScriptFile: undefined, - testEnvironment: 'jsdom', - testMatch: ['/src/**/__tests__/**/*.{js,jsx,ts,tsx}', '/src/**/*.{spec,test}.{js,jsx,ts,tsx}'], - testResultsProcessor: undefined, - transform: { - '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': path.join(projectRoot, 'node_modules', 'react-scripts', 'config', 'jest', 'fileTransform.js'), - '^.+\\.(js|jsx|ts|tsx)$': path.join(projectRoot, 'node_modules', 'react-scripts', 'config', 'jest', 'babelTransform.js'), - '^.+\\.css$': path.join(projectRoot, 'node_modules', 'react-scripts', 'config', 'jest', 'cssTransform.js'), - }, - transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$', '^.+\\.module\\.(css|sass|scss)$'], - verbose: false, - }; - - assertJestConfig(expectedResult, options.jest.config); - }); - - it('should log a deprecation warning when projectType is "react"', () => { - options.jest.projectType = 'react'; - - jestConfigEditor.edit(options); - - expect(testInjector.logger.warn).calledWith( - 'DEPRECATED: The projectType "react" is deprecated. Use projectType "create-react-app" for react projects created by "create-react-app" or use "custom" for other react projects.' - ); - }); - - it('should create a Jest configuration for a create-react-app + TypeScript project', () => { - options.jest.projectType = 'create-react-app-ts'; - - jestConfigEditor.edit(options); - - const expectedResult = { - bail: false, - collectCoverage: false, - collectCoverageFrom: ['!**/*.d.ts', 'src/**/*.{js,jsx,ts,tsx}'], - globals: { - 'ts-jest': { - tsConfigFile: path.join(projectRoot, 'testResources', 'reactTsProject', 'tsconfig.test.json'), - }, - }, - moduleFileExtensions: ['web.ts', 'ts', 'web.tsx', 'tsx', 'web.js', 'js', 'web.jsx', 'jsx', 'json', 'node', 'mjs'], - moduleNameMapper: { - '^react-native$': 'react-native-web', - }, - notify: false, - rootDir: projectRoot, - setupFiles: [path.join(projectRoot, 'node_modules', 'react-scripts-ts', 'config', 'polyfills.js')], - setupTestFrameworkScriptFile: undefined, - testEnvironment: 'jsdom', - testMatch: ['/src/**/__tests__/**/*.(j|t)s?(x)', '/src/**/?(*.)(spec|test).(j|t)s?(x)'], - testResultsProcessor: undefined, - testURL: 'http://localhost', - transform: { - '^(?!.*\\.(js|jsx|mjs|css|json)$)': path.join(projectRoot, 'node_modules', 'react-scripts-ts', 'config', 'jest', 'fileTransform.js'), - '^.+\\.(js|jsx|mjs)$': path.join(projectRoot, 'node_modules', 'react-scripts-ts', 'config', 'jest', 'babelTransform.js'), - '^.+\\.css$': path.join(projectRoot, 'node_modules', 'react-scripts-ts', 'config', 'jest', 'cssTransform.js'), - '^.+\\.tsx?$': path.join(projectRoot, 'node_modules', 'react-scripts-ts', 'config', 'jest', 'typescriptTransform.js'), - }, - transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|ts|tsx)$'], - verbose: false, - }; - - assertJestConfig(expectedResult, options.jest.config); - }); - - it('should create a Jest configuration for a React + TypeScript project', () => { - options.jest.projectType = 'react-ts'; - - jestConfigEditor.edit(options); - - const expectedResult = { - bail: false, - collectCoverage: false, - collectCoverageFrom: ['!**/*.d.ts', 'src/**/*.{js,jsx,ts,tsx}'], - globals: { - 'ts-jest': { - tsConfigFile: path.join(projectRoot, 'testResources', 'reactTsProject', 'tsconfig.test.json'), - }, - }, - moduleFileExtensions: ['web.ts', 'ts', 'web.tsx', 'tsx', 'web.js', 'js', 'web.jsx', 'jsx', 'json', 'node', 'mjs'], - moduleNameMapper: { - '^react-native$': 'react-native-web', - }, - notify: false, - rootDir: projectRoot, - setupFiles: [path.join(projectRoot, 'node_modules', 'react-scripts-ts', 'config', 'polyfills.js')], - setupTestFrameworkScriptFile: undefined, - testEnvironment: 'jsdom', - testMatch: ['/src/**/__tests__/**/*.(j|t)s?(x)', '/src/**/?(*.)(spec|test).(j|t)s?(x)'], - testResultsProcessor: undefined, - testURL: 'http://localhost', - transform: { - '^(?!.*\\.(js|jsx|mjs|css|json)$)': path.join(projectRoot, 'node_modules', 'react-scripts-ts', 'config', 'jest', 'fileTransform.js'), - '^.+\\.(js|jsx|mjs)$': path.join(projectRoot, 'node_modules', 'react-scripts-ts', 'config', 'jest', 'babelTransform.js'), - '^.+\\.css$': path.join(projectRoot, 'node_modules', 'react-scripts-ts', 'config', 'jest', 'cssTransform.js'), - '^.+\\.tsx?$': path.join(projectRoot, 'node_modules', 'react-scripts-ts', 'config', 'jest', 'typescriptTransform.js'), - }, - transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|ts|tsx)$'], - verbose: false, - }; - - assertJestConfig(expectedResult, options.jest.config); - }); - - it('should log a deprecation warning when projectType is "react-ts"', () => { - options.jest.projectType = 'react-ts'; - - jestConfigEditor.edit(options); - - expect(testInjector.logger.warn).calledWith( - 'DEPRECATED: The projectType "react-ts" is deprecated. Use projectType "create-react-app-ts" for react projects created by "create-react-app" or use "custom" for other react projects.' - ); - }); - - it('should load the Jest configuration from the jest.config.js', () => { - getProjectRootStub.returns(path.join(process.cwd(), 'testResources', 'exampleProjectWithExplicitJestConfig')); - - jestConfigEditor.edit(options); - - expect(options.jest.projectType).to.equal('custom'); - expect(options.jest.config).to.deep.equal({ - bail: false, - collectCoverage: false, - moduleFileExtensions: ['js', 'json', 'jsx', 'node'], - notify: false, - testEnvironment: 'jest-environment-jsdom', - testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.jsx?$', - testResultsProcessor: undefined, - testRunner: 'jest-jasmine2', - testURL: 'http://localhost', - verbose: false, - }); - }); - - it('should load the Jest configuration from the package.json', () => { - getProjectRootStub.returns(path.join(process.cwd(), 'testResources', 'exampleProject')); - - jestConfigEditor.edit(options); - - expect(options.jest.projectType).to.equal('custom'); - expect(options.jest.config).to.deep.equal({ - bail: false, - collectCoverage: false, - moduleFileExtensions: ['js', 'json', 'jsx', 'node'], - notify: false, - testEnvironment: 'jest-environment-jsdom', - testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.jsx?$', - testResultsProcessor: undefined, - testRunner: 'jest-jasmine2', - testURL: 'http://localhost', - verbose: false, - }); - }); - - it('should load the default Jest configuration if there is no package.json config or jest.config.js', () => { - getProjectRootStub.returns(path.join(process.cwd(), 'testResources', 'exampleProjectWithDefaultJestConfig')); - - jestConfigEditor.edit(options); - - expect(options.jest.config).to.deep.equal({ - bail: false, - collectCoverage: false, - notify: false, - testResultsProcessor: undefined, - verbose: false, - }); - }); - function assertJestConfig(expected: any, actual: any) { - Object.keys(expected).forEach((key) => { - if (Array.isArray(expected[key])) { - expected[key].sort(); - actual[key].sort(); - } - expect(actual[key]).deep.eq(expected[key]); - }); - } -}); diff --git a/packages/jest-runner/test/integration/StrykerJestRunner.it.spec.ts b/packages/jest-runner/test/integration/JestTestRunner.it.spec.ts similarity index 93% rename from packages/jest-runner/test/integration/StrykerJestRunner.it.spec.ts rename to packages/jest-runner/test/integration/JestTestRunner.it.spec.ts index 6b2d17914b..48c75655cd 100644 --- a/packages/jest-runner/test/integration/StrykerJestRunner.it.spec.ts +++ b/packages/jest-runner/test/integration/JestTestRunner.it.spec.ts @@ -6,8 +6,7 @@ import * as sinon from 'sinon'; import { commonTokens } from '@stryker-mutator/api/plugin'; import { factory, testInjector } from '@stryker-mutator/test-helpers'; -import JestOptionsEditor from '../../src/JestOptionsEditor'; -import { jestTestRunnerFactory } from '../../src/JestTestRunner'; +import JestTestRunner, { jestTestRunnerFactory } from '../../src/JestTestRunner'; import { JestRunnerOptionsWithStrykerOptions } from '../../src/JestRunnerOptionsWithStrykerOptions'; import { JestOptions } from '../../src-generated/jest-runner-options'; import { createJestOptions } from '../helpers/producers'; @@ -22,7 +21,7 @@ const jestProjectRoot = process.cwd(); // Needed for Jest in order to run tests process.env.BABEL_ENV = 'test'; -describe('Integration test for Strykers Jest runner', () => { +describe(`${JestTestRunner.name} integration test`, () => { // Set timeout for integration tests to 10 seconds for travis let processCwdStub: sinon.SinonStub; @@ -43,17 +42,16 @@ describe('Integration test for Strykers Jest runner', () => { }); function createSut(overrides?: Partial) { - const jestOptionsEditor = testInjector.injector.injectClass(JestOptionsEditor); const options: JestRunnerOptionsWithStrykerOptions = factory.strykerWithPluginOptions({ jest: createJestOptions(overrides), }); - jestOptionsEditor.edit(options); return testInjector.injector.provideValue(commonTokens.options, options).injectFunction(jestTestRunnerFactory); } it('should run tests on the example React + TypeScript project', async () => { processCwdStub.returns(getProjectRoot('reactTsProject')); const jestTestRunner = createSut({ projectType: 'react-ts' }); + const result = await jestTestRunner.run(runOptions); expect(result.status).to.equal(RunStatus.Complete); diff --git a/packages/jest-runner/test/unit/JestOptionsEditor.spec.ts b/packages/jest-runner/test/unit/JestOptionsEditor.spec.ts deleted file mode 100644 index 200267fcf8..0000000000 --- a/packages/jest-runner/test/unit/JestOptionsEditor.spec.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { testInjector, factory } from '@stryker-mutator/test-helpers'; -import { assert, expect } from 'chai'; -import * as sinon from 'sinon'; - -import CustomJestConfigLoader, * as defaultJestConfigLoader from '../../src/configLoaders/CustomJestConfigLoader'; -import ReactScriptsJestConfigLoader, * as reactScriptsJestConfigLoader from '../../src/configLoaders/ReactScriptsJestConfigLoader'; -import ReactScriptsTSJestConfigLoader, * as reactScriptsTSJestConfigLoader from '../../src/configLoaders/ReactScriptsTSJestConfigLoader'; -import JestOptionsEditor from '../../src/JestOptionsEditor'; -import { JestRunnerOptionsWithStrykerOptions } from '../../src/JestRunnerOptionsWithStrykerOptions'; - -describe(JestOptionsEditor.name, () => { - let sut: JestOptionsEditor; - let customConfigLoaderStub: ConfigLoaderStub; - let reactScriptsJestConfigLoaderStub: ConfigLoaderStub; - let reactScriptsTSJestConfigLoaderStub: ConfigLoaderStub; - let options: JestRunnerOptionsWithStrykerOptions; - - beforeEach(() => { - customConfigLoaderStub = sinon.createStubInstance(CustomJestConfigLoader); - reactScriptsJestConfigLoaderStub = sinon.createStubInstance(ReactScriptsJestConfigLoader); - reactScriptsTSJestConfigLoaderStub = sinon.createStubInstance(ReactScriptsTSJestConfigLoader); - - sinon.stub(defaultJestConfigLoader, 'default').returns(customConfigLoaderStub); - sinon.stub(reactScriptsJestConfigLoader, 'default').returns(reactScriptsJestConfigLoaderStub); - sinon.stub(reactScriptsTSJestConfigLoader, 'default').returns(reactScriptsTSJestConfigLoaderStub); - - const defaultOptions: Partial = { - collectCoverage: true, - verbose: true, - bail: false, - testResultsProcessor: 'someResultProcessor', - }; - customConfigLoaderStub.loadConfig.returns(defaultOptions); - reactScriptsJestConfigLoaderStub.loadConfig.returns(defaultOptions); - reactScriptsTSJestConfigLoaderStub.loadConfig.returns(defaultOptions); - - sut = testInjector.injector.injectClass(JestOptionsEditor); - options = factory.strykerWithPluginOptions({ - jest: { - enableFindRelatedTests: true, - projectType: 'custom', - }, - }); - }); - - it('should call the defaultConfigLoader loadConfig method when no projectType is defined', () => { - sut.edit(options); - - expect(options.jest.projectType).eq('custom'); - assert(customConfigLoaderStub.loadConfig.calledOnce, 'CustomConfigLoader loadConfig not called'); - }); - - it("should call the ReactScriptsJestConfigLoader loadConfig method when 'react' is defined as projectType", () => { - options.jest = { projectType: 'react', enableFindRelatedTests: true }; - - sut.edit(options); - - assert(reactScriptsJestConfigLoaderStub.loadConfig.calledOnce, 'ReactScriptsJestConfigLoader loadConfig not called'); - }); - - it("should call the ReactScriptsTSJestConfigLoader loadConfig method when 'react-ts' is defined as projectType", () => { - options.jest = { projectType: 'react-ts', enableFindRelatedTests: true }; - - sut.edit(options); - - assert(reactScriptsTSJestConfigLoaderStub.loadConfig.calledOnce, 'ReactScriptsTSJestConfigLoader loadConfig not called'); - }); - - it('should override verbose, collectCoverage, testResultsProcessor, notify and bail on all loaded configs', () => { - sut.edit(options); - - expect(options.jest.config).to.deep.equal({ - bail: false, - collectCoverage: false, - notify: false, - testResultsProcessor: undefined, - verbose: false, - }); - }); -}); - -interface ConfigLoaderStub { - loadConfig: sinon.SinonStub; -} diff --git a/packages/jest-runner/test/unit/JestTestRunner.spec.ts b/packages/jest-runner/test/unit/JestTestRunner.spec.ts index bb689bed55..9fde80ad24 100644 --- a/packages/jest-runner/test/unit/JestTestRunner.spec.ts +++ b/packages/jest-runner/test/unit/JestTestRunner.spec.ts @@ -4,20 +4,25 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { JestTestAdapter } from '../../src/jestTestAdapters'; -import JestTestRunner, { JEST_TEST_ADAPTER_TOKEN, PROCESS_ENV_TOKEN } from '../../src/JestTestRunner'; +import JestTestRunner from '../../src/JestTestRunner'; import * as producers from '../helpers/producers'; +import { processEnvToken, jestTestAdapterToken, configLoaderToken } from '../../src/pluginTokens'; +import JestConfigLoader from '../../src/configLoaders/JestConfigLoader'; describe('JestTestRunner', () => { const basePath = '/path/to/project/root'; const runOptions: RunOptions = { timeout: 0 }; let jestTestAdapterMock: sinon.SinonStubbedInstance; + let jestConfigLoaderMock: sinon.SinonStubbedInstance; let jestTestRunner: JestTestRunner; let processEnvMock: NodeJS.ProcessEnv; beforeEach(() => { jestTestAdapterMock = { run: sinon.stub() }; jestTestAdapterMock.run.resolves({ results: { testResults: [] } }); + jestConfigLoaderMock = { loadConfig: sinon.stub() }; + jestConfigLoaderMock.loadConfig.resolves({}); testInjector.options.jest = { config: { property: 'value' } }; testInjector.options.basePath = basePath; @@ -27,8 +32,9 @@ describe('JestTestRunner', () => { }; jestTestRunner = testInjector.injector - .provideValue(PROCESS_ENV_TOKEN, processEnvMock) - .provideValue(JEST_TEST_ADAPTER_TOKEN, (jestTestAdapterMock as unknown) as JestTestAdapter) + .provideValue(processEnvToken, processEnvMock) + .provideValue(jestTestAdapterToken, (jestTestAdapterMock as unknown) as JestTestAdapter) + .provideValue(configLoaderToken, jestConfigLoaderMock) .injectClass(JestTestRunner); }); @@ -161,4 +167,18 @@ describe('JestTestRunner', () => { expect(processEnvMock.NODE_ENV).to.equal('stryker'); }); + + it('should override verbose, collectCoverage, testResultsProcessor, notify and bail on all loaded configs', async () => { + await jestTestRunner.run(runOptions); + + expect( + jestTestAdapterMock.run.calledWith({ + bail: false, + collectCoverage: false, + notify: false, + testResultsProcessor: undefined, + verbose: false, + }) + ); + }); }); diff --git a/packages/jest-runner/test/unit/configLoaders/CustomConfigLoader.spec.ts b/packages/jest-runner/test/unit/configLoaders/CustomConfigLoader.spec.ts index 13cf0c9262..9e83dbac8f 100644 --- a/packages/jest-runner/test/unit/configLoaders/CustomConfigLoader.spec.ts +++ b/packages/jest-runner/test/unit/configLoaders/CustomConfigLoader.spec.ts @@ -3,6 +3,7 @@ import path from 'path'; import { assert, expect } from 'chai'; import sinon from 'sinon'; +import { logger, strykerOptions } from '@stryker-mutator/test-helpers/src/factory'; import CustomJestConfigLoader from '../../../src/configLoaders/CustomJestConfigLoader'; @@ -19,7 +20,7 @@ describe(CustomJestConfigLoader.name, () => { readFileSyncStub.returns('{ "jest": { "exampleProperty": "examplePackageJsonValue" }}'); requireStub.returns({ exampleProperty: 'exampleJestConfigValue' }); - defaultConfigLoader = new CustomJestConfigLoader(projectRoot, requireStub); + defaultConfigLoader = new CustomJestConfigLoader(logger(), strykerOptions(), requireStub, projectRoot); }); it('should load the Jest configuration from the jest.config.js in the project root', () => { diff --git a/packages/jest-runner/test/unit/configLoaders/DefaultConfigLoader.spec.ts b/packages/jest-runner/test/unit/configLoaders/DefaultConfigLoader.spec.ts index ef59e9ffd0..53ab255670 100644 --- a/packages/jest-runner/test/unit/configLoaders/DefaultConfigLoader.spec.ts +++ b/packages/jest-runner/test/unit/configLoaders/DefaultConfigLoader.spec.ts @@ -3,6 +3,7 @@ import path from 'path'; import { assert, expect } from 'chai'; import sinon from 'sinon'; +import { logger, strykerOptions } from '@stryker-mutator/test-helpers/src/factory'; import CustomJestConfigLoader from '../../../src/configLoaders/CustomJestConfigLoader'; @@ -19,7 +20,7 @@ describe(`${CustomJestConfigLoader.name} integration`, () => { fsStub.readFileSync.returns('{ "jest": { "exampleProperty": "examplePackageJsonValue" }}'); requireStub.returns({ exampleProperty: 'exampleJestConfigValue' }); - sut = new CustomJestConfigLoader(projectRoot, requireStub); + sut = new CustomJestConfigLoader(logger(), strykerOptions(), requireStub, projectRoot); }); it('should load the Jest configuration from the jest.config.js in the projectroot', () => { diff --git a/packages/jest-runner/test/unit/configLoaders/ReactScriptsJestConfigLoader.spec.ts b/packages/jest-runner/test/unit/configLoaders/ReactScriptsJestConfigLoader.spec.ts index 1d6a6b0114..7bb3093264 100644 --- a/packages/jest-runner/test/unit/configLoaders/ReactScriptsJestConfigLoader.spec.ts +++ b/packages/jest-runner/test/unit/configLoaders/ReactScriptsJestConfigLoader.spec.ts @@ -25,7 +25,7 @@ describe(ReactScriptsJestConfigLoader.name, () => { requireResolveStub = sinon.stub(); requireResolveStub.returns(reactScriptsPackagePath); - sut = new ReactScriptsJestConfigLoader(projectRoot, (requireResolveStub as unknown) as RequireResolve); + sut = new ReactScriptsJestConfigLoader((requireResolveStub as unknown) as RequireResolve, projectRoot); }); it('should load the configuration via the createJestConfig method provided by react-scripts', () => { @@ -41,7 +41,6 @@ describe(ReactScriptsJestConfigLoader.name, () => { eject: false, projectRoot: '/path/to/project', relativePath: path.join('node_modules', 'react-scripts', 'test'), - testEnvironment: 'jsdom', }); }); diff --git a/packages/jest-runner/test/unit/configLoaders/ReactScriptsTsJestConfigLoader.spec.ts b/packages/jest-runner/test/unit/configLoaders/ReactScriptsTsJestConfigLoader.spec.ts index f529368e64..57f8946beb 100644 --- a/packages/jest-runner/test/unit/configLoaders/ReactScriptsTsJestConfigLoader.spec.ts +++ b/packages/jest-runner/test/unit/configLoaders/ReactScriptsTsJestConfigLoader.spec.ts @@ -25,7 +25,7 @@ describe(ReactScriptsTSJestConfigLoader.name, () => { requireResolveStub = sinon.stub(); requireResolveStub.returns(reactScriptsTsPackagePath); - sut = new ReactScriptsTSJestConfigLoader(projectRoot, (requireResolveStub as unknown) as RequireResolve); + sut = new ReactScriptsTSJestConfigLoader((requireResolveStub as unknown) as RequireResolve, projectRoot); }); it('should load the configuration via the createJestConfig method provided by react-scripts-ts', () => { diff --git a/packages/jest-runner/test/unit/configLoaders/configLoaderFactory.spec.ts b/packages/jest-runner/test/unit/configLoaders/configLoaderFactory.spec.ts new file mode 100644 index 0000000000..9cfedc174b --- /dev/null +++ b/packages/jest-runner/test/unit/configLoaders/configLoaderFactory.spec.ts @@ -0,0 +1,106 @@ +import { testInjector, factory } from '@stryker-mutator/test-helpers'; +import { expect } from 'chai'; +import Sinon, * as sinon from 'sinon'; +import { commonTokens } from '@stryker-mutator/api/plugin'; + +import CustomJestConfigLoader, * as defaultJestConfigLoader from '../../../src/configLoaders/CustomJestConfigLoader'; +import ReactScriptsJestConfigLoader, * as reactScriptsJestConfigLoader from '../../../src/configLoaders/ReactScriptsJestConfigLoader'; +import ReactScriptsTSJestConfigLoader, * as reactScriptsTSJestConfigLoader from '../../../src/configLoaders/ReactScriptsTSJestConfigLoader'; +import { JestRunnerOptionsWithStrykerOptions } from '../../../src/JestRunnerOptionsWithStrykerOptions'; +import { configLoaderFactory } from '../../../src/configLoaders'; + +describe(configLoaderFactory.name, () => { + let customConfigLoaderStub: Sinon.SinonStubbedInstance; + let reactScriptsJestConfigLoaderStub: Sinon.SinonStubbedInstance; + let reactScriptsTSJestConfigLoaderStub: Sinon.SinonStubbedInstance; + let options: JestRunnerOptionsWithStrykerOptions; + + beforeEach(() => { + customConfigLoaderStub = sinon.createStubInstance(CustomJestConfigLoader); + reactScriptsJestConfigLoaderStub = sinon.createStubInstance(ReactScriptsJestConfigLoader); + reactScriptsTSJestConfigLoaderStub = sinon.createStubInstance(ReactScriptsTSJestConfigLoader); + + sinon.stub(defaultJestConfigLoader, 'default').returns(customConfigLoaderStub); + sinon.stub(reactScriptsJestConfigLoader, 'default').returns(reactScriptsJestConfigLoaderStub); + sinon.stub(reactScriptsTSJestConfigLoader, 'default').returns(reactScriptsTSJestConfigLoaderStub); + + const defaultOptions: Partial = { + collectCoverage: true, + verbose: true, + bail: false, + testResultsProcessor: 'someResultProcessor', + }; + customConfigLoaderStub.loadConfig.returns(defaultOptions); + reactScriptsJestConfigLoaderStub.loadConfig.returns(defaultOptions); + reactScriptsTSJestConfigLoaderStub.loadConfig.returns(defaultOptions); + + options = factory.strykerWithPluginOptions({ + jest: { + enableFindRelatedTests: true, + projectType: 'custom', + }, + }); + }); + + it('should call the defaultConfigLoader loadConfig method when no projectType is defined', () => { + const sut = testInjector.injector.provideValue(commonTokens.options, options).injectFunction(configLoaderFactory); + + expect(sut).eq(customConfigLoaderStub); + }); + + describe('with "projectType": "react"', () => { + beforeEach(() => { + options.jest.projectType = 'react'; + }); + + it('should create a ReactScriptsJestConfigLoader', () => { + const sut = testInjector.injector.provideValue(commonTokens.options, options).injectFunction(configLoaderFactory); + + expect(sut).eq(reactScriptsJestConfigLoaderStub); + }); + + it('should warn when a configFile is set', () => { + testConfigFileWarning(options); + }); + + it('should log a deprecation warning', () => { + testInjector.injector.provideValue(commonTokens.options, options).injectFunction(configLoaderFactory); + + expect(testInjector.logger.warn).calledWith( + 'DEPRECATED: The projectType "react" is deprecated. Use projectType "create-react-app" for react projects created by "create-react-app" or use "custom" for other react projects.' + ); + }); + }); + + describe('with "projectType": "react-ts"', () => { + beforeEach(() => { + options.jest.projectType = 'react-ts'; + }); + + it('should create a ReactScriptsTSJestConfigLoader', () => { + const sut = testInjector.injector.provideValue(commonTokens.options, options).injectFunction(configLoaderFactory); + + expect(sut).eq(reactScriptsTSJestConfigLoaderStub); + }); + + it('should warn when a configFile is set', () => { + testConfigFileWarning(options); + }); + + it('should log a deprecation warning', () => { + testInjector.injector.provideValue(commonTokens.options, options).injectFunction(configLoaderFactory); + + expect(testInjector.logger.warn).calledWith( + 'DEPRECATED: The projectType "react-ts" is deprecated. Use projectType "create-react-app-ts" for react projects created by "create-react-app" or use "custom" for other react projects.' + ); + }); + }); +}); + +function testConfigFileWarning(options: JestRunnerOptionsWithStrykerOptions) { + options.jest.configFile = 'jest.conf.js'; + + testInjector.injector.provideValue(commonTokens.options, options).injectFunction(configLoaderFactory); + + expect(testInjector.logger.warn).calledWith(`Config setting "configFile" is not supported for projectType "${options.jest.projectType}"`); +} diff --git a/packages/jest-runner/test/unit/jestTestAdapters/JestTestAdapterFactory.spec.ts b/packages/jest-runner/test/unit/jestTestAdapters/JestTestAdapterFactory.spec.ts index 6673b06e80..485a12f92c 100644 --- a/packages/jest-runner/test/unit/jestTestAdapters/JestTestAdapterFactory.spec.ts +++ b/packages/jest-runner/test/unit/jestTestAdapters/JestTestAdapterFactory.spec.ts @@ -1,15 +1,16 @@ import { testInjector } from '@stryker-mutator/test-helpers'; import { expect } from 'chai'; -import { JEST_VERSION_TOKEN, JestTestAdapter, jestTestAdapterFactory } from '../../../src/jestTestAdapters'; +import { JestTestAdapter, jestTestAdapterFactory } from '../../../src/jestTestAdapters'; import JestGreaterThan25Adapter from '../../../src/jestTestAdapters/JestGreaterThan25Adapter'; import JestLessThan25Adapter from '../../../src/jestTestAdapters/JestLessThan25Adapter'; +import { jestVersionToken } from '../../../src/pluginTokens'; describe(jestTestAdapterFactory.name, () => { let jestVersion: string; function act(): JestTestAdapter { - return testInjector.injector.provideValue(JEST_VERSION_TOKEN, jestVersion).injectFunction(jestTestAdapterFactory); + return testInjector.injector.provideValue(jestVersionToken, jestVersion).injectFunction(jestTestAdapterFactory); } it('should return a JestGreaterThan25Adapter when the Jest version is higher or equal to 25.0.0', () => {