Skip to content

Commit

Permalink
feat(html-reporter): Remove side effects from html reporter (#1314)
Browse files Browse the repository at this point in the history
Remove use of `ReporterFactory.instance().register()` from html-reporter, instead exporting it in `strykerPlugins` array.
  • Loading branch information
nicojs authored Jan 23, 2019
1 parent 743cf2d commit 66d65f7
Show file tree
Hide file tree
Showing 14 changed files with 110 additions and 74 deletions.
8 changes: 3 additions & 5 deletions packages/stryker-html-reporter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,19 @@
"mkdirp": "~0.5.1",
"mz": "~2.7.0",
"rimraf": "~2.6.1",
"stryker-api": "~0.23.0",
"typed-html": "~0.6.3"
},
"peerDependencies": {
"stryker-api": ">=0.18.0 <0.24.0"
},
"devDependencies": {
"@stryker-mutator/test-helpers": "0.0.0",
"@types/file-url": "~2.0.0",
"@types/jsdom": "~12.2.0",
"@types/node": "^10.11.5",
"bootstrap": "4.1.3",
"highlight.js": "~9.13.0",
"jquery": "~3.3.1",
"jsdom": "~13.1.0",
"popper.js": "~1.14.3",
"stryker-api": "^0.23.0"
"popper.js": "~1.14.3"
},
"contributors": [
"Nico Jansen <[email protected]>",
Expand Down
26 changes: 14 additions & 12 deletions packages/stryker-html-reporter/src/HtmlReporter.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import { getLogger } from 'stryker-api/logging';
import { Logger } from 'stryker-api/logging';
import fileUrl = require('file-url');
import * as path from 'path';
import { Config } from 'stryker-api/config';
import { Reporter, MutantResult, SourceFile, ScoreResult } from 'stryker-api/report';
import * as util from './util';
import * as templates from './templates';
import Breadcrumb from './Breadcrumb';
import { StrykerOptions } from 'stryker-api/core';
import { tokens, commonTokens } from 'stryker-api/plugin';

const DEFAULT_BASE_FOLDER = path.normalize('reports/mutation/html');
export const RESOURCES_DIR_NAME = 'strykerResources';

export default class HtmlReporter implements Reporter {
private readonly log = getLogger(HtmlReporter.name);
private _baseDir: string;
private mainPromise: Promise<void>;
private mutantResults: MutantResult[];
private files: SourceFile[];
private scoreResult: ScoreResult;

constructor(private readonly options: Config) {
private _baseDir!: string;
private mainPromise!: Promise<void>;
private mutantResults!: MutantResult[];
private files!: SourceFile[];
private scoreResult!: ScoreResult;

constructor(private readonly options: StrykerOptions, private readonly log: Logger) {
}

public static readonly inject = tokens(commonTokens.options, commonTokens.logger);

public onAllSourceFilesRead(files: SourceFile[]) {
this.files = files;
}
Expand Down Expand Up @@ -65,7 +67,7 @@ export default class HtmlReporter implements Reporter {
if (child.representsFile) {
return this.writeReportFile(child, currentDirectory, breadcrumb.add(child.name, util.countPathSep(child.name)));
} else {
return this.writeReportDirectory(child, path.join(currentDirectory, child.name), breadcrumb.add(child.name, util.countPathSep(child.name) + 1))
return this.writeReportDirectory(child, path.join(currentDirectory, child.name), breadcrumb.add(child.name, util.countPathSep(child.name) + 1))
.then(_ => void 0);
}
}));
Expand All @@ -92,7 +94,7 @@ export default class HtmlReporter implements Reporter {
return path.join(this.baseDir, RESOURCES_DIR_NAME);
}

private get baseDir() {
private get baseDir(): string {
if (!this._baseDir) {
if (this.options.htmlReporter && this.options.htmlReporter.baseDir) {
this._baseDir = this.options.htmlReporter.baseDir;
Expand Down
6 changes: 4 additions & 2 deletions packages/stryker-html-reporter/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { ReporterFactory } from 'stryker-api/report';
import HtmlReporter from './HtmlReporter';
import { PluginKind, declareClassPlugin } from 'stryker-api/plugin';

ReporterFactory.instance().register('html', HtmlReporter);
export const strykerPlugins = [
declareClassPlugin(PluginKind.Reporter, 'html', HtmlReporter)
];
7 changes: 7 additions & 0 deletions packages/stryker-html-reporter/test/helpers/initSinon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as sinon from 'sinon';
import { testInjector } from '@stryker-mutator/test-helpers';

afterEach(() => {
sinon.restore();
testInjector.reset();
});
30 changes: 0 additions & 30 deletions packages/stryker-html-reporter/test/helpers/loggingMock.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@ import * as fs from 'fs';
import * as path from 'path';
import { expect } from 'chai';
import { Config } from 'stryker-api/config';
import logger from '../helpers/loggingMock';
import EventPlayer from '../helpers/EventPlayer';
import fileUrl = require('file-url');
import HtmlReporter from '../../src/HtmlReporter';
import { testInjector } from '@stryker-mutator/test-helpers';

describe('HtmlReporter with example math project', () => {
let sut: HtmlReporter;
const baseDir = 'reports/mutation/math';

beforeEach(() => {
const config = new Config();
config.set({ htmlReporter: { baseDir } });
sut = new HtmlReporter(config);
testInjector.options.htmlReporter = { baseDir };
sut = testInjector.injector.injectClass(HtmlReporter);
return new EventPlayer(path.join('testResources', 'mathEvents'))
.replay(sut)
.then(() => sut.wrapUp());
Expand All @@ -27,14 +26,14 @@ describe('HtmlReporter with example math project', () => {
});

it('should output a log message with a link to the HTML report', () => {
expect(logger.info).to.have.been.calledWith(`Your report can be found at: ${fileUrl(baseDir + '/index.html')}`);
expect(testInjector.logger.info).to.have.been.calledWith(`Your report can be found at: ${fileUrl(baseDir + '/index.html')}`);
});

describe('when initiated a second time with empty events', () => {
beforeEach(() => {
const config = new Config();
config.set({ htmlReporter: { baseDir } });
sut = new HtmlReporter(config);
sut = testInjector.injector.injectClass(HtmlReporter);
sut.onAllSourceFilesRead([]);
sut.onAllMutantsTested([]);
return sut.wrapUp();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { expect } from 'chai';
import { Config } from 'stryker-api/config';
import HtmlReporter from '../../src/HtmlReporter';
import EventPlayer from '../helpers/EventPlayer';
import { readDirectoryTree } from '../helpers/fsHelpers';
import { testInjector } from '@stryker-mutator/test-helpers';

const REPORT_DIR = 'reports/mutation/stryker';

describe('Html report of stryker', () => {
let sut: HtmlReporter;

beforeEach(() => {
const config = new Config();
config.set({ htmlReporter: { baseDir: REPORT_DIR } });
sut = new HtmlReporter(config);
testInjector.options.htmlReporter = { baseDir: REPORT_DIR };
sut = testInjector.injector.injectClass(HtmlReporter);
return new EventPlayer('testResources/strykerEvents')
.replay(sut)
.then(() => sut.wrapUp());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import * as path from 'path';
import { expect } from 'chai';
import { Config } from 'stryker-api/config';
import EventPlayer from '../helpers/EventPlayer';
import HtmlReporter from '../../src/HtmlReporter';
import { readDirectoryTree } from '../helpers/fsHelpers';
import * as fs from 'fs';
import { testInjector } from '@stryker-mutator/test-helpers';

const REPORT_DIR = 'reports/mutation/singleFileInFolder';

describe('HtmlReporter single file in a folder', () => {
let sut: HtmlReporter;

beforeEach(() => {
const config = new Config();
config.set({ htmlReporter: { baseDir: REPORT_DIR } });
sut = new HtmlReporter(config);
testInjector.options.htmlReporter = { baseDir: REPORT_DIR };
sut = testInjector.injector.injectClass(HtmlReporter);
return new EventPlayer(path.join('testResources', 'singleFileInFolder'))
.replay(sut)
.then(() => sut.wrapUp());
Expand Down
3 changes: 2 additions & 1 deletion packages/stryker-html-reporter/test/ui/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import { browser } from 'protractor';
import { Config } from 'stryker-api/config';
import HtmlReporter from '../../src/HtmlReporter';
import EventPlayer from '../helpers/EventPlayer';
import { factory } from '@stryker-mutator/test-helpers';

export const baseDir = path.join(__dirname, '../../reports/mutation/uiTest');

before(() => {
browser.ignoreSynchronization = true;
const config = new Config();
config.set({ htmlReporter: { baseDir } });
const reporter = new HtmlReporter(config);
const reporter = new HtmlReporter(config, factory.logger());
return new EventPlayer('testResources/mathEvents')
.replay(reporter)
.then(() => reporter.wrapUp());
Expand Down
16 changes: 6 additions & 10 deletions packages/stryker-html-reporter/test/unit/HtmlReporter.spec.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,26 @@
import { normalize, join } from 'path';
import { expect } from 'chai';
import * as sinon from 'sinon';
import { Config } from 'stryker-api/config';
import * as util from '../../src/util';
import HtmlReporter from '../../src/HtmlReporter';
import { sourceFile, mutantResult, scoreResult } from '../helpers/producers';
import { testInjector } from '@stryker-mutator/test-helpers';

describe('HtmlReporter', () => {
let sandbox: sinon.SinonSandbox;
let copyFolderStub: sinon.SinonStub;
let writeFileStub: sinon.SinonStub;
let mkdirStub: sinon.SinonStub;
let deleteDirStub: sinon.SinonStub;
let sut: HtmlReporter;

beforeEach(() => {
sandbox = sinon.createSandbox();
copyFolderStub = sandbox.stub(util, 'copyFolder');
writeFileStub = sandbox.stub(util, 'writeFile');
deleteDirStub = sandbox.stub(util, 'deleteDir');
mkdirStub = sandbox.stub(util, 'mkdir');
sut = new HtmlReporter(new Config());
copyFolderStub = sinon.stub(util, 'copyFolder');
writeFileStub = sinon.stub(util, 'writeFile');
deleteDirStub = sinon.stub(util, 'deleteDir');
mkdirStub = sinon.stub(util, 'mkdir');
sut = testInjector.injector.injectClass(HtmlReporter);
});

afterEach(() => sandbox.restore());

describe('when in happy flow', () => {

beforeEach(() => {
Expand Down
2 changes: 2 additions & 0 deletions packages/stryker-html-reporter/tsconfig.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"es2015.symbol.wellknown"
],
"noImplicitReturns": false,
"strictPropertyInitialization": true,
"strictNullChecks": true,
"jsx": "react",
"jsxFactory": "typedHtml.createElement"
}
Expand Down
3 changes: 3 additions & 0 deletions packages/stryker-html-reporter/tsconfig.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
"references": [
{
"path": "./tsconfig.src.json"
},
{
"path": "../stryker-test-helpers/tsconfig.src.json"
}
]
}
22 changes: 22 additions & 0 deletions packages/stryker/src/di/createPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Plugin, PluginKind, PluginContexts, Plugins, PluginInterfaces, FactoryPlugin, ClassPlugin } from 'stryker-api/plugin';
import { Injector, InjectionToken } from 'typed-inject';

export function createPlugin<TPluginKind extends PluginKind>(kind: TPluginKind, plugin: Plugins[TPluginKind], injector: Injector<PluginContexts[TPluginKind]>):
PluginInterfaces[TPluginKind] {
if (isFactoryPlugin(plugin)) {
return injector.injectFunction(plugin.factory);
} else if (isClassPlugin(plugin)) {
return injector.injectClass(plugin.injectableClass);
} else {
throw new Error(`Plugin "${kind}:${plugin.name}" could not be created, missing "factory" or "injectableClass" property.`);
}
}

function isFactoryPlugin<TPluginKind extends PluginKind>(plugin: Plugin<TPluginKind, InjectionToken<PluginContexts[TPluginKind]>[]>):
plugin is FactoryPlugin<TPluginKind, InjectionToken<PluginContexts[TPluginKind]>[]> {
return !!(plugin as FactoryPlugin<TPluginKind, InjectionToken<PluginContexts[TPluginKind]>[]>).factory;
}
function isClassPlugin<TPluginKind extends PluginKind>(plugin: Plugin<TPluginKind, InjectionToken<PluginContexts[TPluginKind]>[]>):
plugin is ClassPlugin<TPluginKind, InjectionToken<PluginContexts[TPluginKind]>[]> {
return !!(plugin as ClassPlugin<TPluginKind, InjectionToken<PluginContexts[TPluginKind]>[]>).injectableClass;
}
36 changes: 36 additions & 0 deletions packages/stryker/test/unit/di/createPlugin.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { createPlugin } from '../../../src/di/createPlugin';
import { PluginKind, FactoryPlugin, ClassPlugin } from 'stryker-api/plugin';
import { factory, testInjector } from '@stryker-mutator/test-helpers';
import { expect } from 'chai';

describe('createPlugin', () => {
it('should create a FactoryPlugin using it\'s factory method', () => {
const expectedReporter = factory.reporter();
const plugin: FactoryPlugin<PluginKind.Reporter, []> = {
kind: PluginKind.Reporter,
name: 'fooReporter',
factory() {
return expectedReporter;
}
};
const actualReporter = createPlugin(PluginKind.Reporter, plugin, testInjector.injector);
expect(actualReporter).eq(expectedReporter);
});

it('should create a ClassPlugin using it\'s constructor', () => {
class FooReporter {
}
const plugin: ClassPlugin<PluginKind.Reporter, []> = {
injectableClass: FooReporter,
kind: PluginKind.Reporter,
name: 'fooReporter'
};
const actualReporter = createPlugin(PluginKind.Reporter, plugin, testInjector.injector);
expect(actualReporter).instanceOf(FooReporter);
});

it('should throw if plugin is not recognized', () => {
expect(() => createPlugin(PluginKind.Reporter, { name: 'foo' } as any, testInjector.injector))
.throws('Plugin "Reporter:foo" could not be created, missing "factory" or "injectableClass" property.');
});
});

0 comments on commit 66d65f7

Please sign in to comment.