mirror of
https://github.com/vitest-dev/vitest.git
synced 2025-12-08 18:26:03 +00:00
feat(coverage): custom reporter support (#4828)
This commit is contained in:
parent
480d866aaa
commit
96dc6e9aaf
@ -1171,6 +1171,23 @@ The reporter has three different types:
|
||||
}
|
||||
```
|
||||
|
||||
Since Vitest 1.2.0, you can also pass custom coverage reporters. See [Guide - Custom Coverage Reporter](/guide/coverage#custom-coverage-reporter) for more information.
|
||||
|
||||
<!-- eslint-skip -->
|
||||
```ts
|
||||
{
|
||||
reporter: [
|
||||
// Specify reporter using name of the NPM package
|
||||
'@vitest/custom-coverage-reporter',
|
||||
['@vitest/custom-coverage-reporter', { someOption: true }],
|
||||
|
||||
// Specify reporter using local path
|
||||
'/absolute/path/to/custom-reporter.cjs',
|
||||
['/absolute/path/to/custom-reporter.cjs', { someOption: true }],
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Since Vitest 0.31.0, you can check your coverage report in Vitest UI: check [Vitest UI Coverage](/guide/coverage#vitest-ui) for more details.
|
||||
|
||||
#### coverage.reportOnFailure <Badge type="info">0.31.2+</Badge>
|
||||
@ -2045,7 +2062,7 @@ Path to a [workspace](/guide/workspace) config file relative to [root](#root).
|
||||
|
||||
- **Type:** `boolean`
|
||||
- **Default:** `true`
|
||||
- **CLI:** `--no-isolate`, `--isolate=false`
|
||||
- **CLI:** `--no-isolate`, `--isolate=false`
|
||||
|
||||
Run tests in an isolated environment. This option has no effect on `vmThreads` pool.
|
||||
|
||||
|
||||
@ -70,6 +70,55 @@ export default defineConfig({
|
||||
})
|
||||
```
|
||||
|
||||
## Custom Coverage Reporter
|
||||
|
||||
You can use custom coverage reporters by passing either the name of the package or absolute path in `test.coverage.reporter`:
|
||||
|
||||
```ts
|
||||
// vitest.config.ts
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
coverage: {
|
||||
reporter: [
|
||||
// Specify reporter using name of the NPM package
|
||||
['@vitest/custom-coverage-reporter', { someOption: true }],
|
||||
|
||||
// Specify reporter using local path
|
||||
'/absolute/path/to/custom-reporter.cjs',
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Custom reporters are loaded by Istanbul and must match its reporter interface. See [built-in reporters' implementation](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib) for reference.
|
||||
|
||||
```js
|
||||
// custom-reporter.cjs
|
||||
const { ReportBase } = require('istanbul-lib-report')
|
||||
|
||||
module.exports = class CustomReporter extends ReportBase {
|
||||
constructor(opts) {
|
||||
super()
|
||||
|
||||
// Options passed from configuration are available here
|
||||
this.file = opts.file
|
||||
}
|
||||
|
||||
onStart(root, context) {
|
||||
this.contentWriter = context.writer.writeFile(this.file)
|
||||
this.contentWriter.println('Start of custom coverage report')
|
||||
}
|
||||
|
||||
onEnd() {
|
||||
this.contentWriter.println('End of custom coverage report')
|
||||
this.contentWriter.close()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Coverage Provider
|
||||
|
||||
It's also possible to provide your custom coverage provider by passing `'custom'` in `test.coverage.provider`:
|
||||
|
||||
@ -206,7 +206,8 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
|
||||
this.ctx.logger.log(c.blue(' % ') + c.dim('Coverage report from ') + c.yellow(this.name))
|
||||
|
||||
for (const reporter of this.options.reporter) {
|
||||
reports.create(reporter[0], {
|
||||
// Type assertion required for custom reporters
|
||||
reports.create(reporter[0] as Parameters<typeof reports.create>[0], {
|
||||
skipFull: this.options.skipFull,
|
||||
projectRoot: this.ctx.config.root,
|
||||
...reporter[1],
|
||||
|
||||
@ -198,7 +198,8 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
|
||||
this.ctx.logger.log(c.blue(' % ') + c.dim('Coverage report from ') + c.yellow(this.name))
|
||||
|
||||
for (const reporter of this.options.reporter) {
|
||||
reports.create(reporter[0], {
|
||||
// Type assertion required for custom reporters
|
||||
reports.create(reporter[0] as Parameters<typeof reports.create>[0], {
|
||||
skipFull: this.options.skipFull,
|
||||
projectRoot: this.ctx.config.root,
|
||||
...reporter[1],
|
||||
|
||||
@ -57,7 +57,7 @@ function resolveCoverageFolder(ctx: Vitest) {
|
||||
? htmlReporter[1].subdir
|
||||
: undefined
|
||||
|
||||
if (!subdir)
|
||||
if (!subdir || typeof subdir !== 'string')
|
||||
return [root, `/${basename(root)}/`]
|
||||
|
||||
return [resolve(root, subdir), `/${basename(root)}/${subdir}/`]
|
||||
|
||||
@ -52,14 +52,14 @@ export interface CoverageProviderModule {
|
||||
stopCoverage?(): unknown | Promise<unknown>
|
||||
}
|
||||
|
||||
export type CoverageReporter = keyof ReportOptions
|
||||
export type CoverageReporter = keyof ReportOptions | (string & {})
|
||||
|
||||
type CoverageReporterWithOptions<ReporterName extends CoverageReporter = CoverageReporter> =
|
||||
ReporterName extends CoverageReporter
|
||||
ReporterName extends keyof ReportOptions
|
||||
? ReportOptions[ReporterName] extends never
|
||||
? [ReporterName, {}] // E.g. the "none" reporter
|
||||
: [ReporterName, Partial<ReportOptions[ReporterName]>]
|
||||
: never
|
||||
: [ReporterName, Record<string, unknown>]
|
||||
|
||||
type Provider = 'v8' | 'istanbul' | 'custom' | undefined
|
||||
|
||||
|
||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@ -1590,6 +1590,9 @@ importers:
|
||||
'@types/istanbul-lib-coverage':
|
||||
specifier: ^2.0.6
|
||||
version: 2.0.6
|
||||
'@types/istanbul-lib-report':
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: latest
|
||||
version: 4.5.0(vite@5.0.2)(vue@3.3.8)
|
||||
@ -1611,6 +1614,9 @@ importers:
|
||||
istanbul-lib-coverage:
|
||||
specifier: ^3.2.0
|
||||
version: 3.2.0
|
||||
istanbul-lib-report:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
magicast:
|
||||
specifier: ^0.3.2
|
||||
version: 0.3.2
|
||||
|
||||
@ -31,6 +31,20 @@ test('lcov report', async () => {
|
||||
expect(lcovReportFiles).toContain('index.html')
|
||||
})
|
||||
|
||||
test('custom report', async () => {
|
||||
const coveragePath = resolve('./coverage')
|
||||
const files = fs.readdirSync(coveragePath)
|
||||
|
||||
expect(files).toContain('custom-reporter-output.md')
|
||||
|
||||
const content = fs.readFileSync(resolve(coveragePath, 'custom-reporter-output.md'), 'utf-8')
|
||||
expect(content).toMatchInlineSnapshot(`
|
||||
"Start of custom coverage report
|
||||
End of custom coverage report
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('all includes untested files', () => {
|
||||
const coveragePath = resolve('./coverage/src')
|
||||
const files = fs.readdirSync(coveragePath)
|
||||
|
||||
25
test/coverage-test/custom-reporter.cjs
Normal file
25
test/coverage-test/custom-reporter.cjs
Normal file
@ -0,0 +1,25 @@
|
||||
/* Istanbul uses `require`: https://github.com/istanbuljs/istanbuljs/blob/5584b50305a6a17d3573aea25c84e254d4a08b65/packages/istanbul-reports/index.js#L19 */
|
||||
|
||||
'use strict'
|
||||
const { ReportBase } = require('istanbul-lib-report')
|
||||
|
||||
module.exports = class CustomReporter extends ReportBase {
|
||||
constructor(opts) {
|
||||
super()
|
||||
|
||||
if (!opts.file)
|
||||
throw new Error('File is required as custom reporter parameter')
|
||||
|
||||
this.file = opts.file
|
||||
}
|
||||
|
||||
onStart(root, context) {
|
||||
this.contentWriter = context.writer.writeFile(this.file)
|
||||
this.contentWriter.println('Start of custom coverage report')
|
||||
}
|
||||
|
||||
onEnd() {
|
||||
this.contentWriter.println('End of custom coverage report')
|
||||
this.contentWriter.close()
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,7 @@
|
||||
"devDependencies": {
|
||||
"@ampproject/remapping": "^2.2.1",
|
||||
"@types/istanbul-lib-coverage": "^2.0.6",
|
||||
"@types/istanbul-lib-report": "^3.0.3",
|
||||
"@vitejs/plugin-vue": "latest",
|
||||
"@vitest/browser": "workspace:*",
|
||||
"@vitest/coverage-istanbul": "workspace:*",
|
||||
@ -21,6 +22,7 @@
|
||||
"@vue/test-utils": "latest",
|
||||
"happy-dom": "latest",
|
||||
"istanbul-lib-coverage": "^3.2.0",
|
||||
"istanbul-lib-report": "^3.0.1",
|
||||
"magicast": "^0.3.2",
|
||||
"vite": "latest",
|
||||
"vitest": "workspace:*",
|
||||
|
||||
@ -149,9 +149,7 @@ test('reporters, single', () => {
|
||||
assertType<Coverage>({ reporter: 'text-lcov' })
|
||||
assertType<Coverage>({ reporter: 'text-summary' })
|
||||
assertType<Coverage>({ reporter: 'text' })
|
||||
|
||||
// @ts-expect-error -- String reporters must be known built-in's
|
||||
assertType<Coverage>({ reporter: 'unknown-reporter' })
|
||||
assertType<Coverage>({ reporter: 'custom-reporter' })
|
||||
})
|
||||
|
||||
test('reporters, multiple', () => {
|
||||
@ -173,11 +171,8 @@ test('reporters, multiple', () => {
|
||||
],
|
||||
})
|
||||
|
||||
// @ts-expect-error -- List of string reporters must be known built-in's
|
||||
assertType<Coverage>({ reporter: ['unknown-reporter'] })
|
||||
|
||||
// @ts-expect-error -- ... and all reporters must be known
|
||||
assertType<Coverage>({ reporter: ['html', 'json', 'unknown-reporter'] })
|
||||
assertType<Coverage>({ reporter: ['custom-reporter'] })
|
||||
assertType<Coverage>({ reporter: ['html', 'json', 'custom-reporter'] })
|
||||
})
|
||||
|
||||
test('reporters, with options', () => {
|
||||
@ -196,6 +191,7 @@ test('reporters, with options', () => {
|
||||
['text-lcov', { projectRoot: 'string' }],
|
||||
['text-summary', { file: 'string' }],
|
||||
['text', { skipEmpty: true, skipFull: true, maxCols: 1 }],
|
||||
['custom-reporter', { 'someOption': true, 'some-other-custom-option': { width: 123 } }],
|
||||
],
|
||||
})
|
||||
|
||||
@ -209,12 +205,6 @@ test('reporters, with options', () => {
|
||||
|
||||
assertType<Coverage>({
|
||||
reporter: [
|
||||
// @ts-expect-error -- teamcity report option on html reporter
|
||||
['html', { blockName: 'string' }],
|
||||
|
||||
// @ts-expect-error -- html-spa report option on json reporter
|
||||
['json', { metricsToShow: ['branches'] }],
|
||||
|
||||
// @ts-expect-error -- second value should be object even though TS intellisense prompts types of reporters
|
||||
['lcov', 'html-spa'],
|
||||
],
|
||||
@ -225,9 +215,13 @@ test('reporters, mixed variations', () => {
|
||||
assertType<Coverage>({
|
||||
reporter: [
|
||||
'clover',
|
||||
'custom-reporter-1',
|
||||
['cobertura'],
|
||||
['custom-reporter-2'],
|
||||
['html-spa', {}],
|
||||
['custom-reporter-3', {}],
|
||||
['html', { verbose: true, subdir: 'string' }],
|
||||
['custom-reporter-4', { some: 'option', width: 123 }],
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
@ -79,6 +79,7 @@ export default defineConfig({
|
||||
['html'],
|
||||
['lcov', {}],
|
||||
['json', { file: 'custom-json-report-name.json' }],
|
||||
[resolve('./custom-reporter.cjs'), { file: 'custom-reporter-output.md' }],
|
||||
],
|
||||
|
||||
// These will be updated by tests and reseted back by generic.report.test.ts
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user