Skip to content

Commit 821126f

Browse files
authoredJun 6, 2023
feat!: add @vitest/coverage-v8 package (#3339)
1 parent 19ecc6c commit 821126f

File tree

29 files changed

+4076
-105
lines changed

29 files changed

+4076
-105
lines changed
 

‎README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ A blazing fast unit test framework powered by Vite.
3535
- [Jest Snapshot](https://jestjs.io/docs/snapshot-testing)
3636
- [Chai](https://www.chaijs.com/) built-in for assertions, with [Jest expect](https://jestjs.io/docs/expect) compatible APIs.
3737
- [Smart & instant watch mode](https://vitest.dev/guide/features.html#watch-mode), like HMR for tests!
38-
- [Native code coverage](https://vitest.dev/guide/features.html#coverage) via [c8](https://github.com/bcoe/c8) or [`istanbul`](https://istanbul.js.org/).
38+
- [Native code coverage](https://vitest.dev/guide/features.html#coverage) via [`v8`](https://v8.dev/blog/javascript-code-coverage) or [`istanbul`](https://istanbul.js.org/).
3939
- [Tinyspy](https://github.com/tinylibs/tinyspy) built-in for mocking, stubbing, and spies.
4040
- [JSDOM](https://github.com/jsdom/jsdom) and [happy-dom](https://github.com/capricorn86/happy-dom) for DOM and browser API mocking
4141
- Components testing ([Vue](./examples/vue), [React](./examples/react), [Svelte](./examples/svelte), [Lit](./examples/lit), [Vitesse](./examples/vitesse))

‎docs/.vitepress/components/FeaturesList.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<ListItem><a target="_blank" href="https://www.chaijs.com/" rel="noopener noreferrer">Chai</a> built-in for assertions + <a target="_blank" href="https://jestjs.io/docs/expect" rel="noopener noreferrer">Jest expect</a> compatible APIs</ListItem>
2323
<ListItem><a target="_blank" href="https://github.com/tinylibs/tinyspy" rel="noopener noreferrer">Tinyspy</a> built-in for mocking</ListItem>
2424
<ListItem><a target="_blank" href="https://github.com/capricorn86/happy-dom" rel="noopener noreferrer">happy-dom</a> or <a target="_blank" href="https://github.com/jsdom/jsdom" rel="noopener noreferrer">jsdom</a> for DOM mocking</ListItem>
25-
<ListItem>Code coverage via <a target="_blank" href="https://github.com/bcoe/c8" rel="noopener noreferrer">c8</a> or <a target="_blank" href="https://istanbul.js.org/" rel="noopener noreferrer">istanbul</a></ListItem>
25+
<ListItem>Code coverage via <a target="_blank" href="https://v8.dev/blog/javascript-code-coverage" rel="noopener noreferrer">v8</a> or <a target="_blank" href="https://istanbul.js.org/" rel="noopener noreferrer">istanbul</a></ListItem>
2626
<ListItem>Rust-like <a href="/guide/in-source">in-source testing</a></ListItem>
2727
<ListItem>Type Testing via <a target="_blank" href="https://github.com/mmkal/expect-type" rel="noopener noreferrer">expect-type</a></ListItem>
2828
</ul>

‎docs/advanced/api.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ await vitest?.close()
1919
`startVitest` function returns `Vitest` instance if tests can be started. It returns `undefined`, if one of the following occurs:
2020
2121
- Vitest didn't find "vite" package (usually installed with Vitest)
22-
- If coverage is enabled and run mode is "test", but the coverage package is not installed (`@vitest/coverage-c8` or `@vitest/coverage-istanbul`)
22+
- If coverage is enabled and run mode is "test", but the coverage package is not installed (`@vitest/coverage-v8` or `@vitest/coverage-istanbul`)
2323
- If the environment package is not installed (`jsdom`/`happy-dom`/`@edge-runtime/vm`)
2424
2525
If `undefined` is returned or tests failed during the run, Vitest sets `process.exitCode` to `1`.

‎docs/config/index.md

+26-22
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,11 @@ Isolate environment for each test file. Does not work if you disable [`--threads
665665

666666
### coverage<NonProjectOption />
667667

668-
You can use [`c8`](https://github.com/bcoe/c8), [`istanbul`](https://istanbul.js.org/) or [a custom coverage solution](/guide/coverage#custom-coverage-provider) for coverage collection.
668+
You can use [`v8`](https://v8.dev/blog/javascript-code-coverage), [`istanbul`](https://istanbul.js.org/) or [a custom coverage solution](/guide/coverage#custom-coverage-provider) for coverage collection.
669+
670+
::: info
671+
The `c8` provider is being replaced by the `v8` provider. It will be deprecated in the next major version.
672+
:::
669673

670674
You can provide coverage options to CLI with dot notation:
671675

@@ -679,8 +683,8 @@ If you are using coverage options with dot notation, don't forget to specify `--
679683

680684
#### coverage.provider
681685

682-
- **Type:** `'c8' | 'istanbul' | 'custom'`
683-
- **Default:** `'c8'`
686+
- **Type:** `'c8' | 'v8' | 'istanbul' | 'custom'`
687+
- **Default:** `'v8'`
684688
- **CLI:** `--coverage.provider=<provider>`
685689

686690
Use `provider` to select the tool for coverage collection.
@@ -689,7 +693,7 @@ Use `provider` to select the tool for coverage collection.
689693

690694
- **Type:** `boolean`
691695
- **Default:** `false`
692-
- **Available for providers:** `'c8' | 'istanbul'`
696+
- **Available for providers:** `'c8' | 'v8' | 'istanbul'`
693697
- **CLI:** `--coverage.enabled`, `--coverage.enabled=false`
694698

695699
Enables coverage collection. Can be overridden using `--coverage` CLI option.
@@ -698,7 +702,7 @@ Enables coverage collection. Can be overridden using `--coverage` CLI option.
698702

699703
- **Type:** `string[]`
700704
- **Default:** `['**']`
701-
- **Available for providers:** `'c8' | 'istanbul'`
705+
- **Available for providers:** `'c8' | 'v8' | 'istanbul'`
702706
- **CLI:** `--coverage.include=<path>`, `--coverage.include=<path1> --coverage.include=<path2>`
703707

704708
List of files included in coverage as glob patterns
@@ -707,7 +711,7 @@ List of files included in coverage as glob patterns
707711

708712
- **Type:** `string | string[]`
709713
- **Default:** `['.js', '.cjs', '.mjs', '.ts', '.mts', '.cts', '.tsx', '.jsx', '.vue', '.svelte']`
710-
- **Available for providers:** `'c8' | 'istanbul'`
714+
- **Available for providers:** `'c8' | 'v8' | 'istanbul'`
711715
- **CLI:** `--coverage.extension=<extension>`, `--coverage.extension=<extension1> --coverage.extension=<extension2>`
712716

713717
#### coverage.exclude
@@ -729,7 +733,7 @@ List of files included in coverage as glob patterns
729733
'**/.{eslint,mocha,prettier}rc.{?(c|m)js,yml}',
730734
]
731735
```
732-
- **Available for providers:** `'c8' | 'istanbul'`
736+
- **Available for providers:** `'c8' | 'v8' | 'istanbul'`
733737
- **CLI:** `--coverage.exclude=<path>`, `--coverage.exclude=<path1> --coverage.exclude=<path2>`
734738

735739
List of files excluded from coverage as glob patterns.
@@ -738,7 +742,7 @@ List of files excluded from coverage as glob patterns.
738742

739743
- **Type:** `boolean`
740744
- **Default:** `false`
741-
- **Available for providers:** `'c8' | 'istanbul'`
745+
- **Available for providers:** `'c8' | 'v8' | 'istanbul'`
742746
- **CLI:** `--coverage.all`, `--coverage.all=false`
743747

744748
Whether to include all files, including the untested ones into report.
@@ -747,7 +751,7 @@ Whether to include all files, including the untested ones into report.
747751

748752
- **Type:** `boolean`
749753
- **Default:** `true`
750-
- **Available for providers:** `'c8' | 'istanbul'`
754+
- **Available for providers:** `'c8' | 'v8' | 'istanbul'`
751755
- **CLI:** `--coverage.clean`, `--coverage.clean=false`
752756

753757
Clean coverage results before running tests
@@ -756,7 +760,7 @@ Clean coverage results before running tests
756760

757761
- **Type:** `boolean`
758762
- **Default:** `true`
759-
- **Available for providers:** `'c8' | 'istanbul'`
763+
- **Available for providers:** `'c8' | 'v8' | 'istanbul'`
760764
- **CLI:** `--coverage.cleanOnRerun`, `--coverage.cleanOnRerun=false`
761765

762766
Clean coverage report on watch rerun
@@ -765,7 +769,7 @@ Clean coverage report on watch rerun
765769

766770
- **Type:** `string`
767771
- **Default:** `'./coverage'`
768-
- **Available for providers:** `'c8' | 'istanbul'`
772+
- **Available for providers:** `'c8' | 'v8' | 'istanbul'`
769773
- **CLI:** `--coverage.reportsDirectory=<path>`
770774

771775
Directory to write coverage report to.
@@ -774,7 +778,7 @@ Directory to write coverage report to.
774778

775779
- **Type:** `string | string[] | [string, {}][]`
776780
- **Default:** `['text', 'html', 'clover', 'json']`
777-
- **Available for providers:** `'c8' | 'istanbul'`
781+
- **Available for providers:** `'c8' | 'v8' | 'istanbul'`
778782
- **CLI:** `--coverage.reporter=<reporter>`, `--coverage.reporter=<reporter1> --coverage.reporter=<reporter2>`
779783

780784
Coverage reporters to use. See [istanbul documentation](https://istanbul.js.org/docs/advanced/alternative-reporters/) for detailed list of all reporters. See [`@types/istanbul-reporter`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/276d95e4304b3670eaf6e8e5a7ea9e265a14e338/types/istanbul-reports/index.d.ts) for details about reporter specific options.
@@ -801,7 +805,7 @@ Since Vitest 0.31.0, you can check your coverage report in Vitest UI: check [Vit
801805

802806
- **Type:** `boolean`
803807
- **Default:** `true`
804-
- **Available for providers:** `'c8' | 'istanbul'`
808+
- **Available for providers:** `'c8' | 'v8' | 'istanbul'`
805809
- **CLI:** `--coverage.reportOnFailure`, `--coverage.reportOnFailure=false`
806810
- **Version:** Since Vitest 0.31.2
807811

@@ -811,7 +815,7 @@ Generate coverage report even when tests fail.
811815

812816
- **Type:** `boolean`
813817
- **Default:** `false`
814-
- **Available for providers:** `'c8' | 'istanbul'`
818+
- **Available for providers:** `'c8' | 'v8' | 'istanbul'`
815819
- **CLI:** `--coverage.skipFull`, `--coverage.skipFull=false`
816820

817821
Do not show files with 100% statement, branch, and function coverage.
@@ -820,7 +824,7 @@ Do not show files with 100% statement, branch, and function coverage.
820824

821825
- **Type:** `boolean`
822826
- **Default:** `false`
823-
- **Available for providers:** `'c8' | 'istanbul'`
827+
- **Available for providers:** `'c8' | 'v8' | 'istanbul'`
824828
- **CLI:** `--coverage.perFile`, `--coverage.perFile=false`
825829

826830
Check thresholds per file.
@@ -830,7 +834,7 @@ See `lines`, `functions`, `branches` and `statements` for the actual thresholds.
830834

831835
- **Type:** `boolean`
832836
- **Default:** `false`
833-
- **Available for providers:** `'c8' | 'istanbul'`
837+
- **Available for providers:** `'c8' | 'v8' | 'istanbul'`
834838
- **CLI:** `--coverage.thresholdAutoUpdate=<boolean>`
835839

836840
Update threshold values `lines`, `functions`, `branches` and `statements` to configuration file when current coverage is above the configured thresholds.
@@ -839,7 +843,7 @@ This option helps to maintain thresholds when coverage is improved.
839843
#### coverage.lines
840844

841845
- **Type:** `number`
842-
- **Available for providers:** `'c8' | 'istanbul'`
846+
- **Available for providers:** `'c8' | 'v8' | 'istanbul'`
843847
- **CLI:** `--coverage.lines=<number>`
844848

845849
Threshold for lines.
@@ -848,7 +852,7 @@ See [istanbul documentation](https://github.com/istanbuljs/nyc#coverage-threshol
848852
#### coverage.functions
849853

850854
- **Type:** `number`
851-
- **Available for providers:** `'c8' | 'istanbul'`
855+
- **Available for providers:** `'c8' | 'v8' | 'istanbul'`
852856
- **CLI:** `--coverage.functions=<number>`
853857

854858
Threshold for functions.
@@ -857,7 +861,7 @@ See [istanbul documentation](https://github.com/istanbuljs/nyc#coverage-threshol
857861
#### coverage.branches
858862

859863
- **Type:** `number`
860-
- **Available for providers:** `'c8' | 'istanbul'`
864+
- **Available for providers:** `'c8' | 'v8' | 'istanbul'`
861865
- **CLI:** `--coverage.branches=<number>`
862866

863867
Threshold for branches.
@@ -866,7 +870,7 @@ See [istanbul documentation](https://github.com/istanbuljs/nyc#coverage-threshol
866870
#### coverage.statements
867871

868872
- **Type:** `number`
869-
- **Available for providers:** `'c8' | 'istanbul'`
873+
- **Available for providers:** `'c8' | 'v8' | 'istanbul'`
870874
- **CLI:** `--coverage.statements=<number>`
871875

872876
Threshold for statements.
@@ -903,7 +907,7 @@ Specifies the directories that are used when `--all` is enabled.
903907

904908
- **Type:** `boolean`
905909
- **Default:** `false`
906-
- **Available for providers:** `'c8'`
910+
- **Available for providers:** `'c8' | 'v8'`
907911
- **CLI:** `--coverage.100`, `--coverage.100=false`
908912

909913
Shortcut for `--check-coverage --lines 100 --functions 100 --branches 100 --statements 100`.
@@ -942,7 +946,7 @@ See [istanbul documentation](https://github.com/istanbuljs/nyc#ignoring-methods)
942946
}
943947
```
944948

945-
- **Available for providers:** `'c8' | 'istanbul'`
949+
- **Available for providers:** `'c8' | 'v8' | 'istanbul'`
946950

947951
Watermarks for statements, lines, branches and functions. See [istanbul documentation](https://github.com/istanbuljs/nyc#high-and-low-watermarks) for more information.
948952

‎docs/guide/coverage.md

+12-8
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,21 @@ title: Coverage | Guide
44

55
# Coverage
66

7-
Vitest supports Native code coverage via [`c8`](https://github.com/bcoe/c8) and instrumented code coverage via [`istanbul`](https://istanbul.js.org/).
7+
Vitest supports Native code coverage via [`v8`](https://v8.dev/blog/javascript-code-coverage) and instrumented code coverage via [`istanbul`](https://istanbul.js.org/).
8+
9+
:::info
10+
The `c8` provider is being replaced by the [`v8`](https://v8.dev/blog/javascript-code-coverage) provider. It will be deprecated in the next major version.
11+
:::
812

913
## Coverage Providers
1014

1115
:::tip
1216
Since Vitest v0.22.0
1317
:::
1418

15-
Both `c8` and `istanbul` support are optional. By default, `c8` will be used.
19+
Both `v8` and `istanbul` support are optional. By default, `v8` will be used.
1620

17-
You can select the coverage tool by setting `test.coverage.provider` to either `c8` or `istanbul`:
21+
You can select the coverage tool by setting `test.coverage.provider` to `v8` or `istanbul`:
1822

1923
```ts
2024
// vite.config.ts
@@ -23,7 +27,7 @@ import { defineConfig } from 'vitest/config'
2327
export default defineConfig({
2428
test: {
2529
coverage: {
26-
provider: 'istanbul' // or 'c8'
30+
provider: 'istanbul' // or 'v8'
2731
},
2832
},
2933
})
@@ -34,8 +38,8 @@ When you start the Vitest process, it will prompt you to install the correspondi
3438
Or if you prefer to install them manually:
3539

3640
```bash
37-
# For c8
38-
npm i -D @vitest/coverage-c8
41+
# For v8
42+
npm i -D @vitest/coverage-v8
3943

4044
# For istanbul
4145
npm i -D @vitest/coverage-istanbul
@@ -138,7 +142,7 @@ export default defineConfig({
138142

139143
Both coverage providers have their own ways how to ignore code from coverage reports:
140144

141-
- [`c8`](https://github.com/bcoe/c8#ignoring-uncovered-lines-functions-and-blocks)
145+
- [`v8`](https://github.com/istanbuljs/v8-to-istanbul#ignoring-uncovered-lines)
142146
- [`ìstanbul`](https://github.com/istanbuljs/nyc#parsing-hints-ignoring-lines)
143147

144148
When using TypeScript the source codes are transpiled using `esbuild`, which strips all comments from the source codes ([esbuild#516](https://github.com/evanw/esbuild/issues/516)).
@@ -153,7 +157,7 @@ Beware that these ignore hints may now be included in final production build as
153157
if (condition) {
154158
```
155159

156-
For `c8` this does not cause any issues. You can use `c8 ignore` comments with Typescript as usual:
160+
For `v8` this does not cause any issues. You can use `c8 ignore` comments with Typescript as usual:
157161

158162
<!-- eslint-skip -->
159163
```ts

‎docs/guide/features.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ Learn more at [Mocking](/guide/mocking).
143143

144144
## Coverage
145145

146-
Vitest supports Native code coverage via [`c8`](https://github.com/bcoe/c8) and instrumented code coverage via [`istanbul`](https://istanbul.js.org/).
146+
Vitest supports Native code coverage via [`v8`](https://v8.dev/blog/javascript-code-coverage) and instrumented code coverage via [`istanbul`](https://istanbul.js.org/).
147147

148148
```json
149149
{

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"@vitest/browser": "workspace:*",
4747
"@vitest/coverage-c8": "workspace:*",
4848
"@vitest/coverage-istanbul": "workspace:*",
49+
"@vitest/coverage-v8": "workspace:*",
4950
"@vitest/ui": "workspace:*",
5051
"bumpp": "^9.1.0",
5152
"esbuild": "^0.17.18",

‎packages/coverage-c8/src/provider.ts

+9
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ export class C8CoverageProvider extends BaseCoverageProvider implements Coverage
5454
branches: config['100'] ? 100 : config.branches,
5555
statements: config['100'] ? 100 : config.statements,
5656
}
57+
58+
const banner = ' DEPRECATION '
59+
this.ctx.logger.log(
60+
c.bgYellow(c.black((banner))),
61+
c.yellow('@vitest/coverage-c8 is being replaced by @vitest/coverage-v8.'),
62+
c.yellow(`\n${' '.repeat(banner.length)} See`),
63+
c.blue(c.underline('https://github.com/vitest-dev/vitest/pull/3339')),
64+
c.yellow('for more information.'),
65+
)
5766
}
5867

5968
resolveOptions() {

‎packages/coverage-istanbul/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"prepublishOnly": "pnpm build"
4343
},
4444
"peerDependencies": {
45-
"vitest": ">=0.30.0 <1"
45+
"vitest": ">=0.32.0 <1"
4646
},
4747
"dependencies": {
4848
"istanbul-lib-coverage": "^3.2.0",

‎packages/coverage-istanbul/src/provider.ts

+10-54
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { existsSync, promises as fs } from 'node:fs'
2-
import { relative, resolve } from 'pathe'
2+
import { resolve } from 'pathe'
33
import type { AfterSuiteRunMeta, CoverageIstanbulOptions, CoverageProvider, ReportContext, ResolvedCoverageOptions, Vitest } from 'vitest'
44
import { coverageConfigDefaults, defaultExclude, defaultInclude } from 'vitest/config'
55
import { BaseCoverageProvider } from 'vitest/coverage'
@@ -16,8 +16,6 @@ import { COVERAGE_STORE_KEY } from './constants'
1616

1717
type Options = ResolvedCoverageOptions<'istanbul'>
1818

19-
type Threshold = 'lines' | 'functions' | 'statements' | 'branches'
20-
2119
interface TestExclude {
2220
new(opts: {
2321
cwd?: string | string[]
@@ -146,11 +144,15 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
146144
|| this.options.functions
147145
|| this.options.lines
148146
|| this.options.statements) {
149-
this.checkThresholds(coverageMap, {
150-
branches: this.options.branches,
151-
functions: this.options.functions,
152-
lines: this.options.lines,
153-
statements: this.options.statements,
147+
this.checkThresholds({
148+
coverageMap,
149+
thresholds: {
150+
branches: this.options.branches,
151+
functions: this.options.functions,
152+
lines: this.options.lines,
153+
statements: this.options.statements,
154+
},
155+
perFile: this.options.perFile,
154156
})
155157
}
156158

@@ -169,52 +171,6 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
169171
}
170172
}
171173

172-
checkThresholds(coverageMap: CoverageMap, thresholds: Record<Threshold, number | undefined>) {
173-
// Construct list of coverage summaries where thresholds are compared against
174-
const summaries = this.options.perFile
175-
? coverageMap.files()
176-
.map((file: string) => ({
177-
file,
178-
summary: coverageMap.fileCoverageFor(file).toSummary(),
179-
}))
180-
: [{
181-
file: null,
182-
summary: coverageMap.getCoverageSummary(),
183-
}]
184-
185-
// Check thresholds of each summary
186-
for (const { summary, file } of summaries) {
187-
for (const thresholdKey of ['lines', 'functions', 'statements', 'branches'] as const) {
188-
const threshold = thresholds[thresholdKey]
189-
190-
if (threshold !== undefined) {
191-
const coverage = summary.data[thresholdKey].pct
192-
193-
if (coverage < threshold) {
194-
process.exitCode = 1
195-
196-
/*
197-
* Generate error message based on perFile flag:
198-
* - ERROR: Coverage for statements (33.33%) does not meet threshold (85%) for src/math.ts
199-
* - ERROR: Coverage for statements (50%) does not meet global threshold (85%)
200-
*/
201-
let errorMessage = `ERROR: Coverage for ${thresholdKey} (${coverage}%) does not meet`
202-
203-
if (!this.options.perFile)
204-
errorMessage += ' global'
205-
206-
errorMessage += ` threshold (${threshold}%)`
207-
208-
if (this.options.perFile && file)
209-
errorMessage += ` for ${relative('./', file).replace(/\\/g, '/')}`
210-
211-
console.error(errorMessage)
212-
}
213-
}
214-
}
215-
}
216-
}
217-
218174
async includeUntestedFiles(coverageMap: CoverageMap) {
219175
// Load, instrument and collect empty coverages from all files which
220176
// are not already in the coverage map

‎packages/coverage-v8/package.json

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{
2+
"name": "@vitest/coverage-v8",
3+
"type": "module",
4+
"version": "0.31.3",
5+
"description": "V8 coverage provider for Vitest",
6+
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
7+
"license": "MIT",
8+
"funding": "https://opencollective.com/vitest",
9+
"homepage": "https://github.com/vitest-dev/vitest/tree/main/packages/coverage-v8#readme",
10+
"repository": {
11+
"type": "git",
12+
"url": "git+https://github.com/vitest-dev/vitest.git",
13+
"directory": "packages/coverage-v8"
14+
},
15+
"bugs": {
16+
"url": "https://github.com/vitest-dev/vitest/issues"
17+
},
18+
"keywords": [
19+
"vite",
20+
"vitest",
21+
"test",
22+
"coverage",
23+
"v8"
24+
],
25+
"sideEffects": false,
26+
"exports": {
27+
".": {
28+
"types": "./dist/index.d.ts",
29+
"import": "./dist/index.js"
30+
},
31+
"./*": "./*"
32+
},
33+
"main": "./dist/index.js",
34+
"module": "./dist/index.js",
35+
"types": "./dist/index.d.ts",
36+
"files": [
37+
"dist"
38+
],
39+
"scripts": {
40+
"build": "rimraf dist && rollup -c",
41+
"dev": "rollup -c --watch --watch.include 'src/**'",
42+
"prepublishOnly": "pnpm build"
43+
},
44+
"peerDependencies": {
45+
"vitest": ">=0.32.0 <1"
46+
},
47+
"dependencies": {
48+
"@ampproject/remapping": "^2.2.1",
49+
"@bcoe/v8-coverage": "^0.2.3",
50+
"istanbul-lib-coverage": "^3.2.0",
51+
"istanbul-lib-report": "^3.0.0",
52+
"istanbul-lib-source-maps": "^4.0.1",
53+
"istanbul-reports": "^3.1.5",
54+
"magic-string": "^0.30.0",
55+
"picocolors": "^1.0.0",
56+
"std-env": "^3.3.2",
57+
"test-exclude": "^6.0.0",
58+
"v8-to-istanbul": "^9.1.0"
59+
},
60+
"devDependencies": {
61+
"@types/istanbul-lib-coverage": "^2.0.4",
62+
"@types/istanbul-lib-report": "^3.0.0",
63+
"@types/istanbul-lib-source-maps": "^4.0.1",
64+
"@types/istanbul-reports": "^3.0.1",
65+
"pathe": "^1.1.0",
66+
"vite-node": "workspace:*",
67+
"vitest": "workspace:*"
68+
}
69+
}

‎packages/coverage-v8/rollup.config.js

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { builtinModules } from 'node:module'
2+
import esbuild from 'rollup-plugin-esbuild'
3+
import dts from 'rollup-plugin-dts'
4+
import commonjs from '@rollup/plugin-commonjs'
5+
import json from '@rollup/plugin-json'
6+
import alias from '@rollup/plugin-alias'
7+
import nodeResolve from '@rollup/plugin-node-resolve'
8+
import { join } from 'pathe'
9+
import pkg from './package.json' assert { type: 'json' }
10+
11+
const entries = {
12+
index: 'src/index.ts',
13+
provider: 'src/provider.ts',
14+
}
15+
16+
const external = [
17+
...builtinModules,
18+
...Object.keys(pkg.dependencies || {}),
19+
...Object.keys(pkg.peerDependencies || {}),
20+
'node:inspector',
21+
'vitest',
22+
'vitest/node',
23+
'vitest/config',
24+
'vitest/coverage',
25+
]
26+
27+
const plugins = [
28+
alias({
29+
entries: [
30+
{ find: /^node:(.+)$/, replacement: '$1' },
31+
],
32+
}),
33+
nodeResolve(),
34+
json(),
35+
commonjs(),
36+
esbuild({
37+
target: 'node14',
38+
}),
39+
]
40+
41+
export default () => [
42+
{
43+
input: entries,
44+
output: {
45+
dir: 'dist',
46+
format: 'esm',
47+
},
48+
external,
49+
plugins,
50+
},
51+
{
52+
input: entries,
53+
output: {
54+
dir: join(process.cwd(), 'dist'),
55+
entryFileNames: '[name].d.ts',
56+
format: 'esm',
57+
},
58+
external,
59+
plugins: [
60+
dts({ respectExternal: true }),
61+
],
62+
},
63+
]

‎packages/coverage-v8/src/index.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as coverage from './takeCoverage'
2+
3+
export default {
4+
...coverage,
5+
async getProvider() {
6+
// to not bundle the provider
7+
const name = './provider.js'
8+
const { V8CoverageProvider } = await import(name) as typeof import('./provider')
9+
return new V8CoverageProvider()
10+
},
11+
}

‎packages/coverage-v8/src/provider.ts

+263
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
import { existsSync, promises as fs } from 'node:fs'
2+
import type { Profiler } from 'node:inspector'
3+
import { fileURLToPath, pathToFileURL } from 'node:url'
4+
import v8ToIstanbul from 'v8-to-istanbul'
5+
import { mergeProcessCovs } from '@bcoe/v8-coverage'
6+
import libReport from 'istanbul-lib-report'
7+
import reports from 'istanbul-reports'
8+
import type { CoverageMap } from 'istanbul-lib-coverage'
9+
import libCoverage from 'istanbul-lib-coverage'
10+
import libSourceMaps from 'istanbul-lib-source-maps'
11+
import MagicString from 'magic-string'
12+
import remapping from '@ampproject/remapping'
13+
import { normalize, resolve } from 'pathe'
14+
import c from 'picocolors'
15+
import { provider } from 'std-env'
16+
import type { EncodedSourceMap } from 'vite-node'
17+
import { coverageConfigDefaults, defaultExclude, defaultInclude } from 'vitest/config'
18+
import { BaseCoverageProvider } from 'vitest/coverage'
19+
import type { AfterSuiteRunMeta, CoverageProvider, CoverageV8Options, ReportContext, ResolvedCoverageOptions } from 'vitest'
20+
import type { Vitest } from 'vitest/node'
21+
22+
// @ts-expect-error missing types
23+
import _TestExclude from 'test-exclude'
24+
25+
interface TestExclude {
26+
new(opts: {
27+
cwd?: string | string[]
28+
include?: string | string[]
29+
exclude?: string | string[]
30+
extension?: string | string[]
31+
excludeNodeModules?: boolean
32+
}): {
33+
shouldInstrument(filePath: string): boolean
34+
glob(cwd: string): Promise<string[]>
35+
}
36+
}
37+
38+
type Options = ResolvedCoverageOptions<'v8'>
39+
40+
// TODO: vite-node should export this
41+
const WRAPPER_LENGTH = 185
42+
43+
// Note that this needs to match the line ending as well
44+
const VITE_EXPORTS_LINE_PATTERN = /Object\.defineProperty\(__vite_ssr_exports__.*\n/g
45+
46+
export class V8CoverageProvider extends BaseCoverageProvider implements CoverageProvider {
47+
name = 'v8'
48+
49+
ctx!: Vitest
50+
options!: Options
51+
testExclude!: InstanceType<TestExclude>
52+
coverages: Profiler.TakePreciseCoverageReturnType[] = []
53+
54+
initialize(ctx: Vitest) {
55+
const config: CoverageV8Options = ctx.config.coverage
56+
57+
this.ctx = ctx
58+
this.options = {
59+
...coverageConfigDefaults,
60+
61+
// User's options
62+
...config,
63+
64+
// Resolved fields
65+
provider: 'v8',
66+
reporter: this.resolveReporters(config.reporter || coverageConfigDefaults.reporter),
67+
reportsDirectory: resolve(ctx.config.root, config.reportsDirectory || coverageConfigDefaults.reportsDirectory),
68+
lines: config['100'] ? 100 : config.lines,
69+
functions: config['100'] ? 100 : config.functions,
70+
branches: config['100'] ? 100 : config.branches,
71+
statements: config['100'] ? 100 : config.statements,
72+
}
73+
74+
this.testExclude = new _TestExclude({
75+
cwd: ctx.config.root,
76+
include: typeof this.options.include === 'undefined' ? undefined : [...this.options.include],
77+
exclude: [...defaultExclude, ...defaultInclude, ...this.options.exclude],
78+
excludeNodeModules: true,
79+
extension: this.options.extension,
80+
})
81+
}
82+
83+
resolveOptions() {
84+
return this.options
85+
}
86+
87+
async clean(clean = true) {
88+
if (clean && existsSync(this.options.reportsDirectory))
89+
await fs.rm(this.options.reportsDirectory, { recursive: true, force: true, maxRetries: 10 })
90+
91+
this.coverages = []
92+
}
93+
94+
onAfterSuiteRun({ coverage }: AfterSuiteRunMeta) {
95+
this.coverages.push(coverage as Profiler.TakePreciseCoverageReturnType)
96+
}
97+
98+
async reportCoverage({ allTestsRun }: ReportContext = {}) {
99+
if (provider === 'stackblitz')
100+
this.ctx.logger.log(c.blue(' % ') + c.yellow('@vitest/coverage-v8 does not work on Stackblitz. Report will be empty.'))
101+
102+
const merged = mergeProcessCovs(this.coverages)
103+
const scriptCoverages = merged.result.filter(result => this.testExclude.shouldInstrument(fileURLToPath(result.url)))
104+
105+
if (this.options.all && allTestsRun) {
106+
const coveredFiles = Array.from(scriptCoverages.map(r => r.url))
107+
const untestedFiles = await this.getUntestedFiles(coveredFiles)
108+
109+
scriptCoverages.push(...untestedFiles)
110+
}
111+
112+
const converted = await Promise.all(scriptCoverages.map(async ({ url, functions }) => {
113+
const sources = await this.getSources(url)
114+
115+
// If no source map was found from vite-node we can assume this file was not run in the wrapper
116+
const wrapperLength = sources.sourceMap ? WRAPPER_LENGTH : 0
117+
118+
const converter = v8ToIstanbul(url, wrapperLength, sources)
119+
await converter.load()
120+
121+
converter.applyCoverage(functions)
122+
return converter.toIstanbul()
123+
}))
124+
125+
const mergedCoverage = converted.reduce((coverage, previousCoverageMap) => {
126+
const map = libCoverage.createCoverageMap(coverage)
127+
map.merge(previousCoverageMap)
128+
return map
129+
}, libCoverage.createCoverageMap({}))
130+
131+
const sourceMapStore = libSourceMaps.createSourceMapStore()
132+
const coverageMap: CoverageMap = await sourceMapStore.transformCoverage(mergedCoverage)
133+
134+
const context = libReport.createContext({
135+
dir: this.options.reportsDirectory,
136+
coverageMap,
137+
sourceFinder: sourceMapStore.sourceFinder,
138+
watermarks: this.options.watermarks,
139+
})
140+
141+
for (const reporter of this.options.reporter) {
142+
reports.create(reporter[0], {
143+
skipFull: this.options.skipFull,
144+
projectRoot: this.ctx.config.root,
145+
...reporter[1],
146+
}).execute(context)
147+
}
148+
149+
if (this.options.branches
150+
|| this.options.functions
151+
|| this.options.lines
152+
|| this.options.statements) {
153+
this.checkThresholds({
154+
coverageMap,
155+
thresholds: {
156+
branches: this.options.branches,
157+
functions: this.options.functions,
158+
lines: this.options.lines,
159+
statements: this.options.statements,
160+
},
161+
perFile: this.options.perFile,
162+
})
163+
}
164+
165+
if (this.options.thresholdAutoUpdate && allTestsRun) {
166+
this.updateThresholds({
167+
coverageMap,
168+
thresholds: {
169+
branches: this.options.branches,
170+
functions: this.options.functions,
171+
lines: this.options.lines,
172+
statements: this.options.statements,
173+
},
174+
perFile: this.options.perFile,
175+
configurationFile: this.ctx.server.config.configFile,
176+
})
177+
}
178+
}
179+
180+
private async getUntestedFiles(testedFiles: string[]): Promise<Profiler.ScriptCoverage[]> {
181+
const includedFiles = await this.testExclude.glob(this.ctx.config.root)
182+
const uncoveredFiles = includedFiles
183+
.map(file => pathToFileURL(resolve(this.ctx.config.root, file)))
184+
.filter(file => !testedFiles.includes(file.href))
185+
186+
return await Promise.all(uncoveredFiles.map(async (uncoveredFile) => {
187+
const { source } = await this.getSources(uncoveredFile.href)
188+
189+
return {
190+
url: uncoveredFile.href,
191+
scriptId: '0',
192+
// Create a made up function to mark whole file as uncovered. Note that this does not exist in source maps.
193+
functions: [{
194+
ranges: [{
195+
startOffset: 0,
196+
endOffset: source.length,
197+
count: 0,
198+
}],
199+
isBlockCoverage: true,
200+
// This is magical value that indicates an empty report: https://github.com/istanbuljs/v8-to-istanbul/blob/fca5e6a9e6ef38a9cdc3a178d5a6cf9ef82e6cab/lib/v8-to-istanbul.js#LL131C40-L131C40
201+
functionName: '(empty-report)',
202+
}],
203+
}
204+
}))
205+
}
206+
207+
private async getSources(url: string): Promise<{
208+
source: string
209+
originalSource?: string
210+
sourceMap?: { sourcemap: EncodedSourceMap }
211+
}> {
212+
const filePath = normalize(fileURLToPath(url))
213+
const transformResult = this.ctx.projects
214+
.map(project => project.vitenode.fetchCache.get(filePath)?.result)
215+
.filter(Boolean)
216+
.shift()
217+
218+
const map = transformResult?.map
219+
const code = transformResult?.code
220+
const sourcesContent = map?.sourcesContent?.[0] || await fs.readFile(filePath, 'utf-8')
221+
222+
// These can be uncovered files included by "all: true" or files that are loaded outside vite-node
223+
if (!map)
224+
return { source: code || sourcesContent }
225+
226+
return {
227+
originalSource: sourcesContent,
228+
source: code || sourcesContent,
229+
sourceMap: {
230+
sourcemap: removeViteHelpersFromSourceMaps(code, {
231+
...map,
232+
version: 3,
233+
sources: [url],
234+
sourcesContent: [sourcesContent],
235+
}),
236+
},
237+
}
238+
}
239+
}
240+
241+
/**
242+
* Remove generated code from the source maps:
243+
* - Vite's export helpers: e.g. `Object.defineProperty(__vite_ssr_exports__, "sum", { enumerable: true, configurable: true, get(){ return sum }});`
244+
*/
245+
function removeViteHelpersFromSourceMaps(source: string | undefined, map: EncodedSourceMap) {
246+
if (!source || !source.match(VITE_EXPORTS_LINE_PATTERN))
247+
return map
248+
249+
const sourceWithoutHelpers = new MagicString(source)
250+
sourceWithoutHelpers.replaceAll(VITE_EXPORTS_LINE_PATTERN, '\n')
251+
252+
const mapWithoutHelpers = sourceWithoutHelpers.generateMap({
253+
hires: true,
254+
})
255+
256+
// A merged source map where the first one excludes helpers
257+
const combinedMap = remapping(
258+
[{ ...mapWithoutHelpers, version: 3 }, map],
259+
() => null,
260+
)
261+
262+
return combinedMap as EncodedSourceMap
263+
}
+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* For details about the Profiler.* messages see https://chromedevtools.github.io/devtools-protocol/v8/Profiler/
3+
*/
4+
5+
import inspector from 'node:inspector'
6+
import type { Profiler } from 'node:inspector'
7+
import { provider } from 'std-env'
8+
9+
const session = new inspector.Session()
10+
11+
export function startCoverage() {
12+
session.connect()
13+
session.post('Profiler.enable')
14+
session.post('Profiler.startPreciseCoverage', {
15+
callCount: true,
16+
detailed: true,
17+
})
18+
}
19+
20+
export async function takeCoverage() {
21+
return new Promise((resolve, reject) => {
22+
session.post('Profiler.takePreciseCoverage', async (error, coverage) => {
23+
if (error)
24+
return reject(error)
25+
26+
// Reduce amount of data sent over rpc by doing some early result filtering
27+
const result = coverage.result.filter(filterResult)
28+
29+
resolve({ result })
30+
})
31+
32+
if (provider === 'stackblitz')
33+
resolve({ result: [] })
34+
})
35+
}
36+
37+
export function stopCoverage() {
38+
session.post('Profiler.stopPreciseCoverage')
39+
session.post('Profiler.disable')
40+
session.disconnect()
41+
}
42+
43+
function filterResult(coverage: Profiler.ScriptCoverage): boolean {
44+
if (!coverage.url.startsWith('file://'))
45+
return false
46+
47+
if (coverage.url.includes('/node_modules/'))
48+
return false
49+
50+
return true
51+
}

‎packages/vitest/src/defaults.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const defaultCoverageExcludes = [
2626

2727
// These are the generic defaults for coverage. Providers may also set some provider specific defaults.
2828
export const coverageConfigDefaults: ResolvedCoverageOptions = {
29-
provider: 'c8',
29+
provider: 'v8',
3030
enabled: false,
3131
clean: true,
3232
cleanOnRerun: true,

‎packages/vitest/src/integrations/coverage.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ interface Loader {
66

77
export const CoverageProviderMap: Record<string, string> = {
88
c8: '@vitest/coverage-c8',
9+
v8: '@vitest/coverage-v8',
910
istanbul: '@vitest/coverage-istanbul',
1011
}
1112

@@ -15,7 +16,7 @@ async function resolveCoverageProviderModule(options: CoverageOptions | undefine
1516

1617
const provider = options.provider
1718

18-
if (provider === 'c8' || provider === 'istanbul') {
19+
if (provider === 'c8' || provider === 'v8' || provider === 'istanbul') {
1920
const { default: coverageModule } = await loader.executeId(CoverageProviderMap[provider])
2021

2122
if (!coverageModule)

‎packages/vitest/src/node/cli-api.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,21 @@ export async function startVitest(
5959
const ctx = await createVitest(mode, options, viteOverrides)
6060

6161
if (mode === 'test' && ctx.config.coverage.enabled) {
62-
const provider = ctx.config.coverage.provider || 'c8'
62+
const provider = ctx.config.coverage.provider || 'v8'
6363
const requiredPackages = CoverageProviderMap[provider]
6464

6565
if (requiredPackages) {
66-
if (!await ensurePackageInstalled(requiredPackages, root)) {
66+
// Remove this message once support for @vitest/coverage-c8 has been removed completely
67+
const defaultProviderInfo = 'Default coverage provider has changed from "c8" to "v8". '
68+
+ 'New package is required to be installed. '
69+
+ 'To use the old deprecated coverage provider use "--coverage.provider c8" option.\n'
70+
+ 'See https://github.com/vitest-dev/vitest/pull/3339 for more information.\n\n'
71+
72+
const isUsingDefaultProvider
73+
= ctx.server.config.test?.coverage?.provider === undefined
74+
&& options.coverage?.provider === undefined
75+
76+
if (!await ensurePackageInstalled(requiredPackages, root, isUsingDefaultProvider ? defaultProviderInfo : undefined)) {
6777
process.exitCode = 1
6878
return ctx
6979
}

‎packages/vitest/src/node/config.ts

+3
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ export function resolveConfig(
120120
if (resolved.coverage.provider === 'c8' && resolved.coverage.enabled && isBrowserEnabled(resolved))
121121
throw new Error('@vitest/coverage-c8 does not work with --browser. Use @vitest/coverage-istanbul instead')
122122

123+
if (resolved.coverage.provider === 'v8' && resolved.coverage.enabled && isBrowserEnabled(resolved))
124+
throw new Error('@vitest/coverage-v8 does not work with --browser. Use @vitest/coverage-istanbul instead')
125+
123126
resolved.deps = resolved.deps || {}
124127
// vitenode will try to import such file with native node,
125128
// but then our mocker will not work properly

‎packages/vitest/src/node/pkg.ts

+4
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@ const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
99
export async function ensurePackageInstalled(
1010
dependency: string,
1111
root: string,
12+
errorMessage?: string,
1213
) {
1314
if (isPackageExists(dependency, { paths: [root, __dirname] }))
1415
return true
1516

1617
const promptInstall = !isCI && process.stdout.isTTY
1718

19+
if (errorMessage)
20+
process.stderr.write(c.red(errorMessage))
21+
1822
process.stderr.write(c.red(`${c.inverse(c.red(' MISSING DEP '))} Can not find dependency '${dependency}'\n\n`))
1923

2024
if (!promptInstall)

‎packages/vitest/src/types/coverage.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,14 @@ type CoverageReporterWithOptions<ReporterName extends CoverageReporter = Coverag
6161
: [ReporterName, Partial<ReportOptions[ReporterName]>]
6262
: never
6363

64-
type Provider = 'c8' | 'istanbul' | 'custom' | undefined
64+
type Provider = 'c8' | 'v8' | 'istanbul' | 'custom' | undefined
6565

6666
export type CoverageOptions<T extends Provider = Provider> =
6767
T extends 'istanbul' ? ({ provider: T } & CoverageIstanbulOptions) :
6868
T extends 'c8' ? ({ provider: T } & CoverageC8Options) :
69-
T extends 'custom' ? ({ provider: T } & CustomProviderOptions) :
70-
({ provider?: T } & (CoverageC8Options))
69+
T extends 'v8' ? ({ provider: T } & CoverageV8Options) :
70+
T extends 'custom' ? ({ provider: T } & CustomProviderOptions) :
71+
({ provider?: T } & (CoverageC8Options))
7172

7273
/** Fields that have default values. Internally these will always be defined. */
7374
type FieldsWithDefaultValues =
@@ -257,6 +258,15 @@ export interface CoverageC8Options extends BaseCoverageOptions {
257258
100?: boolean
258259
}
259260

261+
export interface CoverageV8Options extends BaseCoverageOptions {
262+
/**
263+
* Shortcut for `--check-coverage --lines 100 --functions 100 --branches 100 --statements 100`
264+
*
265+
* @default false
266+
*/
267+
100?: boolean
268+
}
269+
260270
export interface CustomProviderOptions extends Pick<BaseCoverageOptions, FieldsWithDefaultValues> {
261271
/** Name of the module or path to a file to load the custom provider from */
262272
customProviderModule: string

‎packages/vitest/src/utils/coverage.ts

+54
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { readFileSync, writeFileSync } from 'node:fs'
2+
import { relative } from 'pathe'
23
import type { CoverageMap } from 'istanbul-lib-coverage'
34
import type { BaseCoverageOptions, ResolvedCoverageOptions } from '../types'
45

@@ -61,6 +62,59 @@ export class BaseCoverageProvider {
6162
}
6263
}
6364

65+
/**
66+
* Checked collected coverage against configured thresholds. Sets exit code to 1 when thresholds not reached.
67+
*/
68+
checkThresholds({ coverageMap, thresholds, perFile }: {
69+
coverageMap: CoverageMap
70+
thresholds: Record<Threshold, number | undefined>
71+
perFile?: boolean
72+
}) {
73+
// Construct list of coverage summaries where thresholds are compared against
74+
const summaries = perFile
75+
? coverageMap.files()
76+
.map((file: string) => ({
77+
file,
78+
summary: coverageMap.fileCoverageFor(file).toSummary(),
79+
}))
80+
: [{
81+
file: null,
82+
summary: coverageMap.getCoverageSummary(),
83+
}]
84+
85+
// Check thresholds of each summary
86+
for (const { summary, file } of summaries) {
87+
for (const thresholdKey of ['lines', 'functions', 'statements', 'branches'] as const) {
88+
const threshold = thresholds[thresholdKey]
89+
90+
if (threshold !== undefined) {
91+
const coverage = summary.data[thresholdKey].pct
92+
93+
if (coverage < threshold) {
94+
process.exitCode = 1
95+
96+
/*
97+
* Generate error message based on perFile flag:
98+
* - ERROR: Coverage for statements (33.33%) does not meet threshold (85%) for src/math.ts
99+
* - ERROR: Coverage for statements (50%) does not meet global threshold (85%)
100+
*/
101+
let errorMessage = `ERROR: Coverage for ${thresholdKey} (${coverage}%) does not meet`
102+
103+
if (!perFile)
104+
errorMessage += ' global'
105+
106+
errorMessage += ` threshold (${threshold}%)`
107+
108+
if (perFile && file)
109+
errorMessage += ` for ${relative('./', file).replace(/\\/g, '/')}`
110+
111+
console.error(errorMessage)
112+
}
113+
}
114+
}
115+
}
116+
}
117+
64118
/**
65119
* Resolve reporters from various configuration options
66120
*/

‎pnpm-lock.yaml

+74-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎test/config/test/failures.test.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,17 @@ test('inspect-brk cannot be used with threads', async () => {
4949
})
5050

5151
test('c8 coverage provider cannot be used with browser', async () => {
52-
const { stderr } = await runVitest({ coverage: { enabled: true }, browser: { enabled: true, name: 'chrome' } })
52+
const { stderr } = await runVitest({ coverage: { enabled: true, provider: 'c8' }, browser: { enabled: true, name: 'chrome' } })
5353

5454
expect(stderr).toMatch('Error: @vitest/coverage-c8 does not work with --browser. Use @vitest/coverage-istanbul instead')
5555
})
5656

57+
test('v8 coverage provider cannot be used with browser', async () => {
58+
const { stderr } = await runVitest({ coverage: { enabled: true }, browser: { enabled: true, name: 'chrome' } })
59+
60+
expect(stderr).toMatch('Error: @vitest/coverage-v8 does not work with --browser. Use @vitest/coverage-istanbul instead')
61+
})
62+
5763
test('version number is printed when coverage provider fails to load', async () => {
5864
const { stderr, stdout } = await runVitest({
5965
coverage: {

‎test/coverage-test/coverage-report-tests/__snapshots__/v8.report.test.ts.snap

+3,323
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* V8 coverage provider specific test cases
3+
*/
4+
5+
import { expect, test } from 'vitest'
6+
import { readCoverageJson } from './utils'
7+
8+
test('v8 json report', async () => {
9+
const jsonReport = await readCoverageJson()
10+
11+
// If this fails, you can use "npx live-server@1.2.1 ./coverage" to see coverage report
12+
expect(jsonReport).toMatchSnapshot()
13+
})
14+
15+
test('ignored code is marked as covered in the report', async () => {
16+
const functionName = 'ignoredFunction'
17+
const filename = '<process-cwd>/src/utils.ts'
18+
19+
const coverageMap = await readCoverageJson()
20+
const fileCoverage = coverageMap[filename]
21+
22+
const [functionKey] = Object.entries(fileCoverage.fnMap).find(([, fn]) => fn.name === functionName)!
23+
const functionCallCount = fileCoverage.f[functionKey]
24+
25+
// v8-to-istanbul marks excluded lines as covered, instead of removing them from report completely
26+
expect(functionCallCount).toBe(1)
27+
28+
// Function should still be found from the actual sources
29+
const utils = await import('../src/utils')
30+
expect(utils[functionName]).toBeTypeOf('function')
31+
})

‎test/coverage-test/package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
"name": "@vitest/test-coverage",
33
"private": true,
44
"scripts": {
5-
"test": "pnpm test:c8 && pnpm test:istanbul && pnpm test:custom && pnpm test:browser && pnpm test:types",
5+
"test": "pnpm test:c8 && pnpm test:v8 && pnpm test:istanbul && pnpm test:custom && pnpm test:browser && pnpm test:types",
66
"test:c8": "node ./testing.mjs --provider c8",
7+
"test:v8": "node ./testing.mjs --provider v8",
78
"test:custom": "node ./testing.mjs --provider custom",
89
"test:istanbul": "node ./testing.mjs --provider istanbul",
910
"test:browser": "node ./testing.mjs --browser --provider istanbul",
@@ -13,6 +14,9 @@
1314
"@types/istanbul-lib-coverage": "^2.0.4",
1415
"@vitejs/plugin-vue": "latest",
1516
"@vitest/browser": "workspace:*",
17+
"@vitest/coverage-c8": "workspace:*",
18+
"@vitest/coverage-istanbul": "workspace:*",
19+
"@vitest/coverage-v8": "workspace:*",
1620
"@vue/test-utils": "latest",
1721
"happy-dom": "latest",
1822
"istanbul-lib-coverage": "^3.2.0",

‎test/coverage-test/test/configuration-options.test-d.ts

+26-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type Coverage = NonNullable<Configuration['coverage']>
88

99
test('providers, built-in', () => {
1010
assertType<Coverage>({ provider: 'c8' })
11+
assertType<Coverage>({ provider: 'v8' })
1112
assertType<Coverage>({ provider: 'istanbul' })
1213

1314
// @ts-expect-error -- String options must be known ones only
@@ -32,6 +33,16 @@ test('provider options, generic', () => {
3233
},
3334
})
3435

36+
assertType<Coverage>({
37+
provider: 'v8',
38+
enabled: true,
39+
include: ['string'],
40+
watermarks: {
41+
functions: [80, 95],
42+
lines: [80, 95],
43+
},
44+
})
45+
3546
assertType<Coverage>({
3647
provider: 'istanbul',
3748
enabled: true,
@@ -58,6 +69,19 @@ test('provider specific options, c8', () => {
5869
})
5970
})
6071

72+
test('provider specific options, v8', () => {
73+
assertType<Coverage>({
74+
provider: 'v8',
75+
100: true,
76+
})
77+
78+
assertType<Coverage>({
79+
provider: 'v8',
80+
// @ts-expect-error -- Istanbul-only option is not allowed
81+
ignoreClassMethods: ['string'],
82+
})
83+
})
84+
6185
test('provider specific options, istanbul', () => {
6286
assertType<Coverage>({
6387
provider: 'istanbul',
@@ -66,8 +90,8 @@ test('provider specific options, istanbul', () => {
6690

6791
assertType<Coverage>({
6892
provider: 'istanbul',
69-
// @ts-expect-error -- C8-only option is not allowed
70-
src: ['string'],
93+
// @ts-expect-error -- V8-only option is not allowed
94+
100: true,
7195
})
7296
})
7397

‎test/coverage-test/testing.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const configs = [
3232
// Run tests for checking coverage report contents.
3333
['coverage-report-tests', {
3434
include: [
35-
['c8', 'istanbul'].includes(provider) && './coverage-report-tests/generic.report.test.ts',
35+
['c8', 'v8', 'istanbul'].includes(provider) && './coverage-report-tests/generic.report.test.ts',
3636
`./coverage-report-tests/${provider}.report.test.ts`,
3737
].filter(Boolean),
3838
coverage: { enabled: false, clean: false },

0 commit comments

Comments
 (0)
Please sign in to comment.