Open kal-rein opened 4 months ago
After some debugging I've confirmed the error is due a mutation introduced by Stryker in the source code, before the ivy compiler parses and bind the template with the controller.
When ran through Stryker the original component is modified to introduce the mutations as expected, this includes the object with the input options which contains the alias. Because of the mutations introduced there, the compiler seems to be unable to parse it and fails leading the tests to fail as well.
I believe this wasn't a problem using the annotations because they are excluded from being mutated here. This means aliased inputs and outputs using annotations are safe from being modified, but their recently introduced signal counterparts, input and model, are not since they use an object parameter inside the function call.
Now, I'm not sure where to add this exclusion since it is only necessary to support Angular, my best guess is the karma-runner package, although there is recent support for jest too. In the meantime I've made a custom ignore plugin to avoid introducing mutations in objects inside functions named input or model so Stryker can be used with these, it can be seen in action here.
import { declareValuePlugin, PluginKind } from '@stryker-mutator/api/plugin';
export const strykerPlugins = [declareValuePlugin(PluginKind.Ignore, 'angular.signal-model-input-options', {
shouldIgnore(path) {
const inputOrModelExpression = path.findParent((path) =>
path.isCallExpression() &&
path.node.callee.type === 'Identifier' &&
(path.node.callee.name === 'input' || path.node.callee.name === 'model'),
);
if (path.isObjectExpression() && inputOrModelExpression != null) {
return 'Angular signal or model input options cannot be mutated as that causes issues with the ivy compiler.';
}
},
})];
Wow, great find and workaround, @kal-rein. I'm very glad to see you were able to use the new 'ignore-plugin' type. These kinds of use cases were exactly why I added it.
StrykerJS should ideally come with an "angular"
ignore-plugin and configure this automatically when users choose angular
in npm init stryker
. @kal-rein, would you feel comfortable contributing this feature?
I can give it a try in the upcoming days and implement this workaround as a plugin when the option karma.projectType
equals angular-cli
. Although, that would only fix the problem when using Karma, if someone were to use Jest I'm pretty sure they will experience this issue, maybe a warning can be added in the Jest documentation so users can add it manually.
Also, I'm not experienced with AST and there is a problem that I don't know how to address in my workaround. If the user were to define another function named input
or model
which accepts an object as one of the parameters, the plugin will force Stryker to ignore said object when called. Is there any way to know the source of an identifier using AST?
I can give it a try in the upcoming days and implement this workaround as a plugin when the option
karma.projectType
equalsangular-cli
. Although, that would only fix the problem when using Karma, if someone were to use Jest I'm pretty sure they will experience this issue, maybe a warning can be added in the Jest documentation so users can add it manually.
I see that this ignorer plugin would be shipped with @stryker-mutator/core
. So you could configure it with
{
"ignorers": ["angular"]
}
So configure it for jest would be the same.
Btw, don't focus on jest too much; as of today, jest support is still experimental, see https://stryker-mutator.io/docs/stryker-js/guides/angular/#angular-with-experimental-jest-support.
Also, I'm not experienced with AST and there is a problem that I don't know how to address in my workaround. If the user were to define another function named
input
ormodel
which accepts an object as one of the parameters, the plugin will force Stryker to ignore said object when called. Is there any way to know the source of an identifier using AST?
This plugin would only be active for Angular projects, so I think model()
, input()
, and output()
, etc are most probably from Angular. That being said, I think you should be able to identify that these methods are being called from a class property in a class decorated with a @Component
or @Directive
decorator. I think those limitations are pretty valid, we can always relax them if users experience issues.
See https://astexplorer.net/#/gist/76889bb1091d862b334873658a938f4f/5802b6615a5b6b01e908f32553afc15e1ad70708 for an example.
Summary
While testing an Angular component or directive, if an input is aliased and uses the new signal inputs introduced in Angular 16 Stryker fails to run any test, even those outside the affected element. But, if the same element is tested using an annotated input instead of a signal input Stryker runs all tests correctly, including the affected element.
This is a strange behavior because both inputs works correctly while testing directly using
ng test
.I've prepared a repository where the problem can be reproduced here. The latest commit includes both inputs and fails to run Stryker while this commit includes only the annotated input and runs correctly.
Stryker config
Test runner config
Stryker environment
Test runner environment
Your Environment
Add stryker.log
stryker.log