Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Vue): add Vue mutator #723

Merged
merged 33 commits into from
Aug 16, 2018
Merged

feat(Vue): add Vue mutator #723

merged 33 commits into from
Aug 16, 2018

Conversation

simondel
Copy link
Member

@simondel simondel commented Apr 26, 2018

Adds a new stryker plugin: stryker-vue-mutator. This plugin requires additional mutator plugins to function, as specified in the package's README. When using this plugin, you should set your mutator property to 'vue'.

Fixes #579

@ghost ghost assigned simondel Apr 26, 2018
@ghost ghost added the 🔎 Needs review label Apr 26, 2018
@simondel simondel requested a review from nicojs May 11, 2018 08:18
@simondel simondel changed the title WIP: feat(Vue): add Vue mutator feat(Vue): add Vue mutator May 11, 2018
Copy link
Member

@nicojs nicojs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Just have some remarks on the implementation details.


# Stryker Vue mutator

A mutator that supports JavaScript for [Stryker](https://stryker-mutator.io), the mutation testing framework for JavaScript and friends.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still needs to be updated. We should make it clear that this plugin adds support for *.vue files.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

},
"peerDependencies": {
"stryker-api": "^0.17.0",
"vue-template-compiler": "^2.0.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This peerDep should be removed, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This plugin is used to extract the template code from the vue file. We need it in order to run but we need the version that is exactly the same as the version of vue itself. Else the plugin won't work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, in that case, please remove the dependency on "vue-template-compiler"

const script = compiler.parseComponent(file.textContent).script;
const mutator = this.getVueScriptMutator(script);
const vueFile = new File(
file.name + '.js', /* TODO: Remove this once all mutators understand that vue files are OK to mutate */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should instead choose the extension based on the script tag. This is the most logical step IMHO.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

constructor(private config: Config) { }

mutate(inputFiles: File[]): Mutant[] {
this.initializeMutators();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we move this to the constructor. That way it works nicely with TS 2.7 --strictPropertyInitialization

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


private getVueScriptMutator(script: any): Mutator {
const lang: string | undefined = script.attrs.lang;
let mutatorName: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can make it a const if we move the case switch to a separate method with return statements in the cases

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer we don't do this to keep the readability that we have right now

throw new Error(`Found unsupported language attribute 'lang="${lang}"' on a <script> block.`);
}

let mutator = this.mutators[mutatorName];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const please :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

}

private getMutator(file: File): Mutator {
let mutator = this.mutators['typescript'] || this.mutators['javascript'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const please

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


const mutants = sut.mutate(files);

expect(mutants.length).to.equal(8);
Copy link
Member

@nicojs nicojs May 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is pretty flaky, right? Whenever we update our js mutator, this might fail. Can we verify that some mutants that you expect are there?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I now test on the specific types of mutators that should exist

expect(() => sut.mutate(files)).throws(`The 'javascript' mutator is required to mutate a <script> block but it was not found. Please read the README of this package for information on configuration.`);
});

describe('when the first file is not a Vue file', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this describe is not helping with the explanation of what is tested in the it. Could we move the it to the top level and remove this describe?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

return mutants;
}

private initializeMutators() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this method to a separate file? That way the unit tests can be independent of the current MutatorFactory way of working. We will remove the mutator factory as part of #667 .

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@ghost ghost assigned nicojs May 11, 2018
Copy link
Member

@nicojs nicojs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome. 1 last remark.

import { Mutator, MutatorFactory } from 'stryker-api/mutant';
import { Config } from 'stryker-api/config';

const generateMutators = (config: Config): { [name: string]: Mutator; } => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name... In what way is it actually generating mutators? More like discoverMutators right?

@nicojs
Copy link
Member

nicojs commented Aug 4, 2018

Is this ready to be reviewed?

@simondel
Copy link
Member Author

simondel commented Aug 4, 2018

Allright, one integration test is done. Do we also want a project with jest and typescript @nicojs ?

@nicojs
Copy link
Member

nicojs commented Aug 4, 2018

Hmm. What do you think? Couldn't hurt of course, but does it test something new?

@simondel
Copy link
Member Author

simondel commented Aug 4, 2018

It redirects the code to a different path because all the Vue mutator does is send code to the correct mutator.

"vue-template-compiler": "^2.0.0"
},
"peerDependencies": {
"stryker-api": "^0.17.0",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has to be updated

@simondel
Copy link
Member Author

simondel commented Aug 4, 2018

The package itself does have tests to see if the code works though.

Copy link
Member

@nicojs nicojs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work so far. Let's keep the momentum going 👍 .

I've got some overall small remarks.

const compiler = require('vue-template-compiler');

export default class VueMutator implements Mutator {
private mutators: { [name: string]: Mutator; };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this readonly?

}

mutate(inputFiles: File[]): Mutant[] {
let mutants: Mutant[] = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we declare it as a const to prevent incidental re-initialization?

mutant.range[0] += script.start;
mutant.range[1] += script.start;
});
mutants = mutants.concat(vueMutants);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use mutants.push(...vueMutants)

mutants = mutants.concat(vueMutants);
} else {
const mutator = this.getMutator(file);
mutants = mutants.concat(mutator.mutate([file]));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use mutants.push(...vueMutants)

}

private getMutator(file: File): Mutator {
const mutator = this.mutators['typescript'] || this.mutators['javascript'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels strange to me. Will the typescript mutator always work for javascript as well? Can we create const strings or a string enum for 'typescript' and 'javascript' here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, couldn't we fail faster? As soon as the mutate method is called and no mutators are registered for example?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll save the string names 'typescript' and 'javascript' somewhere. I think the typescript mutator should always be able to process javascript code. Javascript is typescript after all, right?

We could fail faster, but we would still have to check if the correct mutator is available for Vue code. For instance, we can't just say 'if either of the two mutators exists, all is fine' because the JavaScript mutator may be present while there is a Vue file with TypeScript code.

import VueMutator from '../../src/VueMutator';
require('../../src/index');
require('stryker-javascript-mutator');
require('stryker-typescript');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change this to:

import '../../src/index';
import 'stryker-javascript-mutator';
import 'stryker-typescript';

});

afterEach(() => {
sandbox.restore();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed. It is done in /stryker-vue-mutator/test/helpers/initSinon.ts


it('should pass regular files to the TypeScript mutator, even if the JavaScript mutator is installed', () => {
const stubJavaScriptMutator = sandbox.createStubInstance<Mutator>(VueMutator);
const stubTypeScriptMutator = sandbox.createStubInstance<Mutator>(VueMutator);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we create these mocks/stubs once in before each? It seems that you repeat yourself here. We could also opt for an arrangeMutators() helper method of some kind.

@@ -0,0 +1,327 @@
import { expect } from 'chai';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a big fan of the black-boxy unit tests here 👍

@simondel
Copy link
Member Author

@nicojs Thanks for the review! I've refactored the code.

Copy link
Member

@nicojs nicojs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. This comment is still valid:

This test is pretty flaky, right? Whenever we update our js mutator, this might fail. Can we verify that some mutants that you expect are there?

But that's not a blocker for me. Feel free to merge when green

@simondel
Copy link
Member Author

I'll try to make them less flakey later this week 👍

@simondel
Copy link
Member Author

I have updated the tests with my latest commit.

@nicojs
Copy link
Member

nicojs commented Aug 16, 2018

I see you mocked away the mutators for unittests. Great. Unittests are a little wet (vs dry) for my taste, but ok to merge

@simondel
Copy link
Member Author

@nicojs Wet as in the opposite of DRY (don't repeat yourself) or wet as in wet clothes? 🤔

@simondel simondel merged commit bc28fb6 into master Aug 16, 2018
@ghost ghost removed the 🔎 Needs review label Aug 16, 2018
@simondel simondel deleted the vue-mutator branch August 16, 2018 11:24
@nicojs
Copy link
Member

nicojs commented Aug 27, 2018

Wet as opposite of DRY indeed. I like my unit tests a little DAMP

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants