-
Notifications
You must be signed in to change notification settings - Fork 242
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
Conversation
There was a problem hiding this 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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 */ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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 case
s
There was a problem hiding this comment.
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]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const
please :)
There was a problem hiding this comment.
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']; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const
please
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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', () => { |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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 .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this 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; } => { |
There was a problem hiding this comment.
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?
Is this ready to be reviewed? |
Allright, one integration test is done. Do we also want a project with jest and typescript @nicojs ? |
Hmm. What do you think? Couldn't hurt of course, but does it test something new? |
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", |
There was a problem hiding this comment.
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
The package itself does have tests to see if the code works though. |
There was a problem hiding this 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; }; |
There was a problem hiding this comment.
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[] = []; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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])); |
There was a problem hiding this comment.
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']; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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 👍
@nicojs Thanks for the review! I've refactored the code. |
There was a problem hiding this 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
I'll try to make them less flakey later this week 👍 |
I have updated the tests with my latest commit. |
I see you mocked away the mutators for unittests. Great. Unittests are a little wet (vs dry) for my taste, but ok to merge |
@nicojs Wet as in the opposite of DRY (don't repeat yourself) or wet as in wet clothes? 🤔 |
Wet as opposite of DRY indeed. I like my unit tests a little DAMP |
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 yourmutator
property to'vue'
.Fixes #579