diff --git a/packages/stryker-api/plugin.ts b/packages/stryker-api/plugin.ts index 7e10edc4b0..6ff10b0f80 100644 --- a/packages/stryker-api/plugin.ts +++ b/packages/stryker-api/plugin.ts @@ -2,3 +2,8 @@ export * from './src/plugin/Contexts'; export * from './src/plugin/Plugins'; export * from './src/plugin/PluginKind'; export * from './src/plugin/tokens'; +export * from 'typed-inject/src/api/Injectable'; +export * from 'typed-inject/src/api/Injector'; +export * from 'typed-inject/src/api/InjectionToken'; +export * from 'typed-inject/src/api/CorrespondingType'; +export * from 'typed-inject/src/api/Scope'; diff --git a/packages/stryker-api/src/plugin/Plugins.ts b/packages/stryker-api/src/plugin/Plugins.ts index 10adc4aea6..16bcf641ea 100644 --- a/packages/stryker-api/src/plugin/Plugins.ts +++ b/packages/stryker-api/src/plugin/Plugins.ts @@ -3,8 +3,8 @@ import { TestRunner } from '../../test_runner'; import { Reporter } from '../../report'; import { Mutator } from '../../mutant'; import { Transpiler } from '../../transpile'; -import { PluginContexts } from './Contexts'; import { ConfigEditor } from '../../config'; +import { PluginContexts } from './Contexts'; import { InjectionToken, InjectableClass, InjectableFunction } from 'typed-inject'; import { PluginKind } from './PluginKind'; @@ -89,4 +89,5 @@ export type Plugins = { */ export interface PluginResolver { resolve(kind: T, name: string): Plugins[T]; + resolveAll(kind: T): Plugins[T][]; } diff --git a/packages/stryker-jest-runner/package.json b/packages/stryker-jest-runner/package.json index 8ebcf1a80b..2a9ff149d1 100644 --- a/packages/stryker-jest-runner/package.json +++ b/packages/stryker-jest-runner/package.json @@ -37,6 +37,7 @@ }, "homepage": "https://github.com/stryker-mutator/stryker/tree/master/packages/stryker-jest-runner#readme", "devDependencies": { + "@stryker-mutator/test-helpers": "0.0.0", "@types/semver": "~5.5.0", "jest": "~23.6.0", "react": "~16.7.0", diff --git a/packages/stryker-jest-runner/src/JestConfigEditor.ts b/packages/stryker-jest-runner/src/JestConfigEditor.ts index 2a98802e3a..a40b7c7c54 100644 --- a/packages/stryker-jest-runner/src/JestConfigEditor.ts +++ b/packages/stryker-jest-runner/src/JestConfigEditor.ts @@ -1,4 +1,3 @@ -import * as fs from 'fs'; import { Config, ConfigEditor } from 'stryker-api/config'; import JestConfigLoader from './configLoaders/JestConfigLoader'; import CustomJestConfigLoader from './configLoaders/CustomJestConfigLoader'; @@ -6,14 +5,16 @@ import ReactScriptsJestConfigLoader from './configLoaders/ReactScriptsJestConfig import ReactScriptsTSJestConfigLoader from './configLoaders/ReactScriptsTSJestConfigLoader'; import JEST_OVERRIDE_OPTIONS from './jestOverrideOptions'; import { Configuration } from 'jest'; -import { getLogger } from 'stryker-api/logging'; +import { Logger } from 'stryker-api/logging'; +import { tokens, commonTokens } from 'stryker-api/plugin'; const DEFAULT_PROJECT_NAME = 'custom'; const DEFAULT_PROJECT_NAME_DEPRECATED = 'default'; export default class JestConfigEditor implements ConfigEditor { - public log = getLogger(JestConfigEditor.name); + 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 @@ -41,7 +42,7 @@ export default class JestConfigEditor implements ConfigEditor { private getConfigLoader(projectType: string): JestConfigLoader { switch (projectType.toLowerCase()) { case DEFAULT_PROJECT_NAME: - return new CustomJestConfigLoader(process.cwd(), fs); + return new CustomJestConfigLoader(process.cwd()); case 'react': return new ReactScriptsJestConfigLoader(process.cwd()); case 'react-ts': diff --git a/packages/stryker-jest-runner/src/configLoaders/CustomJestConfigLoader.ts b/packages/stryker-jest-runner/src/configLoaders/CustomJestConfigLoader.ts index adc9049644..dd45f80452 100644 --- a/packages/stryker-jest-runner/src/configLoaders/CustomJestConfigLoader.ts +++ b/packages/stryker-jest-runner/src/configLoaders/CustomJestConfigLoader.ts @@ -1,24 +1,20 @@ import JestConfigLoader from './JestConfigLoader'; import * as path from 'path'; +import fs = require('fs'); import { Configuration } from 'jest'; /** * 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 _fs: any; private readonly _projectRoot: string; - private readonly _loader: NodeRequire; - constructor(projectRoot: string, fs: any, loader?: NodeRequire) { + constructor(projectRoot: string, private readonly _loader: NodeRequireFunction = require) { this._projectRoot = projectRoot; - this._fs = fs; - this._loader = loader || /* istanbul ignore next */ require; } public loadConfig(): Configuration { const jestConfig = this.readConfigFromJestConfigFile() || this.readConfigFromPackageJson() || {}; - return jestConfig; } @@ -30,7 +26,7 @@ export default class CustomJestConfigLoader implements JestConfigLoader { private readConfigFromPackageJson() { try { - return JSON.parse(this._fs.readFileSync(path.join(this._projectRoot, 'package.json'), 'utf8')).jest; + return JSON.parse(fs.readFileSync(path.join(this._projectRoot, 'package.json'), 'utf8')).jest; } catch { /* Don't return anything (implicitly return undefined) */ } } } diff --git a/packages/stryker-jest-runner/src/index.ts b/packages/stryker-jest-runner/src/index.ts index bd9bcfa5e5..1375036e10 100644 --- a/packages/stryker-jest-runner/src/index.ts +++ b/packages/stryker-jest-runner/src/index.ts @@ -1,9 +1,11 @@ -import { ConfigEditorFactory } from 'stryker-api/config'; +import { PluginKind, declareClassPlugin } from 'stryker-api/plugin'; import { TestRunnerFactory } from 'stryker-api/test_runner'; import JestConfigEditor from './JestConfigEditor'; import JestTestRunner from './JestTestRunner'; process.env.BABEL_ENV = 'test'; -ConfigEditorFactory.instance().register('jest', JestConfigEditor); +export const strykerPlugins = [ + declareClassPlugin(PluginKind.ConfigEditor, 'jest', JestConfigEditor) +]; TestRunnerFactory.instance().register('jest', JestTestRunner); diff --git a/packages/stryker-jest-runner/test/helpers/initSinon.ts b/packages/stryker-jest-runner/test/helpers/initSinon.ts index b168594f22..00ee898357 100644 --- a/packages/stryker-jest-runner/test/helpers/initSinon.ts +++ b/packages/stryker-jest-runner/test/helpers/initSinon.ts @@ -1,5 +1,7 @@ import * as sinon from 'sinon'; +import { testInjector } from '@stryker-mutator/test-helpers'; afterEach(() => { sinon.restore(); + testInjector.reset(); }); diff --git a/packages/stryker-jest-runner/test/integration/JestConfigEditorSpec.ts b/packages/stryker-jest-runner/test/integration/JestConfigEditor.it.spec.ts similarity index 97% rename from packages/stryker-jest-runner/test/integration/JestConfigEditorSpec.ts rename to packages/stryker-jest-runner/test/integration/JestConfigEditor.it.spec.ts index 7fc96d0675..db23ba57a9 100644 --- a/packages/stryker-jest-runner/test/integration/JestConfigEditorSpec.ts +++ b/packages/stryker-jest-runner/test/integration/JestConfigEditor.it.spec.ts @@ -3,6 +3,7 @@ import { Config } from 'stryker-api/config'; import { expect } from 'chai'; import * as sinon from 'sinon'; import * as path from 'path'; +import { testInjector } from '@stryker-mutator/test-helpers'; describe('Integration test for Jest ConfigEditor', () => { let jestConfigEditor: JestConfigEditor; @@ -15,7 +16,7 @@ describe('Integration test for Jest ConfigEditor', () => { getProjectRootStub = sinon.stub(process, 'cwd'); getProjectRootStub.returns(projectRoot); - jestConfigEditor = new JestConfigEditor(); + jestConfigEditor = testInjector.injector.injectClass(JestConfigEditor); config = new Config(); }); diff --git a/packages/stryker-jest-runner/test/integration/StrykerJestRunnerSpec.ts b/packages/stryker-jest-runner/test/integration/StrykerJestRunner.it.spec.ts similarity index 96% rename from packages/stryker-jest-runner/test/integration/StrykerJestRunnerSpec.ts rename to packages/stryker-jest-runner/test/integration/StrykerJestRunner.it.spec.ts index 4c2664c221..6b3f45de8d 100644 --- a/packages/stryker-jest-runner/test/integration/StrykerJestRunnerSpec.ts +++ b/packages/stryker-jest-runner/test/integration/StrykerJestRunner.it.spec.ts @@ -10,6 +10,7 @@ paths.appTsTestConfig = require.resolve('../../testResources/reactTsProject/tsco import JestConfigEditor from '../../src/JestConfigEditor'; import JestTestRunner from '../../src/JestTestRunner'; +import { testInjector } from '@stryker-mutator/test-helpers'; // Get the actual project root, since we will stub process.cwd later on const jestProjectRoot = process.cwd(); @@ -39,7 +40,7 @@ describe('Integration test for Strykers Jest runner', () => { beforeEach(() => { processCwdStub = sinon.stub(process, 'cwd'); - jestConfigEditor = new JestConfigEditor(); + jestConfigEditor = testInjector.injector.injectClass(JestConfigEditor); runnerOptions = { fileNames: [], diff --git a/packages/stryker-jest-runner/test/unit/JestConfigEditorSpec.ts b/packages/stryker-jest-runner/test/unit/JestConfigEditor.spec.ts similarity index 88% rename from packages/stryker-jest-runner/test/unit/JestConfigEditorSpec.ts rename to packages/stryker-jest-runner/test/unit/JestConfigEditor.spec.ts index f8a87c7b9e..e07f2fc7a5 100644 --- a/packages/stryker-jest-runner/test/unit/JestConfigEditorSpec.ts +++ b/packages/stryker-jest-runner/test/unit/JestConfigEditor.spec.ts @@ -6,9 +6,9 @@ import CustomJestConfigLoader, * as defaultJestConfigLoader from '../../src/conf import ReactScriptsJestConfigLoader, * as reactScriptsJestConfigLoader from '../../src/configLoaders/ReactScriptsJestConfigLoader'; import ReactScriptsTSJestConfigLoader, * as reactScriptsTSJestConfigLoader from '../../src/configLoaders/ReactScriptsTSJestConfigLoader'; import { Configuration } from 'jest'; -import currentLogMock from '../helpers/logMock'; +import { testInjector } from '@stryker-mutator/test-helpers'; -describe('JestConfigEditor', () => { +describe.only('JestConfigEditor', () => { let sut: JestConfigEditor; let customConfigLoaderStub: ConfigLoaderStub; let reactScriptsJestConfigLoaderStub: ConfigLoaderStub; @@ -29,7 +29,7 @@ describe('JestConfigEditor', () => { reactScriptsJestConfigLoaderStub.loadConfig.returns(defaultOptions); reactScriptsTSJestConfigLoaderStub.loadConfig.returns(defaultOptions); - sut = new JestConfigEditor(); + sut = testInjector.injector.injectClass(JestConfigEditor); config = new Config(); }); @@ -78,14 +78,14 @@ describe('JestConfigEditor', () => { const projectType = 'custom'; config.jest = { project: projectType }; sut.edit(config); - expect(currentLogMock().warn).calledWith('DEPRECATED: `jest.project` is renamed to `jest.projectType`. Please change it in your stryker configuration.'); + expect(testInjector.logger.warn).calledWith('DEPRECATED: `jest.project` is renamed to `jest.projectType`. Please change it in your stryker configuration.'); expect(config.jest.projectType).eq(projectType); }); it('should warn when using deprecated "default" project type', () => { config.jest = { projectType: 'default' }; sut.edit(config); - expect(currentLogMock().warn).calledWith('DEPRECATED: The \'default\' `jest.projectType` is renamed to \'custom\'. Please rename it in your stryker configuration.'); + expect(testInjector.logger.warn).calledWith('DEPRECATED: The \'default\' `jest.projectType` is renamed to \'custom\'. Please rename it in your stryker configuration.'); expect(config.jest.projectType).eq('custom'); }); }); diff --git a/packages/stryker-jest-runner/test/unit/configLoaders/CustomConfigLoaderSpec.ts b/packages/stryker-jest-runner/test/unit/configLoaders/CustomConfigLoaderSpec.ts index a57ca7216d..a7bb5f12ae 100644 --- a/packages/stryker-jest-runner/test/unit/configLoaders/CustomConfigLoaderSpec.ts +++ b/packages/stryker-jest-runner/test/unit/configLoaders/CustomConfigLoaderSpec.ts @@ -4,30 +4,26 @@ import { expect, assert } from 'chai'; import * as path from 'path'; import * as fs from 'fs'; -const fakeRequire: any = { - require: () => { } -}; - describe(CustomJestConfigLoader.name, () => { let defaultConfigLoader: CustomJestConfigLoader; const projectRoot: string = '/path/to/project/root'; - const fsStub: FsStub = {}; + let readFileSyncStub: sinon.SinonStub; let requireStub: sinon.SinonStub; beforeEach(() => { - fsStub.readFileSync = sinon.stub(fs, 'readFileSync'); - requireStub = sinon.stub(fakeRequire, 'require'); + readFileSyncStub = sinon.stub(fs, 'readFileSync'); + requireStub = sinon.stub(); - fsStub.readFileSync.returns('{ "jest": { "exampleProperty": "examplePackageJsonValue" }}'); + readFileSyncStub.returns('{ "jest": { "exampleProperty": "examplePackageJsonValue" }}'); requireStub.returns({ exampleProperty: 'exampleJestConfigValue' }); - defaultConfigLoader = new CustomJestConfigLoader(projectRoot, fs, fakeRequire.require); + defaultConfigLoader = new CustomJestConfigLoader(projectRoot, requireStub); }); it('should load the Jest configuration from the jest.config.js in the project root', () => { const config = defaultConfigLoader.loadConfig(); - assert(requireStub.calledWith(path.join(projectRoot, 'jest.config.js')), `loader not called with ${projectRoot}/jest.config.js`); + expect(requireStub).calledWith(path.join(projectRoot, 'jest.config.js')); expect(config).to.deep.equal({ exampleProperty: 'exampleJestConfigValue' }); @@ -37,7 +33,7 @@ describe(CustomJestConfigLoader.name, () => { requireStub.throws(Error('ENOENT: no such file or directory, open package.json')); const config = defaultConfigLoader.loadConfig(); - assert(fsStub.readFileSync.calledWith(path.join(projectRoot, 'package.json'), 'utf8'), `readFileSync not called with ${projectRoot}/package.json`); + assert(readFileSyncStub.calledWith(path.join(projectRoot, 'package.json'), 'utf8'), `readFileSync not called with ${projectRoot}/package.json`); expect(config).to.deep.equal({ exampleProperty: 'examplePackageJsonValue' }); @@ -45,12 +41,8 @@ describe(CustomJestConfigLoader.name, () => { it('should load the default Jest configuration if there is no package.json config or jest.config.js', () => { requireStub.throws(Error('ENOENT: no such file or directory, open package.json')); - fsStub.readFileSync.returns('{ }'); // note, no `jest` key here! + readFileSyncStub.returns('{ }'); // note, no `jest` key here! const config = defaultConfigLoader.loadConfig(); expect(config).to.deep.equal({}); }); }); - -interface FsStub { - [key: string]: sinon.SinonStub; -} diff --git a/packages/stryker-jest-runner/test/unit/configLoaders/DefaultConfigLoaderSpec.ts b/packages/stryker-jest-runner/test/unit/configLoaders/DefaultConfigLoaderSpec.ts index c7ffa96cec..e674daf68f 100644 --- a/packages/stryker-jest-runner/test/unit/configLoaders/DefaultConfigLoaderSpec.ts +++ b/packages/stryker-jest-runner/test/unit/configLoaders/DefaultConfigLoaderSpec.ts @@ -4,10 +4,6 @@ import { expect, assert } from 'chai'; import * as path from 'path'; import * as fs from 'fs'; -const fakeRequire: any = { - require: () => { } -}; - describe(`${CustomJestConfigLoader.name} integration`, () => { let sut: CustomJestConfigLoader; const projectRoot: string = '/path/to/project/root'; @@ -16,12 +12,12 @@ describe(`${CustomJestConfigLoader.name} integration`, () => { beforeEach(() => { fsStub.readFileSync = sinon.stub(fs, 'readFileSync'); - requireStub = sinon.stub(fakeRequire, 'require'); + requireStub = sinon.stub(); fsStub.readFileSync.returns('{ "jest": { "exampleProperty": "examplePackageJsonValue" }}'); requireStub.returns({ exampleProperty: 'exampleJestConfigValue' }); - sut = new CustomJestConfigLoader(projectRoot, fs, fakeRequire.require); + sut = new CustomJestConfigLoader(projectRoot, requireStub); }); it('should load the Jest configuration from the jest.config.js in the projectroot', () => { diff --git a/packages/stryker-jest-runner/tsconfig.test.json b/packages/stryker-jest-runner/tsconfig.test.json index d0690d880b..cda09e0ef3 100644 --- a/packages/stryker-jest-runner/tsconfig.test.json +++ b/packages/stryker-jest-runner/tsconfig.test.json @@ -14,6 +14,9 @@ "references": [ { "path": "./tsconfig.src.json" + }, + { + "path": "../stryker-test-helpers/tsconfig.src.json" } ] } \ No newline at end of file diff --git a/packages/stryker-mocha-runner/package.json b/packages/stryker-mocha-runner/package.json index d3da6ca4b7..eac20649fc 100644 --- a/packages/stryker-mocha-runner/package.json +++ b/packages/stryker-mocha-runner/package.json @@ -37,10 +37,10 @@ "tslib": "~1.9.3" }, "devDependencies": { + "@stryker-mutator/test-helpers": "0.0.0", "@types/multimatch": "~2.1.2", "stryker-api": "^0.23.0", - "stryker-mocha-framework": "^0.14.0", - "@stryker-mutator/test-helpers": "0.0.0" + "stryker-mocha-framework": "^0.14.0" }, "peerDependencies": { "mocha": ">= 2.3.3 < 6", diff --git a/packages/stryker-mocha-runner/src/MochaConfigEditor.ts b/packages/stryker-mocha-runner/src/MochaConfigEditor.ts index ac47a1b178..1d9b9e5929 100644 --- a/packages/stryker-mocha-runner/src/MochaConfigEditor.ts +++ b/packages/stryker-mocha-runner/src/MochaConfigEditor.ts @@ -1,9 +1,14 @@ import { ConfigEditor, Config } from 'stryker-api/config'; import { mochaOptionsKey } from './MochaRunnerOptions'; import MochaOptionsLoader from './MochaOptionsLoader'; +import { tokens } from 'stryker-api/plugin'; export default class MochaConfigEditor implements ConfigEditor { + + public static inject = tokens('loader'); + constructor(private readonly loader: MochaOptionsLoader) {} + public edit(config: Config): void { - config[mochaOptionsKey] = new MochaOptionsLoader().load(config); + config[mochaOptionsKey] = this.loader.load(config); } } diff --git a/packages/stryker-mocha-runner/src/MochaOptionsLoader.ts b/packages/stryker-mocha-runner/src/MochaOptionsLoader.ts index 0f85870c5c..5f58647a18 100644 --- a/packages/stryker-mocha-runner/src/MochaOptionsLoader.ts +++ b/packages/stryker-mocha-runner/src/MochaOptionsLoader.ts @@ -1,14 +1,17 @@ import * as path from 'path'; import * as fs from 'fs'; import { StrykerOptions } from 'stryker-api/core'; -import { getLogger } from 'stryker-api/logging'; import MochaRunnerOptions, { mochaOptionsKey } from './MochaRunnerOptions'; +import { tokens, commonTokens } from 'stryker-api/plugin'; +import { Logger } from 'stryker-api/logging'; export default class MochaOptionsLoader { - private readonly log = getLogger(MochaOptionsLoader.name); private readonly DEFAULT_MOCHA_OPTS = 'test/mocha.opts'; + public static inject = tokens(commonTokens.logger); + constructor(private readonly log: Logger) { } + public load(config: StrykerOptions): MochaRunnerOptions { const mochaOptions = Object.assign({}, config[mochaOptionsKey]) as MochaRunnerOptions; return Object.assign(this.loadMochaOptsFile(mochaOptions.opts), mochaOptions); diff --git a/packages/stryker-mocha-runner/src/index.ts b/packages/stryker-mocha-runner/src/index.ts index de12688084..8246a3b3ed 100644 --- a/packages/stryker-mocha-runner/src/index.ts +++ b/packages/stryker-mocha-runner/src/index.ts @@ -1,8 +1,19 @@ import { TestRunnerFactory } from 'stryker-api/test_runner'; -import { ConfigEditorFactory } from 'stryker-api/config'; +import { declareFactoryPlugin, PluginKind, BaseContext, tokens, commonTokens, Injector } from 'stryker-api/plugin'; import MochaTestRunner from './MochaTestRunner'; import MochaConfigEditor from './MochaConfigEditor'; +import MochaOptionsLoader from './MochaOptionsLoader'; TestRunnerFactory.instance().register('mocha', MochaTestRunner); -ConfigEditorFactory.instance().register('mocha-runner', MochaConfigEditor); + +export const strykerPlugins = [ + declareFactoryPlugin(PluginKind.ConfigEditor, 'mocha-runner', mochaConfigEditorFactory) +]; + +mochaConfigEditorFactory.inject = tokens(commonTokens.injector); +function mochaConfigEditorFactory(injector: Injector): MochaConfigEditor { + return injector + .provideClass('loader', MochaOptionsLoader) + .injectClass(MochaConfigEditor); +} diff --git a/packages/stryker-mocha-runner/test/helpers/initSinon.ts b/packages/stryker-mocha-runner/test/helpers/initSinon.ts index 3f17ab7b90..00ee898357 100644 --- a/packages/stryker-mocha-runner/test/helpers/initSinon.ts +++ b/packages/stryker-mocha-runner/test/helpers/initSinon.ts @@ -1,9 +1,7 @@ import * as sinon from 'sinon'; - -beforeEach(() => { - global.sandbox = sinon.createSandbox(); -}); +import { testInjector } from '@stryker-mutator/test-helpers'; afterEach(() => { - global.sandbox.restore(); + sinon.restore(); + testInjector.reset(); }); diff --git a/packages/stryker-mocha-runner/test/unit/MochaOptionsLoaderSpec.ts b/packages/stryker-mocha-runner/test/unit/MochaOptionsLoaderSpec.ts index 75a99e1928..e2041ae33d 100644 --- a/packages/stryker-mocha-runner/test/unit/MochaOptionsLoaderSpec.ts +++ b/packages/stryker-mocha-runner/test/unit/MochaOptionsLoaderSpec.ts @@ -3,9 +3,9 @@ import * as fs from 'fs'; import { Config } from 'stryker-api/config'; import MochaOptionsLoader from '../../src/MochaOptionsLoader'; import { expect } from 'chai'; -import * as logging from 'stryker-api/logging'; import MochaRunnerOptions from '../../src/MochaRunnerOptions'; -import { logger, Mock } from '../helpers/mockHelpers'; +import sinon = require('sinon'); +import { testInjector } from '@stryker-mutator/test-helpers'; describe('MochaOptionsLoader', () => { @@ -13,19 +13,16 @@ describe('MochaOptionsLoader', () => { let existsFileStub: sinon.SinonStub; let config: Config; let sut: MochaOptionsLoader; - let log: Mock; beforeEach(() => { - log = logger(); - sandbox.stub(logging, 'getLogger').returns(log); - readFileStub = sandbox.stub(fs, 'readFileSync'); - existsFileStub = sandbox.stub(fs, 'existsSync').returns(true); - sut = new MochaOptionsLoader(); + readFileStub = sinon.stub(fs, 'readFileSync'); + existsFileStub = sinon.stub(fs, 'existsSync').returns(true); + sut = testInjector.injector.injectClass(MochaOptionsLoader); config = new Config(); }); afterEach(() => { - sandbox.restore(); + sinon.restore(); }); it('should load a mocha.opts file if specified', () => { @@ -34,7 +31,7 @@ describe('MochaOptionsLoader', () => { opts: 'some/mocha.opts/file' }; sut.load(config); - expect(log.info).calledWith(`Loading mochaOpts from "${path.resolve('some/mocha.opts/file')}"`); + expect(testInjector.logger.info).calledWith(`Loading mochaOpts from "${path.resolve('some/mocha.opts/file')}"`); expect(fs.readFileSync).calledWith(path.resolve('some/mocha.opts/file')); }); @@ -46,13 +43,13 @@ describe('MochaOptionsLoader', () => { }; sut.load(config); - expect(log.error).calledWith(`Could not load opts from "${path.resolve('some/mocha.opts/file')}". Please make sure opts file exists.`); + expect(testInjector.logger.error).calledWith(`Could not load opts from "${path.resolve('some/mocha.opts/file')}". Please make sure opts file exists.`); }); it('should load default mocha.opts file if not specified', () => { readFileStub.returns(''); sut.load(config); - expect(log.info).calledWith(`Loading mochaOpts from "${path.resolve('test/mocha.opts')}"`); + expect(testInjector.logger.info).calledWith(`Loading mochaOpts from "${path.resolve('test/mocha.opts')}"`); expect(fs.readFileSync).calledWith(path.resolve('test/mocha.opts')); }); @@ -62,14 +59,14 @@ describe('MochaOptionsLoader', () => { }; sut.load(config); expect(fs.readFileSync).not.called; - expect(log.debug).calledWith('Not reading additional mochaOpts from a file'); + expect(testInjector.logger.debug).calledWith('Not reading additional mochaOpts from a file'); }); it('should not load default mocha.opts file if not found', () => { existsFileStub.returns(false); const options = sut.load(config); expect(options).deep.eq({}); - expect(log.debug).calledWith('No mocha opts file found, not loading additional mocha options (%s.opts was not defined).', 'mochaOptions'); + expect(testInjector.logger.debug).calledWith('No mocha opts file found, not loading additional mocha options (%s.opts was not defined).', 'mochaOptions'); }); it('should load `--require` and `-r` properties if specified in mocha.opts file', () => { @@ -139,8 +136,8 @@ describe('MochaOptionsLoader', () => { }; const options = sut.load(config); expect(options).deep.eq({ opts: 'some/mocha.opts/file' }); - expect(log.debug).calledWith('Ignoring option "--reporter" as it is not supported.'); - expect(log.debug).calledWith('Ignoring option "--ignore-leaks" as it is not supported.'); + expect(testInjector.logger.debug).calledWith('Ignoring option "--reporter" as it is not supported.'); + expect(testInjector.logger.debug).calledWith('Ignoring option "--ignore-leaks" as it is not supported.'); }); it('should ignore invalid --ui and --timeout options', () => { diff --git a/packages/stryker-mocha-runner/test/unit/MochaTestRunnerSpec.ts b/packages/stryker-mocha-runner/test/unit/MochaTestRunnerSpec.ts index 0ab3e06911..9d1957ad37 100644 --- a/packages/stryker-mocha-runner/test/unit/MochaTestRunnerSpec.ts +++ b/packages/stryker-mocha-runner/test/unit/MochaTestRunnerSpec.ts @@ -10,6 +10,7 @@ import * as utils from '../../src/utils'; import { Mock, mock, logger, runnerOptions } from '../helpers/mockHelpers'; import MochaRunnerOptions from '../../src/MochaRunnerOptions'; import { factory } from '../../../stryker-test-helpers/src'; +import sinon = require('sinon'); describe('MochaTestRunner', () => { @@ -21,15 +22,15 @@ describe('MochaTestRunner', () => { let log: Mock; beforeEach(() => { - MochaStub = sandbox.stub(LibWrapper, 'Mocha'); - requireStub = sandbox.stub(LibWrapper, 'require'); - multimatchStub = sandbox.stub(LibWrapper, 'multimatch'); - sandbox.stub(utils, 'evalGlobal'); + MochaStub = sinon.stub(LibWrapper, 'Mocha'); + requireStub = sinon.stub(LibWrapper, 'require'); + multimatchStub = sinon.stub(LibWrapper, 'multimatch'); + sinon.stub(utils, 'evalGlobal'); mocha = mock(Mocha) as any; mocha.suite = mock(EventEmitter); MochaStub.returns(mocha); log = logger(); - sandbox.stub(logging, 'getLogger').returns(log); + sinon.stub(logging, 'getLogger').returns(log); }); afterEach(() => { diff --git a/packages/stryker-test-helpers/src/TestInjector.ts b/packages/stryker-test-helpers/src/TestInjector.ts index a0aa6033bb..83391aa54c 100644 --- a/packages/stryker-test-helpers/src/TestInjector.ts +++ b/packages/stryker-test-helpers/src/TestInjector.ts @@ -38,7 +38,8 @@ class TestInjector { this.options = {}; this.logger = factory.logger(); this.pluginResolver = { - resolve: sinon.stub() + resolve: sinon.stub(), + resolveAll: sinon.stub() }; } } diff --git a/packages/stryker-test-helpers/src/factory.ts b/packages/stryker-test-helpers/src/factory.ts index 63441d0cd2..6abe582e3f 100644 --- a/packages/stryker-test-helpers/src/factory.ts +++ b/packages/stryker-test-helpers/src/factory.ts @@ -1,6 +1,6 @@ import { TestResult, TestStatus, RunResult, RunStatus } from 'stryker-api/test_runner'; import { Mutant } from 'stryker-api/mutant'; -import { Config } from 'stryker-api/config'; +import { Config, ConfigEditor } from 'stryker-api/config'; import { Logger } from 'stryker-api/logging'; import { TestFramework, TestSelection } from 'stryker-api/test_framework'; import { MutantStatus, MatchedMutant, MutantResult, Reporter, ScoreResult } from 'stryker-api/report'; @@ -136,6 +136,12 @@ export function reporter(name = 'fooReporter'): sinon.SinonStubbedInstance { + return { + edit: sinon.stub() + }; +} + export function matchedMutant(numberOfTests: number, mutantId = numberOfTests.toString()): MatchedMutant { const scopedTestIds: number[] = []; for (let i = 0; i < numberOfTests; i++) { diff --git a/packages/stryker-typescript/package.json b/packages/stryker-typescript/package.json index 22d2598500..75b2237e2a 100644 --- a/packages/stryker-typescript/package.json +++ b/packages/stryker-typescript/package.json @@ -35,11 +35,13 @@ "homepage": "https://github.com/stryker-mutator/stryker/tree/master/packages/stryker-typescript#readme", "license": "Apache-2.0", "dependencies": { + "@stryker-mutator/util": "~0.0.3", "lodash.flatmap": "~4.5.0", "semver": "~5.6.0", "tslib": "~1.9.3" }, "devDependencies": { + "@stryker-mutator/test-helpers": "0.0.0", "@types/lodash.flatmap": "~4.5.3", "@types/semver": "~5.5.0", "stryker-api": "^0.23.0", @@ -56,4 +58,4 @@ "src/**/*.ts" ] } -} +} \ No newline at end of file diff --git a/packages/stryker-typescript/src/TypescriptConfigEditor.ts b/packages/stryker-typescript/src/TypescriptConfigEditor.ts index a8fb98db2d..5306e89f40 100644 --- a/packages/stryker-typescript/src/TypescriptConfigEditor.ts +++ b/packages/stryker-typescript/src/TypescriptConfigEditor.ts @@ -1,11 +1,12 @@ import * as os from 'os'; import * as path from 'path'; import * as ts from 'typescript'; -import { getLogger } from 'stryker-api/logging'; import { ConfigEditor, Config } from 'stryker-api/config'; import { CONFIG_KEY_FILE, CONFIG_KEY } from './helpers/keys'; +import { Logger } from 'stryker-api/logging'; import * as fs from 'fs'; import { normalizeFileForTypescript, normalizeFileFromTypescript } from './helpers/tsHelpers'; +import { tokens, commonTokens } from 'stryker-api/plugin'; // Override some compiler options that have to do with code quality. When mutating, we're not interested in the resulting code quality // See https://github.com/stryker-mutator/stryker/issues/391 for more info @@ -17,7 +18,8 @@ const COMPILER_OPTIONS_OVERRIDES: Readonly> = Object export default class TypescriptConfigEditor implements ConfigEditor { - private readonly log = getLogger(TypescriptConfigEditor.name); + public static inject = tokens(commonTokens.logger); + constructor(private readonly log: Logger) { } public edit(strykerConfig: Config, host: ts.ParseConfigHost = ts.sys) { this.loadTSConfig(strykerConfig, host); diff --git a/packages/stryker-typescript/src/index.ts b/packages/stryker-typescript/src/index.ts index f42906da61..e34078abc2 100644 --- a/packages/stryker-typescript/src/index.ts +++ b/packages/stryker-typescript/src/index.ts @@ -1,10 +1,12 @@ -import { ConfigEditorFactory } from 'stryker-api/config'; import { MutatorFactory } from 'stryker-api/mutant'; import { TranspilerFactory } from 'stryker-api/transpile'; +import { declareClassPlugin, PluginKind } from 'stryker-api/plugin'; import TypescriptConfigEditor from './TypescriptConfigEditor'; import TypescriptMutator from './TypescriptMutator'; import TypescriptTranspiler from './TypescriptTranspiler'; -ConfigEditorFactory.instance().register('typescript', TypescriptConfigEditor); +export const strykerPlugins = [ + declareClassPlugin(PluginKind.ConfigEditor, 'typescript', TypescriptConfigEditor) +]; MutatorFactory.instance().register('typescript', TypescriptMutator); TranspilerFactory.instance().register('typescript', TypescriptTranspiler); diff --git a/packages/stryker-typescript/test/helpers/initSinon.ts b/packages/stryker-typescript/test/helpers/initSinon.ts index 525a422ab0..8924a26204 100644 --- a/packages/stryker-typescript/test/helpers/initSinon.ts +++ b/packages/stryker-typescript/test/helpers/initSinon.ts @@ -2,11 +2,11 @@ import * as sinon from 'sinon'; import * as chai from 'chai'; import * as sinonChai from 'sinon-chai'; import * as chaiAsPromised from 'chai-as-promised'; +import { testInjector } from '@stryker-mutator/test-helpers'; + chai.use(sinonChai); chai.use(chaiAsPromised); -beforeEach(() => { - global.sandbox = sinon.createSandbox(); -}); afterEach(() => { - global.sandbox.restore(); + sinon.restore(); + testInjector.reset(); }); diff --git a/packages/stryker-typescript/test/integration/allowJS.it.ts b/packages/stryker-typescript/test/integration/allowJS.it.ts index d4deacf8b6..fc9eccebb6 100644 --- a/packages/stryker-typescript/test/integration/allowJS.it.ts +++ b/packages/stryker-typescript/test/integration/allowJS.it.ts @@ -6,6 +6,7 @@ import { File } from 'stryker-api/core'; import { CONFIG_KEY } from '../../src/helpers/keys'; import TypescriptTranspiler from '../../src/TypescriptTranspiler'; import { expect } from 'chai'; +import { testInjector } from '@stryker-mutator/test-helpers'; describe('AllowJS integration', () => { @@ -13,7 +14,7 @@ describe('AllowJS integration', () => { let inputFiles: File[]; beforeEach(() => { - const configEditor = new TypescriptConfigEditor(); + const configEditor = testInjector.injector.injectClass(TypescriptConfigEditor); config = new Config(); config.set({ tsconfigFile: path.resolve(__dirname, '..', '..', 'testResources', 'allowJS', 'tsconfig.json'), diff --git a/packages/stryker-typescript/test/integration/configEditor.it.ts b/packages/stryker-typescript/test/integration/configEditor.it.ts index 522fbb83ad..6b0f049903 100644 --- a/packages/stryker-typescript/test/integration/configEditor.it.ts +++ b/packages/stryker-typescript/test/integration/configEditor.it.ts @@ -2,6 +2,7 @@ import * as path from 'path'; import { expect } from 'chai'; import TypescriptConfigEditor from '../../src/TypescriptConfigEditor'; import { Config } from 'stryker-api/config'; +import { testInjector } from '@stryker-mutator/test-helpers'; function resolveSampleProject(relativeFileName: string) { return path.resolve(__dirname, '..', '..', 'testResources', 'sampleProject', relativeFileName); @@ -11,7 +12,7 @@ describe('Read TS Config file integration', () => { it('should discover files like TS does', () => { const config = new Config(); config.tsconfigFile = resolveSampleProject('tsconfig.json'); - new TypescriptConfigEditor().edit(config); + testInjector.injector.injectClass(TypescriptConfigEditor).edit(config); const actual = config.tsconfig; expect(actual.fileNames.map(path.normalize)).deep.eq([ resolveSampleProject('math.ts'), diff --git a/packages/stryker-typescript/test/integration/ownDogFoodSpec.ts b/packages/stryker-typescript/test/integration/ownDogFoodSpec.ts index 7a58e53543..b1b3935ed5 100644 --- a/packages/stryker-typescript/test/integration/ownDogFoodSpec.ts +++ b/packages/stryker-typescript/test/integration/ownDogFoodSpec.ts @@ -6,6 +6,7 @@ import { File } from 'stryker-api/core'; import TypescriptConfigEditor from '../../src/TypescriptConfigEditor'; import TypescriptTranspiler from '../../src/TypescriptTranspiler'; import { CONFIG_KEY } from '../../src/helpers/keys'; +import { testInjector } from '@stryker-mutator/test-helpers'; describe('stryker-typescript', () => { @@ -13,7 +14,7 @@ describe('stryker-typescript', () => { let inputFiles: File[]; beforeEach(() => { - const configEditor = new TypescriptConfigEditor(); + const configEditor = testInjector.injector.injectClass(TypescriptConfigEditor); config = new Config(); config.set({ tsconfigFile: path.resolve(__dirname, '..', '..', 'tsconfig.src.json'), diff --git a/packages/stryker-typescript/test/integration/sampleSpec.ts b/packages/stryker-typescript/test/integration/sampleSpec.ts index 438c6109ae..a736766ede 100644 --- a/packages/stryker-typescript/test/integration/sampleSpec.ts +++ b/packages/stryker-typescript/test/integration/sampleSpec.ts @@ -8,6 +8,7 @@ import TypescriptConfigEditor from '../../src/TypescriptConfigEditor'; import TypescriptMutator from '../../src/TypescriptMutator'; import TypescriptTranspiler from '../../src/TypescriptTranspiler'; import { CONFIG_KEY } from '../../src/helpers/keys'; +import { testInjector } from '@stryker-mutator/test-helpers'; describe('Sample integration', () => { @@ -15,7 +16,7 @@ describe('Sample integration', () => { let inputFiles: File[]; beforeEach(() => { - const configEditor = new TypescriptConfigEditor(); + const configEditor = testInjector.injector.injectClass(TypescriptConfigEditor); config = new Config(); config.set({ tsconfigFile: path.resolve(__dirname, '..', '..', 'testResources', 'sampleProject', 'tsconfig.json'), diff --git a/packages/stryker-typescript/test/integration/useHeaderFile.ts b/packages/stryker-typescript/test/integration/useHeaderFile.ts index 014d3778c0..4b8ee02727 100644 --- a/packages/stryker-typescript/test/integration/useHeaderFile.ts +++ b/packages/stryker-typescript/test/integration/useHeaderFile.ts @@ -6,6 +6,7 @@ import { File } from 'stryker-api/core'; import TypescriptConfigEditor from '../../src/TypescriptConfigEditor'; import TypescriptTranspiler from '../../src/TypescriptTranspiler'; import { CONFIG_KEY } from '../../src/helpers/keys'; +import { testInjector } from '@stryker-mutator/test-helpers'; describe('Use header file integration', () => { @@ -13,7 +14,7 @@ describe('Use header file integration', () => { let inputFiles: File[]; beforeEach(() => { - const configEditor = new TypescriptConfigEditor(); + const configEditor = testInjector.injector.injectClass(TypescriptConfigEditor); config = new Config(); config.set({ tsconfigFile: path.resolve(__dirname, '..', '..', 'testResources', 'useHeaderFile', 'tsconfig.json'), diff --git a/packages/stryker-typescript/test/unit/TypescriptConfigEditorSpec.ts b/packages/stryker-typescript/test/unit/TypescriptConfigEditorSpec.ts index 44b20e7d48..450c98140b 100644 --- a/packages/stryker-typescript/test/unit/TypescriptConfigEditorSpec.ts +++ b/packages/stryker-typescript/test/unit/TypescriptConfigEditorSpec.ts @@ -1,41 +1,31 @@ import * as fs from 'fs'; import * as path from 'path'; -import * as logging from 'stryker-api/logging'; import * as ts from 'typescript'; import { expect } from 'chai'; import { SinonStub, match } from 'sinon'; import { Config } from 'stryker-api/config'; import TypescriptConfigEditor from './../../src/TypescriptConfigEditor'; +import sinon = require('sinon'); +import { testInjector } from '@stryker-mutator/test-helpers'; const CONFIG_KEY = 'tsconfigFile'; describe('TypescriptConfigEditor edit', () => { let readFileSyncStub: SinonStub; - let loggerStub: { - debug: SinonStub; - info: SinonStub; - error: SinonStub; - }; let config: Config; let sut: TypescriptConfigEditor; beforeEach(() => { - readFileSyncStub = sandbox.stub(fs, 'readFileSync'); - loggerStub = { - debug: sandbox.stub(), - error: sandbox.stub(), - info: sandbox.stub() - }; - sandbox.stub(logging, 'getLogger').returns(loggerStub); + readFileSyncStub = sinon.stub(fs, 'readFileSync'); config = new Config(); - sut = new TypescriptConfigEditor(); + sut = testInjector.injector.injectClass(TypescriptConfigEditor); }); it('should not load any config if "tsconfigFile" is not specified', () => { sut.edit(config); expect(config[CONFIG_KEY]).undefined; - expect(loggerStub.debug).calledWith('No \'%s\' specified, not loading any config', CONFIG_KEY); + expect(testInjector.logger.debug).calledWith('No \'%s\' specified, not loading any config', CONFIG_KEY); }); it('should load the given tsconfig file', () => { @@ -96,7 +86,7 @@ describe('TypescriptConfigEditor edit', () => { readFileSyncStub.returns(`{ "extends": "./parent.tsconfig.json" }`); config[CONFIG_KEY] = 'tsconfig.json'; sut.edit(config, parseConfigHost({ readFile: () => `invalid json` })); - expect(loggerStub.error).calledWithMatch(match('error TS1005: \'{\' expected.')); + expect(testInjector.logger.error).calledWithMatch(match('error TS1005: \'{\' expected.')); }); function parseConfigHost(overrides?: Partial): ts.ParseConfigHost { diff --git a/packages/stryker-typescript/test/unit/TypescriptTranspilerSpec.ts b/packages/stryker-typescript/test/unit/TypescriptTranspilerSpec.ts index 031993e85b..9dcd9ca1ce 100644 --- a/packages/stryker-typescript/test/unit/TypescriptTranspilerSpec.ts +++ b/packages/stryker-typescript/test/unit/TypescriptTranspilerSpec.ts @@ -7,6 +7,7 @@ import { File } from 'stryker-api/core'; import { EmitOutput } from '../../src/transpiler/TranspilingLanguageService'; import { serialize } from 'surrial'; import TranspileFilter from '../../src/transpiler/TranspileFilter'; +import sinon = require('sinon'); describe('TypescriptTranspiler', () => { @@ -20,10 +21,10 @@ describe('TypescriptTranspiler', () => { languageService = mock(TranspilingLanguageService); transpileFilterMock = { // Cannot use `mock` as it is an abstract class - isIncluded: sandbox.stub() + isIncluded: sinon.stub() }; - sandbox.stub(TranspileFilter, 'create').returns(transpileFilterMock); - sandbox.stub(transpilingLanguageService, 'default').returns(languageService); + sinon.stub(TranspileFilter, 'create').returns(transpileFilterMock); + sinon.stub(transpilingLanguageService, 'default').returns(languageService); }); describe('transpile', () => { diff --git a/packages/stryker-typescript/test/unit/helpers/tsHelpersSpec.ts b/packages/stryker-typescript/test/unit/helpers/tsHelpersSpec.ts index c93deee64a..0c6aa335ff 100644 --- a/packages/stryker-typescript/test/unit/helpers/tsHelpersSpec.ts +++ b/packages/stryker-typescript/test/unit/helpers/tsHelpersSpec.ts @@ -3,12 +3,13 @@ import * as tsHelpers from '../../../src/helpers/tsHelpers'; import * as semver from 'semver'; import * as ts from 'typescript'; import { File } from 'stryker-api/core'; +import sinon = require('sinon'); describe('tsHelpers', () => { let satisfiesStub: sinon.SinonStub; beforeEach(() => { - satisfiesStub = sandbox.stub(semver, 'satisfies'); + satisfiesStub = sinon.stub(semver, 'satisfies'); }); describe('guardTypescriptVersion', () => { diff --git a/packages/stryker-typescript/tsconfig.test.json b/packages/stryker-typescript/tsconfig.test.json index f5f5672f06..1efe3a769c 100644 --- a/packages/stryker-typescript/tsconfig.test.json +++ b/packages/stryker-typescript/tsconfig.test.json @@ -13,6 +13,9 @@ "references": [ { "path": "tsconfig.src.json" - } + }, + { + "path": "../stryker-test-helpers/tsconfig.src.json" + }, ] } \ No newline at end of file diff --git a/packages/stryker/src/Stryker.ts b/packages/stryker/src/Stryker.ts index a3865e41b4..1218a6826b 100644 --- a/packages/stryker/src/Stryker.ts +++ b/packages/stryker/src/Stryker.ts @@ -1,5 +1,5 @@ import { getLogger } from 'stryker-api/logging'; -import { Config, ConfigEditorFactory } from 'stryker-api/config'; +import { Config } from 'stryker-api/config'; import { StrykerOptions, MutatorDescriptor } from 'stryker-api/core'; import { MutantResult } from 'stryker-api/report'; import { TestFramework } from 'stryker-api/test_framework'; @@ -25,6 +25,7 @@ import * as coreTokens from './di/coreTokens'; import { Injector, rootInjector, Scope } from 'typed-inject'; import { loggerFactory } from './di/loggerFactory'; import { PluginCreator } from './di/PluginCreator'; +import { ConfigEditorApplier } from './config/ConfigEditorApplier'; export default class Stryker { @@ -51,16 +52,19 @@ export default class Stryker { pluginLoader.load(); // Log level may have changed LogConfigurator.configureMainProcess(this.config.logLevel, this.config.fileLogLevel, this.config.allowConsoleColors); // logLevel could be changed - this.applyConfigEditors(); - this.freezeConfig(); - this.injector = rootInjector + const configEditorInjector = rootInjector .provideValue(commonTokens.getLogger, getLogger) .provideFactory(commonTokens.logger, loggerFactory, Scope.Transient) - .provideValue(commonTokens.pluginResolver, pluginLoader as PluginResolver) + .provideValue(commonTokens.pluginResolver, pluginLoader as PluginResolver); + configEditorInjector + .provideFactory(coreTokens.pluginCreatorConfigEditor, PluginCreator.createFactory(PluginKind.ConfigEditor)) + .injectClass(ConfigEditorApplier).edit(this.config); + this.freezeConfig(); + this.injector = configEditorInjector .provideValue(commonTokens.config, this.config) .provideValue(commonTokens.options, this.config as StrykerOptions); this.reporter = this.injector - .provideFactory(coreTokens.reporterPluginCreator, PluginCreator.createFactory(PluginKind.Reporter)) + .provideFactory(coreTokens.pluginCreatorReporter, PluginCreator.createFactory(PluginKind.Reporter)) .injectClass(BroadcastReporter); this.testFramework = new TestFrameworkOrchestrator(this.config).determineTestFramework(); new ConfigValidator(this.config, this.testFramework).validate(); @@ -144,12 +148,6 @@ export default class Stryker { } } - private applyConfigEditors() { - ConfigEditorFactory.instance().knownNames().forEach(configEditorName => { - ConfigEditorFactory.instance().create(configEditorName, undefined).edit(this.config); - }); - } - private freezeConfig() { // A config class instance is not serializable using surrial. // This is a temporary work around diff --git a/packages/stryker/src/config/ConfigEditorApplier.ts b/packages/stryker/src/config/ConfigEditorApplier.ts new file mode 100644 index 0000000000..63fc820222 --- /dev/null +++ b/packages/stryker/src/config/ConfigEditorApplier.ts @@ -0,0 +1,23 @@ +import { Config, ConfigEditor } from 'stryker-api/config'; +import { tokens } from 'typed-inject'; +import { PluginResolver, PluginKind, commonTokens } from 'stryker-api/plugin'; +import { PluginCreator } from '../di/PluginCreator'; +import * as coreTokens from '../di/coreTokens'; + +/** + * Class that applies all config editor plugins + */ +export class ConfigEditorApplier implements ConfigEditor { + + public static inject = tokens(commonTokens.pluginResolver, coreTokens.pluginCreatorConfigEditor); + + constructor(private readonly pluginResolver: PluginResolver, + private readonly pluginCreator: PluginCreator) { } + + public edit(config: Config): void { + this.pluginResolver.resolveAll(PluginKind.ConfigEditor).forEach(plugin => { + const configEditor = this.pluginCreator.create(plugin.name); + configEditor.edit(config); + }); + } +} diff --git a/packages/stryker/src/di/PluginLoader.ts b/packages/stryker/src/di/PluginLoader.ts index 71a72670a8..50bd8b9958 100644 --- a/packages/stryker/src/di/PluginLoader.ts +++ b/packages/stryker/src/di/PluginLoader.ts @@ -45,6 +45,11 @@ export default class PluginLoader implements PluginResolver { } } + public resolveAll(kind: T): Plugins[T][] { + const plugins = this.pluginsByKind.get(kind); + return plugins || [] as any; + } + private loadDeprecatedPlugins() { this.loadDeprecatedPluginsFor(PluginKind.ConfigEditor, ConfigEditorFactory.instance(), [], () => undefined); this.loadDeprecatedPluginsFor(PluginKind.Reporter, ReporterFactory.instance(), tokens('config'), ([config]) => config); diff --git a/packages/stryker/src/di/coreTokens.ts b/packages/stryker/src/di/coreTokens.ts index 786d1c83ad..2176b21f16 100644 --- a/packages/stryker/src/di/coreTokens.ts +++ b/packages/stryker/src/di/coreTokens.ts @@ -1,2 +1,3 @@ export const pluginKind = 'pluginKind'; -export const reporterPluginCreator = 'reporterPluginCreator'; +export const pluginCreatorConfigEditor = 'pluginCreatorConfigEditor'; +export const pluginCreatorReporter = 'pluginCreatorReporter'; diff --git a/packages/stryker/src/reporters/BroadcastReporter.ts b/packages/stryker/src/reporters/BroadcastReporter.ts index 8b78dd703f..3c0e2ed14c 100644 --- a/packages/stryker/src/reporters/BroadcastReporter.ts +++ b/packages/stryker/src/reporters/BroadcastReporter.ts @@ -12,7 +12,7 @@ export default class BroadcastReporter implements StrictReporter { public static readonly inject = tokens( commonTokens.options, - coreTokens.reporterPluginCreator, + coreTokens.pluginCreatorReporter, commonTokens.logger); public readonly reporters: { diff --git a/packages/stryker/test/unit/StrykerSpec.ts b/packages/stryker/test/unit/StrykerSpec.ts index fcf73600d0..aa32361dcf 100644 --- a/packages/stryker/test/unit/StrykerSpec.ts +++ b/packages/stryker/test/unit/StrykerSpec.ts @@ -4,7 +4,7 @@ import { File, LogLevel } from 'stryker-api/core'; import { RunResult } from 'stryker-api/test_runner'; import { TestFramework } from 'stryker-api/test_framework'; import Stryker from '../../src/Stryker'; -import { Config, ConfigEditorFactory, ConfigEditor } from 'stryker-api/config'; +import { Config } from 'stryker-api/config'; import { expect } from 'chai'; import InputFileResolver, * as inputFileResolver from '../../src/input/InputFileResolver'; import ConfigReader, * as configReader from '../../src/config/ConfigReader'; @@ -26,13 +26,7 @@ import InputFileCollection from '../../src/input/InputFileCollection'; import LogConfigurator from '../../src/logging/LogConfigurator'; import LoggingClientContext from '../../src/logging/LoggingClientContext'; import { OptionsContext } from 'stryker-api/plugin'; - -class FakeConfigEditor implements ConfigEditor { - constructor() { } - public edit(config: Config) { - config.testRunner = 'fakeTestRunner'; - } -} +import { ConfigEditorApplier } from '../../src/config/ConfigEditorApplier'; const LOGGING_CONTEXT: LoggingClientContext = Object.freeze({ level: LogLevel.Debug, @@ -44,6 +38,7 @@ describe('Stryker', () => { let testFramework: TestFramework; let inputFileResolverMock: Mock; let testFrameworkOrchestratorMock: Mock; + let configEditorApplierMock: Mock; let configValidatorMock: Mock; let configReaderMock: Mock; let initialTestExecutorMock: Mock; @@ -80,13 +75,16 @@ describe('Stryker', () => { injectorMock.provideFactory.returnsThis(); injectorMock.provideValue.returnsThis(); mutantRunResultMatcherMock = mock(MutantRunResultMatcher); + configEditorApplierMock = mock(ConfigEditorApplier); mutatorMock = mock(MutatorFacade); + injectorMock.injectClass + .withArgs(ConfigEditorApplier).returns(configEditorApplierMock) + .withArgs(BroadcastReporter).returns(reporter); configureMainProcessStub = sinon.stub(LogConfigurator, 'configureMainProcess'); configureLoggingServerStub = sinon.stub(LogConfigurator, 'configureLoggingServer'); shutdownLoggingStub = sinon.stub(LogConfigurator, 'shutdown'); configureLoggingServerStub.resolves(LOGGING_CONTEXT); inputFileResolverMock = mock(InputFileResolver); - injectorMock.injectClass.returns(reporter); testFramework = testFrameworkMock(); initialTestExecutorMock = mock(InitialTestExecutor); mutationTestExecutorMock = mock(MutationTestExecutor); @@ -112,13 +110,12 @@ describe('Stryker', () => { describe('when constructed', () => { beforeEach(() => { - ConfigEditorFactory.instance().register('FakeConfigEditor', FakeConfigEditor); strykerConfig.plugins = ['plugin1']; sut = new Stryker({}); }); - it('should use the config editor to override config', () => { - expect(sut.config.testRunner).to.be.eq('fakeTestRunner'); + it('should apply the config editors', () => { + expect(configEditorApplierMock.edit).calledWith(strykerConfig); }); it('should configure logging for master', () => { diff --git a/packages/stryker/test/unit/config/ConfigEditorApplier.spec.ts b/packages/stryker/test/unit/config/ConfigEditorApplier.spec.ts new file mode 100644 index 0000000000..8a25c28289 --- /dev/null +++ b/packages/stryker/test/unit/config/ConfigEditorApplier.spec.ts @@ -0,0 +1,34 @@ +import { ConfigEditorApplier } from '../../../src/config/ConfigEditorApplier'; +import { testInjector, factory } from '@stryker-mutator/test-helpers'; +import { PluginKind } from 'stryker-api/plugin'; +import * as sinon from 'sinon'; +import { expect } from 'chai'; +import * as coreTokens from '../../../src/di/coreTokens'; +import { PluginCreator } from '../../../src/di/PluginCreator'; + +describe('ConfigEditorApplier', () => { + let sut: ConfigEditorApplier; + let pluginCreatorMock: sinon.SinonStubbedInstance>; + + beforeEach(() => { + pluginCreatorMock = sinon.createStubInstance(PluginCreator); + sut = testInjector.injector + .provideValue(coreTokens.pluginCreatorConfigEditor, pluginCreatorMock as unknown as PluginCreator) + .injectClass(ConfigEditorApplier); + }); + + it('should apply all config editors', () => { + const config = factory.config(); + const fooConfigEditor = factory.configEditor(); + const barConfigEditor = factory.configEditor(); + const configEditorPlugins = [{ name: 'fooConfigEditorPlugin' }, { name: 'barConfigEditorPlugin' }]; + testInjector.pluginResolver.resolveAll.returns(configEditorPlugins); + pluginCreatorMock.create + .withArgs(configEditorPlugins[0].name).returns(fooConfigEditor) + .withArgs(configEditorPlugins[1].name).returns(barConfigEditor); + sut.edit(config); + expect(fooConfigEditor.edit).calledWith(config); + expect(barConfigEditor.edit).calledWith(config); + }); + +}); diff --git a/packages/stryker/test/unit/reporters/BroadcastReporterSpec.ts b/packages/stryker/test/unit/reporters/BroadcastReporterSpec.ts index 7bba6b759f..499efb5281 100644 --- a/packages/stryker/test/unit/reporters/BroadcastReporterSpec.ts +++ b/packages/stryker/test/unit/reporters/BroadcastReporterSpec.ts @@ -142,7 +142,7 @@ describe('BroadcastReporter', () => { function createSut() { return testInjector.injector - .provideValue(coreTokens.reporterPluginCreator, pluginCreatorMock as unknown as PluginCreator) + .provideValue(coreTokens.pluginCreatorReporter, pluginCreatorMock as unknown as PluginCreator) .injectClass(BroadcastReporter); }