Skip to content

Commit

Permalink
feat(config-editors): Remove side effects from all config editor plug…
Browse files Browse the repository at this point in the history
…ins (#1317)

Load all config editor plugins via new plugin mechanism. Migrate the existing config editors to new plugin mechanism.

* `JestConfigEditor`
* `MochaRunnerConfigEditor`
* `TypeScriptConfigEditor`
  • Loading branch information
nicojs committed Feb 5, 2019
1 parent 04dff24 commit 1f61bed
Show file tree
Hide file tree
Showing 43 changed files with 227 additions and 140 deletions.
5 changes: 5 additions & 0 deletions packages/stryker-api/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
3 changes: 2 additions & 1 deletion packages/stryker-api/src/plugin/Plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -89,4 +89,5 @@ export type Plugins = {
*/
export interface PluginResolver {
resolve<T extends keyof Plugins>(kind: T, name: string): Plugins[T];
resolveAll<T extends keyof Plugins>(kind: T): Plugins[T][];
}
1 change: 1 addition & 0 deletions packages/stryker-jest-runner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
9 changes: 5 additions & 4 deletions packages/stryker-jest-runner/src/JestConfigEditor.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import * as fs from 'fs';
import { Config, ConfigEditor } from 'stryker-api/config';
import JestConfigLoader from './configLoaders/JestConfigLoader';
import CustomJestConfigLoader from './configLoaders/CustomJestConfigLoader';
import ReactScriptsJestConfigLoader from './configLoaders/ReactScriptsJestConfigLoader';
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
Expand Down Expand Up @@ -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':
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}

Expand All @@ -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) */ }
}
}
6 changes: 4 additions & 2 deletions packages/stryker-jest-runner/src/index.ts
Original file line number Diff line number Diff line change
@@ -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);
2 changes: 2 additions & 0 deletions packages/stryker-jest-runner/test/helpers/initSinon.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as sinon from 'sinon';
import { testInjector } from '@stryker-mutator/test-helpers';

afterEach(() => {
sinon.restore();
testInjector.reset();
});
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
});

Expand Down Expand Up @@ -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');
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
});
Expand All @@ -37,20 +33,16 @@ 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'
});
});

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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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', () => {
Expand Down
3 changes: 3 additions & 0 deletions packages/stryker-jest-runner/tsconfig.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"references": [
{
"path": "./tsconfig.src.json"
},
{
"path": "../stryker-test-helpers/tsconfig.src.json"
}
]
}
4 changes: 2 additions & 2 deletions packages/stryker-mocha-runner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 6 additions & 1 deletion packages/stryker-mocha-runner/src/MochaConfigEditor.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
7 changes: 5 additions & 2 deletions packages/stryker-mocha-runner/src/MochaOptionsLoader.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
15 changes: 13 additions & 2 deletions packages/stryker-mocha-runner/src/index.ts
Original file line number Diff line number Diff line change
@@ -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<BaseContext>): MochaConfigEditor {
return injector
.provideClass('loader', MochaOptionsLoader)
.injectClass(MochaConfigEditor);
}
8 changes: 3 additions & 5 deletions packages/stryker-mocha-runner/test/helpers/initSinon.ts
Original file line number Diff line number Diff line change
@@ -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();
});
Loading

0 comments on commit 1f61bed

Please sign in to comment.