From dbf961e74f1005c22f0c49560343528a1b3357a8 Mon Sep 17 00:00:00 2001 From: Peter Wessels Date: Tue, 21 Jan 2020 11:53:03 +0100 Subject: [PATCH] feat(cli): change react to create-react-app As reported (#1435) the react preset is only applicable to a react project set up by create-react-app. In addition, the error (unable to locate package react-scripts) isn't that useful either. Therefore, I propose we change the projectType to react-create-app as it resembles the applicability of this preset. --- .../src/initializer/presets/ReactPreset.ts | 4 +- .../test/unit/initializer/Presets.spec.ts | 4 +- packages/jest-runner/README.md | 4 +- packages/jest-runner/src/JestConfigEditor.ts | 16 ++++ .../ReactScriptsJestConfigLoader.ts | 2 +- .../ReactScriptsTSJestConfigLoader.ts | 2 +- .../integration/JestConfigEditor.it.spec.ts | 92 +++++++++++++++++++ .../ReactScriptsJestConfigLoader.spec.ts | 4 +- .../ReactScriptsTsJestConfigLoader.spec.ts | 2 +- 9 files changed, 120 insertions(+), 10 deletions(-) diff --git a/packages/core/src/initializer/presets/ReactPreset.ts b/packages/core/src/initializer/presets/ReactPreset.ts index 9dafd03ab9..eafe60ff45 100644 --- a/packages/core/src/initializer/presets/ReactPreset.ts +++ b/packages/core/src/initializer/presets/ReactPreset.ts @@ -9,14 +9,14 @@ const handbookUrl = 'https://github.com/stryker-mutator/stryker-handbook/blob/ma * https://github.com/stryker-mutator/stryker-handbook/blob/master/stryker/guides/react.md#react */ export class ReactPreset implements Preset { - public readonly name = 'react'; + public readonly name = 'create-react-app'; private readonly generalDependencies = ['@stryker-mutator/core', '@stryker-mutator/jest-runner', '@stryker-mutator/html-reporter']; private readonly sharedConfig = `testRunner: 'jest', reporters: ['progress', 'clear-text', 'html'], coverageAnalysis: 'off', jest: { - projectType: 'react' + projectType: 'create-react-app' } `; diff --git a/packages/core/test/unit/initializer/Presets.spec.ts b/packages/core/test/unit/initializer/Presets.spec.ts index 30a052dbfb..f1ccfbd64e 100644 --- a/packages/core/test/unit/initializer/Presets.spec.ts +++ b/packages/core/test/unit/initializer/Presets.spec.ts @@ -41,8 +41,8 @@ describe('Presets', () => { reactPreset = new ReactPreset(); }); - it('should have the name "react"', () => { - expect(reactPreset.name).to.eq('react'); + it('should have the name "create-react-app"', () => { + expect(reactPreset.name).to.eq('create-react-app'); }); it('should mutate typescript when TSX is chosen', async () => { diff --git a/packages/jest-runner/README.md b/packages/jest-runner/README.md index 75f6ba857e..ed32c6cc17 100644 --- a/packages/jest-runner/README.md +++ b/packages/jest-runner/README.md @@ -49,8 +49,8 @@ The @stryker-mutator/jest-runner also provides a couple of configurable options | option | description | default value | alternative values | |----|----|----|---| | projectType (optional) | The type of project you are working on. | `custom` | `custom` uses the `config` option (see below)| -| | | | `react` when you are using [create-react-app](https://github.com/facebook/create-react-app) | -| | | | `react-ts` when you are using [create-react-app-typescript](https://github.com/wmonk/create-react-app-typescript) | +| | | | `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 | | | 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 | diff --git a/packages/jest-runner/src/JestConfigEditor.ts b/packages/jest-runner/src/JestConfigEditor.ts index b683b6aa8d..a127dd1daa 100644 --- a/packages/jest-runner/src/JestConfigEditor.ts +++ b/packages/jest-runner/src/JestConfigEditor.ts @@ -1,5 +1,7 @@ import { Config, ConfigEditor } from '@stryker-mutator/api/config'; +import { Logger } from '@stryker-mutator/api/logging'; import jest from 'jest'; +import { commonTokens, tokens } from '@stryker-mutator/api/plugin'; import CustomJestConfigLoader from './configLoaders/CustomJestConfigLoader'; import JestConfigLoader from './configLoaders/JestConfigLoader'; @@ -10,6 +12,10 @@ import JEST_OVERRIDE_OPTIONS from './jestOverrideOptions'; const DEFAULT_PROJECT_NAME = 'custom'; export default class JestConfigEditor implements ConfigEditor { + public static inject = tokens(commonTokens.logger); + + constructor(private readonly log: Logger) {} + public edit(strykerConfig: Config): void { // If there is no Jest property on the Stryker config create it strykerConfig.jest = strykerConfig.jest || {}; @@ -28,9 +34,19 @@ export default class JestConfigEditor implements ConfigEditor { 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}`); diff --git a/packages/jest-runner/src/configLoaders/ReactScriptsJestConfigLoader.ts b/packages/jest-runner/src/configLoaders/ReactScriptsJestConfigLoader.ts index 79491836c8..3b6f8af13b 100644 --- a/packages/jest-runner/src/configLoaders/ReactScriptsJestConfigLoader.ts +++ b/packages/jest-runner/src/configLoaders/ReactScriptsJestConfigLoader.ts @@ -27,7 +27,7 @@ export default class ReactScriptsJestConfigLoader implements JestConfigLoader { return jestConfiguration; } catch (e) { if (this.isNodeErrnoException(e) && e.code === 'MODULE_NOT_FOUND') { - throw Error('Unable to locate package react-scripts. This package is required when projectType is set to "react".'); + throw Error('Unable to locate package react-scripts. This package is required when projectType is set to "create-react-app".'); } throw e; } diff --git a/packages/jest-runner/src/configLoaders/ReactScriptsTSJestConfigLoader.ts b/packages/jest-runner/src/configLoaders/ReactScriptsTSJestConfigLoader.ts index c6c341b96e..4a9e6936ae 100644 --- a/packages/jest-runner/src/configLoaders/ReactScriptsTSJestConfigLoader.ts +++ b/packages/jest-runner/src/configLoaders/ReactScriptsTSJestConfigLoader.ts @@ -27,7 +27,7 @@ export default class ReactScriptsTSJestConfigLoader implements JestConfigLoader return jestConfiguration; } catch (e) { if (this.isNodeErrnoException(e) && e.code === 'MODULE_NOT_FOUND') { - throw Error('Unable to locate package react-scripts-ts. ' + 'This package is required when projectType is set to "react-ts".'); + throw Error('Unable to locate package react-scripts-ts. ' + 'This package is required when projectType is set to "create-react-app-ts".'); } throw e; } diff --git a/packages/jest-runner/test/integration/JestConfigEditor.it.spec.ts b/packages/jest-runner/test/integration/JestConfigEditor.it.spec.ts index 32ae020e7d..4df19fd036 100644 --- a/packages/jest-runner/test/integration/JestConfigEditor.it.spec.ts +++ b/packages/jest-runner/test/integration/JestConfigEditor.it.spec.ts @@ -23,6 +23,39 @@ describe('Integration test for Jest ConfigEditor', () => { config = new Config(); }); + it('should create a Jest configuration for a create-react-app project', () => { + config.set({ jest: { projectType: 'create-react-app' } }); + + jestConfigEditor.edit(config); + + 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, config.jest.config); + }); + it('should create a Jest configuration for a React project', () => { config.set({ jest: { projectType: 'react' } }); @@ -56,6 +89,55 @@ describe('Integration test for Jest ConfigEditor', () => { assertJestConfig(expectedResult, config.jest.config); }); + it('should log a deprecation warning when projectType is "react"', () => { + config.set({ jest: { projectType: 'react' } }); + + jestConfigEditor.edit(config); + + 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', () => { + config.set({ jest: { projectType: 'create-react-app-ts' } }); + + jestConfigEditor.edit(config); + + 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, config.jest.config); + }); + it('should create a Jest configuration for a React + TypeScript project', () => { config.set({ jest: { projectType: 'react-ts' } }); @@ -95,6 +177,16 @@ describe('Integration test for Jest ConfigEditor', () => { assertJestConfig(expectedResult, config.jest.config); }); + it('should log a deprecation warning when projectType is "react-ts"', () => { + config.set({ jest: { projectType: 'react-ts' } }); + + jestConfigEditor.edit(config); + + 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')); diff --git a/packages/jest-runner/test/unit/configLoaders/ReactScriptsJestConfigLoader.spec.ts b/packages/jest-runner/test/unit/configLoaders/ReactScriptsJestConfigLoader.spec.ts index f3185d1476..a3aa4ae52a 100644 --- a/packages/jest-runner/test/unit/configLoaders/ReactScriptsJestConfigLoader.spec.ts +++ b/packages/jest-runner/test/unit/configLoaders/ReactScriptsJestConfigLoader.spec.ts @@ -52,6 +52,8 @@ describe(ReactScriptsJestConfigLoader.name, () => { requireResolveStub.throws(error); // Act & Assert - expect(() => sut.loadConfig()).throws('Unable to locate package react-scripts. This package is required when projectType is set to "react".'); + expect(() => sut.loadConfig()).throws( + 'Unable to locate package react-scripts. This package is required when projectType is set to "create-react-app".' + ); }); }); diff --git a/packages/jest-runner/test/unit/configLoaders/ReactScriptsTsJestConfigLoader.spec.ts b/packages/jest-runner/test/unit/configLoaders/ReactScriptsTsJestConfigLoader.spec.ts index aaa21e4444..9365e91096 100644 --- a/packages/jest-runner/test/unit/configLoaders/ReactScriptsTsJestConfigLoader.spec.ts +++ b/packages/jest-runner/test/unit/configLoaders/ReactScriptsTsJestConfigLoader.spec.ts @@ -53,7 +53,7 @@ describe(ReactScriptsTSJestConfigLoader.name, () => { // Act & Assert expect(() => sut.loadConfig()).throws( - 'Unable to locate package react-scripts-ts. This package is required when projectType is set to "react-ts".' + 'Unable to locate package react-scripts-ts. This package is required when projectType is set to "create-react-app-ts".' ); }); });