Open index23 opened 9 months ago
This usually happens when source maps are missing. v8-to-istanbul
simply marks whole file as covered then. I haven't yet looked into the reproduction case, but that will likely reveal in issue with source maps.
Does this happen with latest version of @vitejs/plugin-vue
?
@AriPerkkio Yes, it is same with latest version "@vitejs/plugin-vue": "^5.0.3"
. I not sure whether this happens, because I've tested some other cases where coverage for template works fine. For example, I have v-if
and v-else
in template and have test for v-if
, v-else
part of template is correct marked as uncovered.
<template v-if="hasMessage">
<h1 class="green">{{ msg }}</h1>
</template>
<template v-else>
<h1 class="yellow">Default message</h1>
</template>
and tests...
describe('HelloWorld', () => {
it('renders properly', () => {
const wrapper = mount(HelloWorld, {props:{msg:'Yes did it!'}})
expect(wrapper.find('.greetings').exists()).toBe(true)
})
})
coverage...
Vue parser was updated in the latest minor version - maybe it's a regression on Vue's part?
Source maps are present in this case. If you add some unreached code into <script>
tags, it will be shown correctly.
The reason why line 17's <h1>...</h1>
is shown covered, is because Vue compiler will generate the class="green"
part on another line again on the compiled code - and it's included in source maps.
The line 24 of generated code is executed by Node as it's in top level scope. It's source maps points it to line 17 in sources -> it's covered. You can see the line mapped here by clicking line 24 on "Generated" side: https://ariperkkio.github.io/source-map-debugger/?s=N4Ig5gpg...
In the V8 report this line is marked as covered as it's executed. If Vue excluded that line from source maps, or added /* v8 ignore next */
(and Istanbul equivalent one) on top of it, the uncovered line would show correctly. There's another part in the compiled code which is uncovered in V8 report that would make it show uncovered in final report:
This is similar issue as Svelte has: https://github.com/sveltejs/svelte/issues/7824.
If Vue and Svelte compilers had something like Babel's auxiliaryCommentBefore
we could use it like Jest does: https://github.com/jestjs/jest/blob/8c78a08c6e75c1a0c4447aa8a0c61ad9e395e16f/e2e/coverage-transform-instrumented/preprocessor.js#L25
So to summarize: Issue is in Vue compiler. It's generated code does not work with code coverage tools.
@AriPerkkio Thank you for explanation. Just summarize to be more clear to me. Coverage tool marks those lines as covered because it is hoisted in top level scope after compilation. Am I right?
And I have another question. Do you find any solution? I think that it is not minor issue. For example, process on my project is based on code coverage, among other tools.
Just summarize to be more clear to me. Coverage tool marks those lines as covered because it is hoisted in top level scope after compilation. Am I right?
Yep, but it looks like there is also another issue. Even if the hoisted variable was dropped out of the source maps, this line would still mark the v-if
line as covered. Both sides of the $setup.hasMessage
ternary are pointing to that line -> it's always marked as covered.
Link https://evanw.github.io/source-map-visualization/#MjQ5....
And I have another question. Do you find any solution?
There are no good work-arounds for this. Vue compiler needs to adjust their source maps.
It's clear to me. I am ready to go further. We have two issues here:
I would start with resolving second issue...
@AriPerkkio Is it ok to link your noted links, if I open issue to another repo?
I think this is an issue of Vue compiler, not the vite-plugin-vue itself. While their source maps are correct right now, they are not compatible with code coverage tools. It's similar issue as Svelte has: https://github.com/sveltejs/svelte/issues/7824. Feel free to link this issue in other ones.
Downgrading from Vue 3.4 to 3.3 fixes these issues.
Downgrading from Vue 3.4 to 3.3 fixes these issues.
Thanks for answer, but unfortunately, doesn't work for me. @boboldehampsink Can you send me your lock file? Maybe it solves issue with correct version from another dependency.
I used
"resolutions": {
"vue": "~3.3.0"
},
and ran yarn upgrade
Nope, same issue...
The reasons could be:
1, The soucemap is inaccurate, especially for Vue which map JS code to HTML code.
2, The conversion of the soucemap is inaccurate. For example, when the target position is between two mappings, v8-to-istanbul
is unable to find the precise position, see here
We found this issue previously but have been unable to solve it until we implemented our own converter.
You could try it simply using vitest customProviderModule
1, npm install vitest-monocart-coverage
2, vitest.config.ts
import { fileURLToPath } from 'node:url'
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
import viteConfig from './vite.config'
export default mergeConfig(
viteConfig,
// @ts-ignore
defineConfig({
test: {
environment: 'jsdom',
exclude: [...configDefaults.exclude, 'e2e/*'],
root: fileURLToPath(new URL('./', import.meta.url)),
coverage: {
// @ts-ignore
reporter: ['v8', 'lcov'],
provider: 'custom',
customProviderModule: 'vitest-monocart-coverage'
}
}
})
)
1, The soucemap is inaccurate, especially for Vue which map JS code to HTML code.
There's link to the source maps above https://github.com/vitest-dev/vitest/issues/4993#issuecomment-1904344364. It is accurate.
2, The conversion of the soucemap is inaccurate.
What do you mean?
The root cause is that this line is actually covered. In the transpiled code it's in two positions: once on top level and once inside the conditional call. The top level code is always executed. I'm not sure what of rule could be used to exclude the top level code from coverage report.
Please see my screenshot, there are two mappings:
start mapping: gm1
(generated mapping 1) -> om1
(original mapping 1)
end mapping: gm2
(generated mapping 2) -> om2
(original mapping 2)
the coverage start position is gp
(generated position), and we need to map it to op
(original position), So, where should the position of op
be?
We can only say that its position is between om1
and om2
, because gp
is between gm1
and gm2
. for this case, it should be om2
or om2+1
.
For Vue, JS is unable to precisely match HTML.
Same problem for end position:
Right, but what about code that's on the top level? That's always covering the line. I guess you would need AST analysis to exclude that part.
Firstly, the case top level
you indicated will be ignored, because it is not a block of coverage.
It doesn't provide any coverage information for this key/value class: "green"
.
And, the logic of V8 coverage should be as follows:
So, when <h1>
is uncovered, It's impossible for class="green"
to be covered.
What do you think?
Here's the V8 report and the contents that Vitest runs for this file:
Should v8-to-istanbul
ignore the first two entries of coverage.json
here? Currently anything inside that is marked as covered, including the class: "green"
part. Isn't logic something like "Mark everything inside a covered block as covered. Mark uncovered blocks inside that block as uncovered"?
I think it should be:
Yep, exactly. So the first two entries of the V8 report will mark the class: "green"
part as covered.
Yes, at the beginning, the first two entries indeed mark class: "green"
as covered, but if later there is block like <h1>
with a count of 0, it will re-mark class: "green"
as uncovered. Uncovered has a higher priority than covered.
see https://github.com/istanbuljs/v8-to-istanbul/blob/master/lib/v8-to-istanbul.js#L198-L205
default line count is 1: https://github.com/istanbuljs/v8-to-istanbul/blob/master/lib/line.js#L16
but if later there is block like
<h1>
with a count of 0
Sure, but there is no such block. That's why it's covered. The class: "green"
is between offsets 1931 - 1981
in the transpiled code. From the coverage.json
above you can see that there is no block that marks it uncovered.
this one is <h1>
and is uncovered in your coverage.json
{ "startOffset": 2914, "endOffset": 3112, "count": 0 }
@cenfun could you file a bug report to v8-to-istanbul
about this? To me it sounds like you've found a more precise source map tracing than source-map
or @jridgewell/trace-mapping
.
There is already a bug here.
At that time, we were also using v8-to-istanbul
as the converter, but found the above issue. we couldn't wait for it to be solved, so I had to solve it myself, which is to implement my own converter.
Until today, I still find some similar issues releated to v8-to-istanbul
, so just share some of my experiences with you.
For example the comments issue:
Describe the bug
v8 coverage provider report 100% statement coverage for uncovered v-if inside template
HelloWorld.vue
HelloWorld.spec.ts
In this example we test component without props so template with h1 tag will not render but provider report that coverage for that lines are 100% covered, as well as for entire file.
Reproduction
Link to repo where you can reproduce issue, https://github.com/index23/vue-test-template-coverage
Steps to reproduce:
npm run test:unit
coverage
directorycoverage/lcov-report/index.html
System Info
Used Package Manager
npm
Validations