diff --git a/packages/core/src/initializer/StrykerConfigWriter.ts b/packages/core/src/initializer/StrykerConfigWriter.ts index fd11cdd969..24982564a3 100644 --- a/packages/core/src/initializer/StrykerConfigWriter.ts +++ b/packages/core/src/initializer/StrykerConfigWriter.ts @@ -1,6 +1,6 @@ import { existsSync, promises as fs } from 'fs'; -import { StrykerOptions } from '@stryker-mutator/api/core'; +import { PartialStrykerOptions, StrykerOptions } from '@stryker-mutator/api/core'; import { Logger } from '@stryker-mutator/api/logging'; import { commonTokens, tokens } from '@stryker-mutator/api/plugin'; import { childProcessAsPromised } from '@stryker-mutator/util'; @@ -60,14 +60,14 @@ export default class StrykerConfigWriter { */ public async writePreset(presetConfig: PresetConfiguration, exportAsJson: boolean) { const config = { - comment: `This config was generated using a preset. Please see the handbook for more information: ${presetConfig.handbookUrl}`, + _comment: `This config was generated using a preset. Please see the handbook for more information: ${presetConfig.handbookUrl}`, ...presetConfig.config, }; return this.writeStrykerConfig(config, exportAsJson); } - private writeStrykerConfig(config: Partial, exportAsJson: boolean) { + private writeStrykerConfig(config: PartialStrykerOptions, exportAsJson: boolean) { if (exportAsJson) { return this.writeJsonConfig(config); } else { @@ -75,7 +75,7 @@ export default class StrykerConfigWriter { } } - private async writeJsConfig(commentedConfig: Partial) { + private async writeJsConfig(commentedConfig: PartialStrykerOptions) { this.out(`Writing & formatting ${STRYKER_JS_CONFIG_FILE}...`); const rawConfig = this.stringify(commentedConfig); const formattedConfig = `/** @@ -93,7 +93,7 @@ export default class StrykerConfigWriter { return STRYKER_JS_CONFIG_FILE; } - private async writeJsonConfig(commentedConfig: Partial) { + private async writeJsonConfig(commentedConfig: PartialStrykerOptions) { this.out(`Writing & formatting ${STRYKER_JSON_CONFIG_FILE}...`); const typedConfig = { $schema: './node_modules/@stryker-mutator/core/schema/stryker-schema.json', diff --git a/packages/core/src/initializer/StrykerInitializer.ts b/packages/core/src/initializer/StrykerInitializer.ts index 16246df8b0..c71db612fb 100644 --- a/packages/core/src/initializer/StrykerInitializer.ts +++ b/packages/core/src/initializer/StrykerInitializer.ts @@ -1,4 +1,5 @@ import * as child from 'child_process'; +import { promises as fs } from 'fs'; import { commonTokens, tokens } from '@stryker-mutator/api/plugin'; import { Logger } from '@stryker-mutator/api/logging'; @@ -87,6 +88,9 @@ export default class StrykerInitializer { const presetConfig = await selectedPreset.createConfig(); const isJsonSelected = await this.selectJsonConfigType(); const configFileName = await configWriter.writePreset(presetConfig, isJsonSelected); + if (presetConfig.additionalConfigFiles) { + await Promise.all(presetConfig.additionalConfigFiles.map(({ name, content }) => fs.writeFile(name, content))); + } const selectedPackageManager = await this.selectPackageManager(); this.installNpmDependencies(presetConfig.dependencies, selectedPackageManager); return configFileName; diff --git a/packages/core/src/initializer/presets/AngularPreset.ts b/packages/core/src/initializer/presets/AngularPreset.ts index 86dcc2600b..9a7c89328a 100644 --- a/packages/core/src/initializer/presets/AngularPreset.ts +++ b/packages/core/src/initializer/presets/AngularPreset.ts @@ -10,7 +10,7 @@ const handbookUrl = 'https://github.com/stryker-mutator/stryker-handbook/blob/ma export class AngularPreset implements Preset { public readonly name = 'angular-cli'; // Please keep config in sync with handbook - private readonly dependencies = ['@stryker-mutator/core', '@stryker-mutator/karma-runner', '@stryker-mutator/typescript']; + private readonly dependencies = ['@stryker-mutator/karma-runner']; private readonly config: Partial = { mutate: ['src/**/*.ts', '!src/**/*.spec.ts', '!src/test.ts', '!src/environments/*.ts'], testRunner: 'karma', @@ -24,8 +24,8 @@ export class AngularPreset implements Preset { reporters: ['progress', 'clear-text', 'html'], concurrency: Math.floor(os.cpus().length / 2), // eslint-disable-next-line camelcase - maxConcurrentTestRunners_comment: 'Recommended to use about half of your available cores when running stryker with angular', - coverageAnalysis: 'off', + concurrency_comment: 'Recommended to use about half of your available cores when running stryker with angular', + coverageAnalysis: 'perTest', }; public async createConfig(): Promise { diff --git a/packages/core/src/initializer/presets/PresetConfiguration.ts b/packages/core/src/initializer/presets/PresetConfiguration.ts index ac5d0d276b..55c74c67e8 100644 --- a/packages/core/src/initializer/presets/PresetConfiguration.ts +++ b/packages/core/src/initializer/presets/PresetConfiguration.ts @@ -1,7 +1,8 @@ -import { StrykerOptions } from '@stryker-mutator/api/core'; +import { File, PartialStrykerOptions } from '@stryker-mutator/api/core'; export default interface PresetConfiguration { - config: Partial; + config: PartialStrykerOptions; handbookUrl: string; dependencies: string[]; + additionalConfigFiles?: File[]; } diff --git a/packages/core/src/initializer/presets/ReactPreset.ts b/packages/core/src/initializer/presets/ReactPreset.ts index 7a09578245..3659973091 100644 --- a/packages/core/src/initializer/presets/ReactPreset.ts +++ b/packages/core/src/initializer/presets/ReactPreset.ts @@ -1,4 +1,3 @@ -import inquirer = require('inquirer'); import { StrykerOptions } from '@stryker-mutator/api/core'; import Preset from './Preset'; @@ -12,9 +11,9 @@ const handbookUrl = 'https://github.com/stryker-mutator/stryker-handbook/blob/ma */ export class ReactPreset implements Preset { public readonly name = 'create-react-app'; - private readonly generalDependencies = ['@stryker-mutator/core', '@stryker-mutator/jest-runner']; + private readonly dependencies = ['@stryker-mutator/jest-runner']; - private readonly sharedConfig: Partial = { + private readonly config: Partial = { testRunner: 'jest', reporters: ['progress', 'clear-text', 'html'], coverageAnalysis: 'off', @@ -23,35 +22,7 @@ export class ReactPreset implements Preset { }, }; - private readonly tsxDependencies = ['@stryker-mutator/typescript', ...this.generalDependencies]; - private readonly tsxConf: Partial = { - mutate: ['src/**/*.ts?(x)', '!src/**/*@(.test|.spec|Spec).ts?(x)'], - ...this.sharedConfig, - }; - - private readonly jsxDependencies = ['@stryker-mutator/javascript-mutator', ...this.generalDependencies]; - private readonly jsxConf: Partial = { - mutate: ['src/**/*.js?(x)', '!src/**/*@(.test|.spec|Spec).js?(x)'], - ...this.sharedConfig, - }; - public async createConfig(): Promise { - const choices = ['JSX', 'TSX']; - const answers = await inquirer.prompt<{ choice: string }>({ - choices, - message: 'Is your project a JSX project or a TSX project?', - name: 'choice', - type: 'list', - }); - return this.load(answers.choice); - } - private load(choice: string): PresetConfiguration { - if (choice === 'JSX') { - return { config: this.jsxConf, handbookUrl, dependencies: this.jsxDependencies }; - } else if (choice === 'TSX') { - return { config: this.tsxConf, handbookUrl, dependencies: this.tsxDependencies }; - } else { - throw new Error(`Invalid project type ${choice}`); - } + return { config: this.config, handbookUrl, dependencies: this.dependencies }; } } diff --git a/packages/core/src/initializer/presets/VueJsPreset.ts b/packages/core/src/initializer/presets/VueJsPreset.ts index a930841c7c..6bd4d49b54 100644 --- a/packages/core/src/initializer/presets/VueJsPreset.ts +++ b/packages/core/src/initializer/presets/VueJsPreset.ts @@ -1,5 +1,5 @@ import inquirer = require('inquirer'); -import { StrykerOptions } from '@stryker-mutator/api/core'; +import { File, PartialStrykerOptions } from '@stryker-mutator/api/core'; import Preset from './Preset'; import PresetConfiguration from './PresetConfiguration'; @@ -11,61 +11,53 @@ const handbookUrl = 'https://github.com/stryker-mutator/stryker-handbook/blob/ma * https://github.com/stryker-mutator/stryker-handbook/blob/master/stryker/guides/vuejs.md#vuejs */ export class VueJsPreset implements Preset { - public readonly name = 'vueJs'; - private readonly generalDependencies = ['@stryker-mutator/core', '@stryker-mutator/vue-mutator']; + public readonly name = 'vue-cli'; - private readonly jestDependency = '@stryker-mutator/jest-runner'; - private readonly jestConf: Partial = { - mutate: ['src/**/*.js', 'src/**/*.ts', 'src/**/*.vue'], + private readonly jestConf: PartialStrykerOptions = { testRunner: 'jest', + mutator: { + plugins: [], + }, jest: { // config: require('path/to/your/custom/jestConfig.js') }, reporters: ['progress', 'clear-text', 'html'], coverageAnalysis: 'off', }; - - private readonly karmaDependency = '@stryker-mutator/karma-runner'; - private readonly karmaConf: Partial = { - mutate: ['src/**/*.js', 'src/**/*.ts', 'src/**/*.vue'], - testRunner: 'karma', - karma: { - configFile: 'test/unit/karma.conf.js', - config: { - browsers: ['ChromeHeadless'], - }, + private readonly mochaConf: PartialStrykerOptions = { + testRunner: 'mocha', + mutator: { + plugins: [], }, + mochaOptions: { + require: ['@vue/cli-plugin-unit-mocha/setup.js'], + spec: ['dist/js/chunk-vendors.js', 'dist/js/tests.js'], + }, + buildCommand: 'webpack --config webpack.config.stryker.js', reporters: ['progress', 'clear-text', 'html'], - coverageAnalysis: 'off', + coverageAnalysis: 'perTest', }; public async createConfig(): Promise { - const testRunnerChoices = ['karma', 'jest']; + const testRunnerChoices = ['mocha', 'jest']; const testRunnerAnswers = await inquirer.prompt<{ testRunner: string }>({ choices: testRunnerChoices, message: 'Which test runner do you want to use?', name: 'testRunner', type: 'list', }); - const scriptChoices = ['typescript', 'javascript']; - const scriptAnswers = await inquirer.prompt<{ script: string }>({ - choices: scriptChoices, - message: 'Which language does your project use?', - name: 'script', - type: 'list', - }); const chosenTestRunner = testRunnerAnswers.testRunner; - const chosenScript = scriptAnswers.script; return { config: this.getConfig(chosenTestRunner), - dependencies: this.createDependencies(chosenTestRunner, chosenScript), + dependencies: this.createDependencies(chosenTestRunner), handbookUrl, + additionalConfigFiles: this.getAdditionalConfigFiles(chosenTestRunner), }; } private getConfig(testRunner: string) { - if (testRunner === 'karma') { - return this.karmaConf; + if (testRunner === 'mocha') { + return this.mochaConf; } else if (testRunner === 'jest') { return this.jestConf; } else { @@ -73,28 +65,49 @@ export class VueJsPreset implements Preset { } } - private createDependencies(testRunner: string, script: string): string[] { - const dependencies = this.generalDependencies; - dependencies.push(this.getTestRunnerDependency(testRunner)); - dependencies.push(this.getScriptDependency(script)); - return dependencies; - } + private getAdditionalConfigFiles(testRunner: string): File[] | undefined { + if (testRunner === 'mocha') { + return [ + new File( + 'webpack.config.stryker.js', + ` +const glob = require('glob'); + +// Set env +process.env.BABEL_ENV = 'test'; +process.env.NODE_ENV = 'test'; +process.env.VUE_CLI_BABEL_TARGET_NODE = 'true'; +process.env.VUE_CLI_TRANSPILE_BABEL_RUNTIME = 'true'; + +// Load webpack config +const conf = require('@vue/cli-service/webpack.config.js'); + +// Override the entry files +conf.entry = { + // Choose your test files here: + tests: glob.sync('{test,tests}/**/*+(spec).js').map((fileName) => \`./\${fileName}\`), +}; - private getScriptDependency(script: string): string { - if (script === 'typescript') { - return '@stryker-mutator/typescript'; - } else if (script === 'javascript') { - return '@stryker-mutator/javascript-mutator'; +module.exports = conf; +` + ), + ]; } else { - throw new Error(`Invalid script chosen: ${script}`); + return; } } - private getTestRunnerDependency(testRunner: string): string { - if (testRunner === 'karma') { - return this.karmaDependency; + private createDependencies(testRunner: string): string[] { + const dependencies = []; + dependencies.push(...this.getTestRunnerDependency(testRunner)); + return dependencies; + } + + private getTestRunnerDependency(testRunner: string): string[] { + if (testRunner === 'mocha') { + return ['@stryker-mutator/mocha-runner', 'glob', 'webpack-cli']; } else if (testRunner === 'jest') { - return this.jestDependency; + return ['@stryker-mutator/jest-runner']; } else { throw new Error(`Invalid test runner chosen: ${testRunner}`); } diff --git a/packages/core/test/unit/initializer/Presets.spec.ts b/packages/core/test/unit/initializer/Presets.spec.ts index 7e4a2cb3ff..2a92425e8e 100644 --- a/packages/core/test/unit/initializer/Presets.spec.ts +++ b/packages/core/test/unit/initializer/Presets.spec.ts @@ -39,22 +39,6 @@ describe('Presets', () => { it('should have the name "create-react-app"', () => { expect(reactPreset.name).to.eq('create-react-app'); }); - - it('should install @stryker-mutator/typescript when TSX is chosen', async () => { - inquirerPrompt.resolves({ - choice: 'TSX', - }); - const config = await reactPreset.createConfig(); - expect(config.dependencies).to.include('@stryker-mutator/typescript'); - }); - - it('should install @stryker-mutator/javascript-mutator when JSX is chosen', async () => { - inquirerPrompt.resolves({ - choice: 'JSX', - }); - const config = await reactPreset.createConfig(); - expect(config.dependencies).to.include('@stryker-mutator/javascript-mutator'); - }); }); describe('VueJsPreset', () => { @@ -64,21 +48,21 @@ describe('Presets', () => { vueJsPreset = new VueJsPreset(); inquirerPrompt.resolves({ script: 'typescript', - testRunner: 'karma', + testRunner: 'jest', }); }); - it('should have the name "vueJs"', () => { - expect(vueJsPreset.name).to.eq('vueJs'); + it('should have the name "vue-cli"', () => { + expect(vueJsPreset.name).to.eq('vue-cli'); }); - it('should install @stryker-mutator/karma-runner when karma is chosen', async () => { + it('should install @stryker-mutator/mocha-runner when mocha is chosen', async () => { inquirerPrompt.resolves({ script: 'typescript', - testRunner: 'karma', + testRunner: 'mocha', }); const config = await vueJsPreset.createConfig(); - expect(config.dependencies).to.include('@stryker-mutator/karma-runner'); + expect(config.dependencies).to.include('@stryker-mutator/mocha-runner'); }); it('should install @stryker-mutator/jest-runner when jest is chosen', async () => { @@ -89,23 +73,5 @@ describe('Presets', () => { const config = await vueJsPreset.createConfig(); expect(config.dependencies).to.include('@stryker-mutator/jest-runner'); }); - - it('should install @stryker-mutator/typescript when typescript is chosen', async () => { - inquirerPrompt.resolves({ - script: 'typescript', - testRunner: 'karma', - }); - const config = await vueJsPreset.createConfig(); - expect(config.dependencies).to.include('@stryker-mutator/typescript'); - }); - - it('should install @stryker-mutator/javascript-mutator when javascript is chosen', async () => { - inquirerPrompt.resolves({ - script: 'javascript', - testRunner: 'karma', - }); - const config = await vueJsPreset.createConfig(); - expect(config.dependencies).to.include('@stryker-mutator/javascript-mutator'); - }); }); }); diff --git a/packages/core/test/unit/initializer/StrykerInitializer.spec.ts b/packages/core/test/unit/initializer/StrykerInitializer.spec.ts index ad33915fd5..06fe976512 100644 --- a/packages/core/test/unit/initializer/StrykerInitializer.spec.ts +++ b/packages/core/test/unit/initializer/StrykerInitializer.spec.ts @@ -128,10 +128,7 @@ describe(StrykerInitializer.name, () => { it('should correctly write and format the stryker js configuration file', async () => { const handbookUrl = 'https://awesome-preset.org'; - const config = { - comment: `This config was generated using a preset. Please see the handbook for more information: ${handbookUrl}`, - awesomeConf: 'awesome', - }; + const config = { awesomeConf: 'awesome' }; childExec.resolves(); resolvePresetConfig({ config, @@ -141,7 +138,7 @@ describe(StrykerInitializer.name, () => { * @type {import('@stryker-mutator/api/core').StrykerOptions} */ module.exports = { - "comment": "${config.comment}", + "_comment": "This config was generated using a preset. Please see the handbook for more information: https://awesome-preset.org", "awesomeConf": "${config.awesomeConf}" };`; inquirerPrompt.resolves({ @@ -436,10 +433,8 @@ describe(StrykerInitializer.name, () => { } function expectStrykerConfWritten(expectedRawConfig: string) { - expect(fsWriteFile).calledWithMatch( - sinon.match('stryker.conf.js'), - sinon.match((actualConf: string) => normalizeWhitespaces(expectedRawConfig) === normalizeWhitespaces(actualConf)) - ); - fsWriteFile.getCall(0); + const [fileName, actualConfig] = fsWriteFile.getCall(0).args; + expect(fileName).eq('stryker.conf.js'); + expect(normalizeWhitespaces(actualConfig)).deep.eq(normalizeWhitespaces(expectedRawConfig)); } });