diff --git a/README.md b/README.md index ff4a86614302..9f60e47d91fb 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ A blazing fast unit test framework powered by Vite. - [Jest Snapshot](https://jestjs.io/docs/snapshot-testing) - [Chai](https://www.chaijs.com/) built-in for assertions, with [Jest expect](https://jestjs.io/docs/expect) compatible APIs. - [Smart & instant watch mode](https://vitest.dev/guide/features.html#watch-mode), like HMR for tests! -- [Native code coverage](https://vitest.dev/guide/features.html#coverage) via [c8](https://github.com/bcoe/c8) or [`istanbul`](https://istanbul.js.org/). +- [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/). - [Tinyspy](https://github.com/tinylibs/tinyspy) built-in for mocking, stubbing, and spies. - [JSDOM](https://github.com/jsdom/jsdom) and [happy-dom](https://github.com/capricorn86/happy-dom) for DOM and browser API mocking - Components testing ([Vue](./examples/vue), [React](./examples/react), [Svelte](./examples/svelte), [Lit](./examples/lit), [Vitesse](./examples/vitesse)) diff --git a/docs/.vitepress/components/FeaturesList.vue b/docs/.vitepress/components/FeaturesList.vue index add0417bcb08..fef14975f7f2 100644 --- a/docs/.vitepress/components/FeaturesList.vue +++ b/docs/.vitepress/components/FeaturesList.vue @@ -22,7 +22,7 @@ Chai built-in for assertions + Jest expect compatible APIs Tinyspy built-in for mocking happy-dom or jsdom for DOM mocking - Code coverage via c8 or istanbul + Code coverage via v8 or istanbul Rust-like in-source testing Type Testing via expect-type diff --git a/docs/advanced/api.md b/docs/advanced/api.md index 68e8720b2dc4..fa4990699fda 100644 --- a/docs/advanced/api.md +++ b/docs/advanced/api.md @@ -19,7 +19,7 @@ await vitest?.close() `startVitest` function returns `Vitest` instance if tests can be started. It returns `undefined`, if one of the following occurs: - Vitest didn't find "vite" package (usually installed with Vitest) -- If coverage is enabled and run mode is "test", but the coverage package is not installed (`@vitest/coverage-c8` or `@vitest/coverage-istanbul`) +- If coverage is enabled and run mode is "test", but the coverage package is not installed (`@vitest/coverage-v8` or `@vitest/coverage-istanbul`) - If the environment package is not installed (`jsdom`/`happy-dom`/`@edge-runtime/vm`) If `undefined` is returned or tests failed during the run, Vitest sets `process.exitCode` to `1`. diff --git a/docs/config/index.md b/docs/config/index.md index b26dd6dfc701..432fe7657127 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -665,7 +665,11 @@ Isolate environment for each test file. Does not work if you disable [`--threads ### coverage -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. +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. + +::: info +The `c8` provider is being replaced by the `v8` provider. It will be deprecated in the next major version. +::: You can provide coverage options to CLI with dot notation: @@ -679,8 +683,8 @@ If you are using coverage options with dot notation, don't forget to specify `-- #### coverage.provider -- **Type:** `'c8' | 'istanbul' | 'custom'` -- **Default:** `'c8'` +- **Type:** `'c8' | 'v8' | 'istanbul' | 'custom'` +- **Default:** `'v8'` - **CLI:** `--coverage.provider=` Use `provider` to select the tool for coverage collection. @@ -689,7 +693,7 @@ Use `provider` to select the tool for coverage collection. - **Type:** `boolean` - **Default:** `false` -- **Available for providers:** `'c8' | 'istanbul'` +- **Available for providers:** `'c8' | 'v8' | 'istanbul'` - **CLI:** `--coverage.enabled`, `--coverage.enabled=false` Enables coverage collection. Can be overridden using `--coverage` CLI option. @@ -698,7 +702,7 @@ Enables coverage collection. Can be overridden using `--coverage` CLI option. - **Type:** `string[]` - **Default:** `['**']` -- **Available for providers:** `'c8' | 'istanbul'` +- **Available for providers:** `'c8' | 'v8' | 'istanbul'` - **CLI:** `--coverage.include=`, `--coverage.include= --coverage.include=` List of files included in coverage as glob patterns @@ -707,7 +711,7 @@ List of files included in coverage as glob patterns - **Type:** `string | string[]` - **Default:** `['.js', '.cjs', '.mjs', '.ts', '.mts', '.cts', '.tsx', '.jsx', '.vue', '.svelte']` -- **Available for providers:** `'c8' | 'istanbul'` +- **Available for providers:** `'c8' | 'v8' | 'istanbul'` - **CLI:** `--coverage.extension=`, `--coverage.extension= --coverage.extension=` #### coverage.exclude @@ -729,7 +733,7 @@ List of files included in coverage as glob patterns '**/.{eslint,mocha,prettier}rc.{?(c|m)js,yml}', ] ``` -- **Available for providers:** `'c8' | 'istanbul'` +- **Available for providers:** `'c8' | 'v8' | 'istanbul'` - **CLI:** `--coverage.exclude=`, `--coverage.exclude= --coverage.exclude=` List of files excluded from coverage as glob patterns. @@ -738,7 +742,7 @@ List of files excluded from coverage as glob patterns. - **Type:** `boolean` - **Default:** `false` -- **Available for providers:** `'c8' | 'istanbul'` +- **Available for providers:** `'c8' | 'v8' | 'istanbul'` - **CLI:** `--coverage.all`, `--coverage.all=false` 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. - **Type:** `boolean` - **Default:** `true` -- **Available for providers:** `'c8' | 'istanbul'` +- **Available for providers:** `'c8' | 'v8' | 'istanbul'` - **CLI:** `--coverage.clean`, `--coverage.clean=false` Clean coverage results before running tests @@ -756,7 +760,7 @@ Clean coverage results before running tests - **Type:** `boolean` - **Default:** `true` -- **Available for providers:** `'c8' | 'istanbul'` +- **Available for providers:** `'c8' | 'v8' | 'istanbul'` - **CLI:** `--coverage.cleanOnRerun`, `--coverage.cleanOnRerun=false` Clean coverage report on watch rerun @@ -765,7 +769,7 @@ Clean coverage report on watch rerun - **Type:** `string` - **Default:** `'./coverage'` -- **Available for providers:** `'c8' | 'istanbul'` +- **Available for providers:** `'c8' | 'v8' | 'istanbul'` - **CLI:** `--coverage.reportsDirectory=` Directory to write coverage report to. @@ -774,7 +778,7 @@ Directory to write coverage report to. - **Type:** `string | string[] | [string, {}][]` - **Default:** `['text', 'html', 'clover', 'json']` -- **Available for providers:** `'c8' | 'istanbul'` +- **Available for providers:** `'c8' | 'v8' | 'istanbul'` - **CLI:** `--coverage.reporter=`, `--coverage.reporter= --coverage.reporter=` 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 - **Type:** `boolean` - **Default:** `true` -- **Available for providers:** `'c8' | 'istanbul'` +- **Available for providers:** `'c8' | 'v8' | 'istanbul'` - **CLI:** `--coverage.reportOnFailure`, `--coverage.reportOnFailure=false` - **Version:** Since Vitest 0.31.2 @@ -811,7 +815,7 @@ Generate coverage report even when tests fail. - **Type:** `boolean` - **Default:** `false` -- **Available for providers:** `'c8' | 'istanbul'` +- **Available for providers:** `'c8' | 'v8' | 'istanbul'` - **CLI:** `--coverage.skipFull`, `--coverage.skipFull=false` 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. - **Type:** `boolean` - **Default:** `false` -- **Available for providers:** `'c8' | 'istanbul'` +- **Available for providers:** `'c8' | 'v8' | 'istanbul'` - **CLI:** `--coverage.perFile`, `--coverage.perFile=false` Check thresholds per file. @@ -830,7 +834,7 @@ See `lines`, `functions`, `branches` and `statements` for the actual thresholds. - **Type:** `boolean` - **Default:** `false` -- **Available for providers:** `'c8' | 'istanbul'` +- **Available for providers:** `'c8' | 'v8' | 'istanbul'` - **CLI:** `--coverage.thresholdAutoUpdate=` 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. #### coverage.lines - **Type:** `number` -- **Available for providers:** `'c8' | 'istanbul'` +- **Available for providers:** `'c8' | 'v8' | 'istanbul'` - **CLI:** `--coverage.lines=` Threshold for lines. @@ -848,7 +852,7 @@ See [istanbul documentation](https://github.com/istanbuljs/nyc#coverage-threshol #### coverage.functions - **Type:** `number` -- **Available for providers:** `'c8' | 'istanbul'` +- **Available for providers:** `'c8' | 'v8' | 'istanbul'` - **CLI:** `--coverage.functions=` Threshold for functions. @@ -857,7 +861,7 @@ See [istanbul documentation](https://github.com/istanbuljs/nyc#coverage-threshol #### coverage.branches - **Type:** `number` -- **Available for providers:** `'c8' | 'istanbul'` +- **Available for providers:** `'c8' | 'v8' | 'istanbul'` - **CLI:** `--coverage.branches=` Threshold for branches. @@ -866,7 +870,7 @@ See [istanbul documentation](https://github.com/istanbuljs/nyc#coverage-threshol #### coverage.statements - **Type:** `number` -- **Available for providers:** `'c8' | 'istanbul'` +- **Available for providers:** `'c8' | 'v8' | 'istanbul'` - **CLI:** `--coverage.statements=` Threshold for statements. @@ -903,7 +907,7 @@ Specifies the directories that are used when `--all` is enabled. - **Type:** `boolean` - **Default:** `false` -- **Available for providers:** `'c8'` +- **Available for providers:** `'c8' | 'v8'` - **CLI:** `--coverage.100`, `--coverage.100=false` 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) } ``` -- **Available for providers:** `'c8' | 'istanbul'` +- **Available for providers:** `'c8' | 'v8' | 'istanbul'` Watermarks for statements, lines, branches and functions. See [istanbul documentation](https://github.com/istanbuljs/nyc#high-and-low-watermarks) for more information. diff --git a/docs/guide/coverage.md b/docs/guide/coverage.md index 14e5ea3ebc63..8bc72938723f 100644 --- a/docs/guide/coverage.md +++ b/docs/guide/coverage.md @@ -4,7 +4,11 @@ title: Coverage | Guide # Coverage -Vitest supports Native code coverage via [`c8`](https://github.com/bcoe/c8) and instrumented code coverage via [`istanbul`](https://istanbul.js.org/). +Vitest supports Native code coverage via [`v8`](https://v8.dev/blog/javascript-code-coverage) and instrumented code coverage via [`istanbul`](https://istanbul.js.org/). + +:::info +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. +::: ## Coverage Providers @@ -12,9 +16,9 @@ Vitest supports Native code coverage via [`c8`](https://github.com/bcoe/c8) and Since Vitest v0.22.0 ::: -Both `c8` and `istanbul` support are optional. By default, `c8` will be used. +Both `v8` and `istanbul` support are optional. By default, `v8` will be used. -You can select the coverage tool by setting `test.coverage.provider` to either `c8` or `istanbul`: +You can select the coverage tool by setting `test.coverage.provider` to `v8` or `istanbul`: ```ts // vite.config.ts @@ -23,7 +27,7 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { coverage: { - provider: 'istanbul' // or 'c8' + provider: 'istanbul' // or 'v8' }, }, }) @@ -34,8 +38,8 @@ When you start the Vitest process, it will prompt you to install the correspondi Or if you prefer to install them manually: ```bash -# For c8 -npm i -D @vitest/coverage-c8 +# For v8 +npm i -D @vitest/coverage-v8 # For istanbul npm i -D @vitest/coverage-istanbul @@ -138,7 +142,7 @@ export default defineConfig({ Both coverage providers have their own ways how to ignore code from coverage reports: -- [`c8`](https://github.com/bcoe/c8#ignoring-uncovered-lines-functions-and-blocks) +- [`v8`](https://github.com/istanbuljs/v8-to-istanbul#ignoring-uncovered-lines) - [`ìstanbul`](https://github.com/istanbuljs/nyc#parsing-hints-ignoring-lines) 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 if (condition) { ``` -For `c8` this does not cause any issues. You can use `c8 ignore` comments with Typescript as usual: +For `v8` this does not cause any issues. You can use `c8 ignore` comments with Typescript as usual: ```ts diff --git a/docs/guide/features.md b/docs/guide/features.md index a2387f838dde..f1f8f66fa7ab 100644 --- a/docs/guide/features.md +++ b/docs/guide/features.md @@ -143,7 +143,7 @@ Learn more at [Mocking](/guide/mocking). ## Coverage -Vitest supports Native code coverage via [`c8`](https://github.com/bcoe/c8) and instrumented code coverage via [`istanbul`](https://istanbul.js.org/). +Vitest supports Native code coverage via [`v8`](https://v8.dev/blog/javascript-code-coverage) and instrumented code coverage via [`istanbul`](https://istanbul.js.org/). ```json { diff --git a/package.json b/package.json index ab985a33d905..4dff5cc81e80 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@vitest/browser": "workspace:*", "@vitest/coverage-c8": "workspace:*", "@vitest/coverage-istanbul": "workspace:*", + "@vitest/coverage-v8": "workspace:*", "@vitest/ui": "workspace:*", "bumpp": "^9.1.0", "esbuild": "^0.17.18", diff --git a/packages/coverage-c8/src/provider.ts b/packages/coverage-c8/src/provider.ts index 8fc0e8a2d388..8ac20792c0f6 100644 --- a/packages/coverage-c8/src/provider.ts +++ b/packages/coverage-c8/src/provider.ts @@ -54,6 +54,15 @@ export class C8CoverageProvider extends BaseCoverageProvider implements Coverage branches: config['100'] ? 100 : config.branches, statements: config['100'] ? 100 : config.statements, } + + const banner = ' DEPRECATION ' + this.ctx.logger.log( + c.bgYellow(c.black((banner))), + c.yellow('@vitest/coverage-c8 is being replaced by @vitest/coverage-v8.'), + c.yellow(`\n${' '.repeat(banner.length)} See`), + c.blue(c.underline('https://github.com/vitest-dev/vitest/pull/3339')), + c.yellow('for more information.'), + ) } resolveOptions() { diff --git a/packages/coverage-istanbul/package.json b/packages/coverage-istanbul/package.json index 4ee8a2ca1b7b..f9c43b235a7d 100644 --- a/packages/coverage-istanbul/package.json +++ b/packages/coverage-istanbul/package.json @@ -42,7 +42,7 @@ "prepublishOnly": "pnpm build" }, "peerDependencies": { - "vitest": ">=0.30.0 <1" + "vitest": ">=0.32.0 <1" }, "dependencies": { "istanbul-lib-coverage": "^3.2.0", diff --git a/packages/coverage-istanbul/src/provider.ts b/packages/coverage-istanbul/src/provider.ts index 12e490d202ee..84af13763de9 100644 --- a/packages/coverage-istanbul/src/provider.ts +++ b/packages/coverage-istanbul/src/provider.ts @@ -1,5 +1,5 @@ import { existsSync, promises as fs } from 'node:fs' -import { relative, resolve } from 'pathe' +import { resolve } from 'pathe' import type { AfterSuiteRunMeta, CoverageIstanbulOptions, CoverageProvider, ReportContext, ResolvedCoverageOptions, Vitest } from 'vitest' import { coverageConfigDefaults, defaultExclude, defaultInclude } from 'vitest/config' import { BaseCoverageProvider } from 'vitest/coverage' @@ -16,8 +16,6 @@ import { COVERAGE_STORE_KEY } from './constants' type Options = ResolvedCoverageOptions<'istanbul'> -type Threshold = 'lines' | 'functions' | 'statements' | 'branches' - interface TestExclude { new(opts: { cwd?: string | string[] @@ -146,11 +144,15 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co || this.options.functions || this.options.lines || this.options.statements) { - this.checkThresholds(coverageMap, { - branches: this.options.branches, - functions: this.options.functions, - lines: this.options.lines, - statements: this.options.statements, + this.checkThresholds({ + coverageMap, + thresholds: { + branches: this.options.branches, + functions: this.options.functions, + lines: this.options.lines, + statements: this.options.statements, + }, + perFile: this.options.perFile, }) } @@ -169,52 +171,6 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co } } - checkThresholds(coverageMap: CoverageMap, thresholds: Record) { - // Construct list of coverage summaries where thresholds are compared against - const summaries = this.options.perFile - ? coverageMap.files() - .map((file: string) => ({ - file, - summary: coverageMap.fileCoverageFor(file).toSummary(), - })) - : [{ - file: null, - summary: coverageMap.getCoverageSummary(), - }] - - // Check thresholds of each summary - for (const { summary, file } of summaries) { - for (const thresholdKey of ['lines', 'functions', 'statements', 'branches'] as const) { - const threshold = thresholds[thresholdKey] - - if (threshold !== undefined) { - const coverage = summary.data[thresholdKey].pct - - if (coverage < threshold) { - process.exitCode = 1 - - /* - * Generate error message based on perFile flag: - * - ERROR: Coverage for statements (33.33%) does not meet threshold (85%) for src/math.ts - * - ERROR: Coverage for statements (50%) does not meet global threshold (85%) - */ - let errorMessage = `ERROR: Coverage for ${thresholdKey} (${coverage}%) does not meet` - - if (!this.options.perFile) - errorMessage += ' global' - - errorMessage += ` threshold (${threshold}%)` - - if (this.options.perFile && file) - errorMessage += ` for ${relative('./', file).replace(/\\/g, '/')}` - - console.error(errorMessage) - } - } - } - } - } - async includeUntestedFiles(coverageMap: CoverageMap) { // Load, instrument and collect empty coverages from all files which // are not already in the coverage map diff --git a/packages/coverage-v8/package.json b/packages/coverage-v8/package.json new file mode 100644 index 000000000000..5ec20790e5c9 --- /dev/null +++ b/packages/coverage-v8/package.json @@ -0,0 +1,69 @@ +{ + "name": "@vitest/coverage-v8", + "type": "module", + "version": "0.31.3", + "description": "V8 coverage provider for Vitest", + "author": "Anthony Fu ", + "license": "MIT", + "funding": "https://opencollective.com/vitest", + "homepage": "https://github.com/vitest-dev/vitest/tree/main/packages/coverage-v8#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/vitest-dev/vitest.git", + "directory": "packages/coverage-v8" + }, + "bugs": { + "url": "https://github.com/vitest-dev/vitest/issues" + }, + "keywords": [ + "vite", + "vitest", + "test", + "coverage", + "v8" + ], + "sideEffects": false, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./*": "./*" + }, + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "rimraf dist && rollup -c", + "dev": "rollup -c --watch --watch.include 'src/**'", + "prepublishOnly": "pnpm build" + }, + "peerDependencies": { + "vitest": ">=0.32.0 <1" + }, + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@bcoe/v8-coverage": "^0.2.3", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.1.5", + "magic-string": "^0.30.0", + "picocolors": "^1.0.0", + "std-env": "^3.3.2", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.1.0" + }, + "devDependencies": { + "@types/istanbul-lib-coverage": "^2.0.4", + "@types/istanbul-lib-report": "^3.0.0", + "@types/istanbul-lib-source-maps": "^4.0.1", + "@types/istanbul-reports": "^3.0.1", + "pathe": "^1.1.0", + "vite-node": "workspace:*", + "vitest": "workspace:*" + } +} diff --git a/packages/coverage-v8/rollup.config.js b/packages/coverage-v8/rollup.config.js new file mode 100644 index 000000000000..15844d9de912 --- /dev/null +++ b/packages/coverage-v8/rollup.config.js @@ -0,0 +1,63 @@ +import { builtinModules } from 'node:module' +import esbuild from 'rollup-plugin-esbuild' +import dts from 'rollup-plugin-dts' +import commonjs from '@rollup/plugin-commonjs' +import json from '@rollup/plugin-json' +import alias from '@rollup/plugin-alias' +import nodeResolve from '@rollup/plugin-node-resolve' +import { join } from 'pathe' +import pkg from './package.json' assert { type: 'json' } + +const entries = { + index: 'src/index.ts', + provider: 'src/provider.ts', +} + +const external = [ + ...builtinModules, + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.peerDependencies || {}), + 'node:inspector', + 'vitest', + 'vitest/node', + 'vitest/config', + 'vitest/coverage', +] + +const plugins = [ + alias({ + entries: [ + { find: /^node:(.+)$/, replacement: '$1' }, + ], + }), + nodeResolve(), + json(), + commonjs(), + esbuild({ + target: 'node14', + }), +] + +export default () => [ + { + input: entries, + output: { + dir: 'dist', + format: 'esm', + }, + external, + plugins, + }, + { + input: entries, + output: { + dir: join(process.cwd(), 'dist'), + entryFileNames: '[name].d.ts', + format: 'esm', + }, + external, + plugins: [ + dts({ respectExternal: true }), + ], + }, +] diff --git a/packages/coverage-v8/src/index.ts b/packages/coverage-v8/src/index.ts new file mode 100644 index 000000000000..cbdedfa38ef8 --- /dev/null +++ b/packages/coverage-v8/src/index.ts @@ -0,0 +1,11 @@ +import * as coverage from './takeCoverage' + +export default { + ...coverage, + async getProvider() { + // to not bundle the provider + const name = './provider.js' + const { V8CoverageProvider } = await import(name) as typeof import('./provider') + return new V8CoverageProvider() + }, +} diff --git a/packages/coverage-v8/src/provider.ts b/packages/coverage-v8/src/provider.ts new file mode 100644 index 000000000000..b9fe8a97f174 --- /dev/null +++ b/packages/coverage-v8/src/provider.ts @@ -0,0 +1,263 @@ +import { existsSync, promises as fs } from 'node:fs' +import type { Profiler } from 'node:inspector' +import { fileURLToPath, pathToFileURL } from 'node:url' +import v8ToIstanbul from 'v8-to-istanbul' +import { mergeProcessCovs } from '@bcoe/v8-coverage' +import libReport from 'istanbul-lib-report' +import reports from 'istanbul-reports' +import type { CoverageMap } from 'istanbul-lib-coverage' +import libCoverage from 'istanbul-lib-coverage' +import libSourceMaps from 'istanbul-lib-source-maps' +import MagicString from 'magic-string' +import remapping from '@ampproject/remapping' +import { normalize, resolve } from 'pathe' +import c from 'picocolors' +import { provider } from 'std-env' +import type { EncodedSourceMap } from 'vite-node' +import { coverageConfigDefaults, defaultExclude, defaultInclude } from 'vitest/config' +import { BaseCoverageProvider } from 'vitest/coverage' +import type { AfterSuiteRunMeta, CoverageProvider, CoverageV8Options, ReportContext, ResolvedCoverageOptions } from 'vitest' +import type { Vitest } from 'vitest/node' + +// @ts-expect-error missing types +import _TestExclude from 'test-exclude' + +interface TestExclude { + new(opts: { + cwd?: string | string[] + include?: string | string[] + exclude?: string | string[] + extension?: string | string[] + excludeNodeModules?: boolean + }): { + shouldInstrument(filePath: string): boolean + glob(cwd: string): Promise + } +} + +type Options = ResolvedCoverageOptions<'v8'> + +// TODO: vite-node should export this +const WRAPPER_LENGTH = 185 + +// Note that this needs to match the line ending as well +const VITE_EXPORTS_LINE_PATTERN = /Object\.defineProperty\(__vite_ssr_exports__.*\n/g + +export class V8CoverageProvider extends BaseCoverageProvider implements CoverageProvider { + name = 'v8' + + ctx!: Vitest + options!: Options + testExclude!: InstanceType + coverages: Profiler.TakePreciseCoverageReturnType[] = [] + + initialize(ctx: Vitest) { + const config: CoverageV8Options = ctx.config.coverage + + this.ctx = ctx + this.options = { + ...coverageConfigDefaults, + + // User's options + ...config, + + // Resolved fields + provider: 'v8', + reporter: this.resolveReporters(config.reporter || coverageConfigDefaults.reporter), + reportsDirectory: resolve(ctx.config.root, config.reportsDirectory || coverageConfigDefaults.reportsDirectory), + lines: config['100'] ? 100 : config.lines, + functions: config['100'] ? 100 : config.functions, + branches: config['100'] ? 100 : config.branches, + statements: config['100'] ? 100 : config.statements, + } + + this.testExclude = new _TestExclude({ + cwd: ctx.config.root, + include: typeof this.options.include === 'undefined' ? undefined : [...this.options.include], + exclude: [...defaultExclude, ...defaultInclude, ...this.options.exclude], + excludeNodeModules: true, + extension: this.options.extension, + }) + } + + resolveOptions() { + return this.options + } + + async clean(clean = true) { + if (clean && existsSync(this.options.reportsDirectory)) + await fs.rm(this.options.reportsDirectory, { recursive: true, force: true, maxRetries: 10 }) + + this.coverages = [] + } + + onAfterSuiteRun({ coverage }: AfterSuiteRunMeta) { + this.coverages.push(coverage as Profiler.TakePreciseCoverageReturnType) + } + + async reportCoverage({ allTestsRun }: ReportContext = {}) { + if (provider === 'stackblitz') + this.ctx.logger.log(c.blue(' % ') + c.yellow('@vitest/coverage-v8 does not work on Stackblitz. Report will be empty.')) + + const merged = mergeProcessCovs(this.coverages) + const scriptCoverages = merged.result.filter(result => this.testExclude.shouldInstrument(fileURLToPath(result.url))) + + if (this.options.all && allTestsRun) { + const coveredFiles = Array.from(scriptCoverages.map(r => r.url)) + const untestedFiles = await this.getUntestedFiles(coveredFiles) + + scriptCoverages.push(...untestedFiles) + } + + const converted = await Promise.all(scriptCoverages.map(async ({ url, functions }) => { + const sources = await this.getSources(url) + + // If no source map was found from vite-node we can assume this file was not run in the wrapper + const wrapperLength = sources.sourceMap ? WRAPPER_LENGTH : 0 + + const converter = v8ToIstanbul(url, wrapperLength, sources) + await converter.load() + + converter.applyCoverage(functions) + return converter.toIstanbul() + })) + + const mergedCoverage = converted.reduce((coverage, previousCoverageMap) => { + const map = libCoverage.createCoverageMap(coverage) + map.merge(previousCoverageMap) + return map + }, libCoverage.createCoverageMap({})) + + const sourceMapStore = libSourceMaps.createSourceMapStore() + const coverageMap: CoverageMap = await sourceMapStore.transformCoverage(mergedCoverage) + + const context = libReport.createContext({ + dir: this.options.reportsDirectory, + coverageMap, + sourceFinder: sourceMapStore.sourceFinder, + watermarks: this.options.watermarks, + }) + + for (const reporter of this.options.reporter) { + reports.create(reporter[0], { + skipFull: this.options.skipFull, + projectRoot: this.ctx.config.root, + ...reporter[1], + }).execute(context) + } + + if (this.options.branches + || this.options.functions + || this.options.lines + || this.options.statements) { + this.checkThresholds({ + coverageMap, + thresholds: { + branches: this.options.branches, + functions: this.options.functions, + lines: this.options.lines, + statements: this.options.statements, + }, + perFile: this.options.perFile, + }) + } + + if (this.options.thresholdAutoUpdate && allTestsRun) { + this.updateThresholds({ + coverageMap, + thresholds: { + branches: this.options.branches, + functions: this.options.functions, + lines: this.options.lines, + statements: this.options.statements, + }, + perFile: this.options.perFile, + configurationFile: this.ctx.server.config.configFile, + }) + } + } + + private async getUntestedFiles(testedFiles: string[]): Promise { + const includedFiles = await this.testExclude.glob(this.ctx.config.root) + const uncoveredFiles = includedFiles + .map(file => pathToFileURL(resolve(this.ctx.config.root, file))) + .filter(file => !testedFiles.includes(file.href)) + + return await Promise.all(uncoveredFiles.map(async (uncoveredFile) => { + const { source } = await this.getSources(uncoveredFile.href) + + return { + url: uncoveredFile.href, + scriptId: '0', + // Create a made up function to mark whole file as uncovered. Note that this does not exist in source maps. + functions: [{ + ranges: [{ + startOffset: 0, + endOffset: source.length, + count: 0, + }], + isBlockCoverage: true, + // 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 + functionName: '(empty-report)', + }], + } + })) + } + + private async getSources(url: string): Promise<{ + source: string + originalSource?: string + sourceMap?: { sourcemap: EncodedSourceMap } + }> { + const filePath = normalize(fileURLToPath(url)) + const transformResult = this.ctx.projects + .map(project => project.vitenode.fetchCache.get(filePath)?.result) + .filter(Boolean) + .shift() + + const map = transformResult?.map + const code = transformResult?.code + const sourcesContent = map?.sourcesContent?.[0] || await fs.readFile(filePath, 'utf-8') + + // These can be uncovered files included by "all: true" or files that are loaded outside vite-node + if (!map) + return { source: code || sourcesContent } + + return { + originalSource: sourcesContent, + source: code || sourcesContent, + sourceMap: { + sourcemap: removeViteHelpersFromSourceMaps(code, { + ...map, + version: 3, + sources: [url], + sourcesContent: [sourcesContent], + }), + }, + } + } +} + +/** + * Remove generated code from the source maps: + * - Vite's export helpers: e.g. `Object.defineProperty(__vite_ssr_exports__, "sum", { enumerable: true, configurable: true, get(){ return sum }});` + */ +function removeViteHelpersFromSourceMaps(source: string | undefined, map: EncodedSourceMap) { + if (!source || !source.match(VITE_EXPORTS_LINE_PATTERN)) + return map + + const sourceWithoutHelpers = new MagicString(source) + sourceWithoutHelpers.replaceAll(VITE_EXPORTS_LINE_PATTERN, '\n') + + const mapWithoutHelpers = sourceWithoutHelpers.generateMap({ + hires: true, + }) + + // A merged source map where the first one excludes helpers + const combinedMap = remapping( + [{ ...mapWithoutHelpers, version: 3 }, map], + () => null, + ) + + return combinedMap as EncodedSourceMap +} diff --git a/packages/coverage-v8/src/takeCoverage.ts b/packages/coverage-v8/src/takeCoverage.ts new file mode 100644 index 000000000000..ea49e52e17ea --- /dev/null +++ b/packages/coverage-v8/src/takeCoverage.ts @@ -0,0 +1,51 @@ +/* + * For details about the Profiler.* messages see https://chromedevtools.github.io/devtools-protocol/v8/Profiler/ +*/ + +import inspector from 'node:inspector' +import type { Profiler } from 'node:inspector' +import { provider } from 'std-env' + +const session = new inspector.Session() + +export function startCoverage() { + session.connect() + session.post('Profiler.enable') + session.post('Profiler.startPreciseCoverage', { + callCount: true, + detailed: true, + }) +} + +export async function takeCoverage() { + return new Promise((resolve, reject) => { + session.post('Profiler.takePreciseCoverage', async (error, coverage) => { + if (error) + return reject(error) + + // Reduce amount of data sent over rpc by doing some early result filtering + const result = coverage.result.filter(filterResult) + + resolve({ result }) + }) + + if (provider === 'stackblitz') + resolve({ result: [] }) + }) +} + +export function stopCoverage() { + session.post('Profiler.stopPreciseCoverage') + session.post('Profiler.disable') + session.disconnect() +} + +function filterResult(coverage: Profiler.ScriptCoverage): boolean { + if (!coverage.url.startsWith('file://')) + return false + + if (coverage.url.includes('/node_modules/')) + return false + + return true +} diff --git a/packages/vitest/src/defaults.ts b/packages/vitest/src/defaults.ts index e422d315b38e..b75f658eb304 100644 --- a/packages/vitest/src/defaults.ts +++ b/packages/vitest/src/defaults.ts @@ -26,7 +26,7 @@ const defaultCoverageExcludes = [ // These are the generic defaults for coverage. Providers may also set some provider specific defaults. export const coverageConfigDefaults: ResolvedCoverageOptions = { - provider: 'c8', + provider: 'v8', enabled: false, clean: true, cleanOnRerun: true, diff --git a/packages/vitest/src/integrations/coverage.ts b/packages/vitest/src/integrations/coverage.ts index 983334fafd20..db35ec79cbdc 100644 --- a/packages/vitest/src/integrations/coverage.ts +++ b/packages/vitest/src/integrations/coverage.ts @@ -6,6 +6,7 @@ interface Loader { export const CoverageProviderMap: Record = { c8: '@vitest/coverage-c8', + v8: '@vitest/coverage-v8', istanbul: '@vitest/coverage-istanbul', } @@ -15,7 +16,7 @@ async function resolveCoverageProviderModule(options: CoverageOptions | undefine const provider = options.provider - if (provider === 'c8' || provider === 'istanbul') { + if (provider === 'c8' || provider === 'v8' || provider === 'istanbul') { const { default: coverageModule } = await loader.executeId(CoverageProviderMap[provider]) if (!coverageModule) diff --git a/packages/vitest/src/node/cli-api.ts b/packages/vitest/src/node/cli-api.ts index 542a6e61982c..84b280f79b1d 100644 --- a/packages/vitest/src/node/cli-api.ts +++ b/packages/vitest/src/node/cli-api.ts @@ -59,11 +59,21 @@ export async function startVitest( const ctx = await createVitest(mode, options, viteOverrides) if (mode === 'test' && ctx.config.coverage.enabled) { - const provider = ctx.config.coverage.provider || 'c8' + const provider = ctx.config.coverage.provider || 'v8' const requiredPackages = CoverageProviderMap[provider] if (requiredPackages) { - if (!await ensurePackageInstalled(requiredPackages, root)) { + // Remove this message once support for @vitest/coverage-c8 has been removed completely + const defaultProviderInfo = 'Default coverage provider has changed from "c8" to "v8". ' + + 'New package is required to be installed. ' + + 'To use the old deprecated coverage provider use "--coverage.provider c8" option.\n' + + 'See https://github.com/vitest-dev/vitest/pull/3339 for more information.\n\n' + + const isUsingDefaultProvider + = ctx.server.config.test?.coverage?.provider === undefined + && options.coverage?.provider === undefined + + if (!await ensurePackageInstalled(requiredPackages, root, isUsingDefaultProvider ? defaultProviderInfo : undefined)) { process.exitCode = 1 return ctx } diff --git a/packages/vitest/src/node/config.ts b/packages/vitest/src/node/config.ts index e1648daccc8f..7351dc070ede 100644 --- a/packages/vitest/src/node/config.ts +++ b/packages/vitest/src/node/config.ts @@ -120,6 +120,9 @@ export function resolveConfig( if (resolved.coverage.provider === 'c8' && resolved.coverage.enabled && isBrowserEnabled(resolved)) throw new Error('@vitest/coverage-c8 does not work with --browser. Use @vitest/coverage-istanbul instead') + if (resolved.coverage.provider === 'v8' && resolved.coverage.enabled && isBrowserEnabled(resolved)) + throw new Error('@vitest/coverage-v8 does not work with --browser. Use @vitest/coverage-istanbul instead') + resolved.deps = resolved.deps || {} // vitenode will try to import such file with native node, // but then our mocker will not work properly diff --git a/packages/vitest/src/node/pkg.ts b/packages/vitest/src/node/pkg.ts index f1de8b90d01c..36475498107a 100644 --- a/packages/vitest/src/node/pkg.ts +++ b/packages/vitest/src/node/pkg.ts @@ -9,12 +9,16 @@ const __dirname = url.fileURLToPath(new URL('.', import.meta.url)) export async function ensurePackageInstalled( dependency: string, root: string, + errorMessage?: string, ) { if (isPackageExists(dependency, { paths: [root, __dirname] })) return true const promptInstall = !isCI && process.stdout.isTTY + if (errorMessage) + process.stderr.write(c.red(errorMessage)) + process.stderr.write(c.red(`${c.inverse(c.red(' MISSING DEP '))} Can not find dependency '${dependency}'\n\n`)) if (!promptInstall) diff --git a/packages/vitest/src/types/coverage.ts b/packages/vitest/src/types/coverage.ts index 65cf0d9e800b..48d983813673 100644 --- a/packages/vitest/src/types/coverage.ts +++ b/packages/vitest/src/types/coverage.ts @@ -61,13 +61,14 @@ type CoverageReporterWithOptions] : never -type Provider = 'c8' | 'istanbul' | 'custom' | undefined +type Provider = 'c8' | 'v8' | 'istanbul' | 'custom' | undefined export type CoverageOptions = T extends 'istanbul' ? ({ provider: T } & CoverageIstanbulOptions) : T extends 'c8' ? ({ provider: T } & CoverageC8Options) : - T extends 'custom' ? ({ provider: T } & CustomProviderOptions) : - ({ provider?: T } & (CoverageC8Options)) + T extends 'v8' ? ({ provider: T } & CoverageV8Options) : + T extends 'custom' ? ({ provider: T } & CustomProviderOptions) : + ({ provider?: T } & (CoverageC8Options)) /** Fields that have default values. Internally these will always be defined. */ type FieldsWithDefaultValues = @@ -257,6 +258,15 @@ export interface CoverageC8Options extends BaseCoverageOptions { 100?: boolean } +export interface CoverageV8Options extends BaseCoverageOptions { + /** + * Shortcut for `--check-coverage --lines 100 --functions 100 --branches 100 --statements 100` + * + * @default false + */ + 100?: boolean +} + export interface CustomProviderOptions extends Pick { /** Name of the module or path to a file to load the custom provider from */ customProviderModule: string diff --git a/packages/vitest/src/utils/coverage.ts b/packages/vitest/src/utils/coverage.ts index 20ff0a709ff1..7379243fd584 100644 --- a/packages/vitest/src/utils/coverage.ts +++ b/packages/vitest/src/utils/coverage.ts @@ -1,4 +1,5 @@ import { readFileSync, writeFileSync } from 'node:fs' +import { relative } from 'pathe' import type { CoverageMap } from 'istanbul-lib-coverage' import type { BaseCoverageOptions, ResolvedCoverageOptions } from '../types' @@ -61,6 +62,59 @@ export class BaseCoverageProvider { } } + /** + * Checked collected coverage against configured thresholds. Sets exit code to 1 when thresholds not reached. + */ + checkThresholds({ coverageMap, thresholds, perFile }: { + coverageMap: CoverageMap + thresholds: Record + perFile?: boolean + }) { + // Construct list of coverage summaries where thresholds are compared against + const summaries = perFile + ? coverageMap.files() + .map((file: string) => ({ + file, + summary: coverageMap.fileCoverageFor(file).toSummary(), + })) + : [{ + file: null, + summary: coverageMap.getCoverageSummary(), + }] + + // Check thresholds of each summary + for (const { summary, file } of summaries) { + for (const thresholdKey of ['lines', 'functions', 'statements', 'branches'] as const) { + const threshold = thresholds[thresholdKey] + + if (threshold !== undefined) { + const coverage = summary.data[thresholdKey].pct + + if (coverage < threshold) { + process.exitCode = 1 + + /* + * Generate error message based on perFile flag: + * - ERROR: Coverage for statements (33.33%) does not meet threshold (85%) for src/math.ts + * - ERROR: Coverage for statements (50%) does not meet global threshold (85%) + */ + let errorMessage = `ERROR: Coverage for ${thresholdKey} (${coverage}%) does not meet` + + if (!perFile) + errorMessage += ' global' + + errorMessage += ` threshold (${threshold}%)` + + if (perFile && file) + errorMessage += ` for ${relative('./', file).replace(/\\/g, '/')}` + + console.error(errorMessage) + } + } + } + } + } + /** * Resolve reporters from various configuration options */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0cb6dcb68d31..6b00a8ea56f1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,6 +51,9 @@ importers: '@vitest/coverage-istanbul': specifier: workspace:* version: link:packages/coverage-istanbul + '@vitest/coverage-v8': + specifier: workspace:* + version: link:packages/coverage-v8 '@vitest/ui': specifier: workspace:* version: link:packages/ui @@ -958,6 +961,64 @@ importers: specifier: workspace:* version: link:../vitest + packages/coverage-v8: + dependencies: + '@ampproject/remapping': + specifier: ^2.2.1 + version: 2.2.1 + '@bcoe/v8-coverage': + specifier: ^0.2.3 + version: 0.2.3 + istanbul-lib-coverage: + specifier: ^3.2.0 + version: 3.2.0 + istanbul-lib-report: + specifier: ^3.0.0 + version: 3.0.0 + istanbul-lib-source-maps: + specifier: ^4.0.1 + version: 4.0.1 + istanbul-reports: + specifier: ^3.1.5 + version: 3.1.5 + magic-string: + specifier: ^0.30.0 + version: 0.30.0 + picocolors: + specifier: ^1.0.0 + version: 1.0.0 + std-env: + specifier: ^3.3.2 + version: 3.3.2 + test-exclude: + specifier: ^6.0.0 + version: 6.0.0 + v8-to-istanbul: + specifier: ^9.1.0 + version: 9.1.0 + devDependencies: + '@types/istanbul-lib-coverage': + specifier: ^2.0.4 + version: 2.0.4 + '@types/istanbul-lib-report': + specifier: ^3.0.0 + version: 3.0.0 + '@types/istanbul-lib-source-maps': + specifier: ^4.0.1 + version: 4.0.1 + '@types/istanbul-reports': + specifier: ^3.0.1 + version: 3.0.1 + pathe: + specifier: ^1.1.0 + version: 1.1.0 + vite-node: + specifier: workspace:* + version: link:../vite-node + vitest: + specifier: workspace:* + version: link:../vitest + packages/expect: dependencies: '@vitest/spy': @@ -1528,6 +1589,15 @@ importers: '@vitest/browser': specifier: workspace:* version: link:../../packages/browser + '@vitest/coverage-c8': + specifier: workspace:* + version: link:../../packages/coverage-c8 + '@vitest/coverage-istanbul': + specifier: workspace:* + version: link:../../packages/coverage-istanbul + '@vitest/coverage-v8': + specifier: workspace:* + version: link:../../packages/coverage-v8 '@vue/test-utils': specifier: latest version: 2.3.2(vue@3.3.4) @@ -11363,7 +11433,7 @@ packages: istanbul-reports: 3.1.5 rimraf: 3.0.2 test-exclude: 6.0.0 - v8-to-istanbul: 9.0.1 + v8-to-istanbul: 9.1.0 yargs: 16.2.0 yargs-parser: 20.2.9 dev: true @@ -11382,7 +11452,7 @@ packages: istanbul-reports: 3.1.5 rimraf: 3.0.2 test-exclude: 6.0.0 - v8-to-istanbul: 9.0.1 + v8-to-istanbul: 9.1.0 yargs: 16.2.0 yargs-parser: 20.2.9 @@ -23949,8 +24019,8 @@ packages: source-map: 0.7.4 dev: true - /v8-to-istanbul@9.0.1: - resolution: {integrity: sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==} + /v8-to-istanbul@9.1.0: + resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} engines: {node: '>=10.12.0'} dependencies: '@jridgewell/trace-mapping': 0.3.18 diff --git a/test/config/test/failures.test.ts b/test/config/test/failures.test.ts index 0ad3abf50ed9..0ad1bf064faa 100644 --- a/test/config/test/failures.test.ts +++ b/test/config/test/failures.test.ts @@ -49,11 +49,17 @@ test('inspect-brk cannot be used with threads', async () => { }) test('c8 coverage provider cannot be used with browser', async () => { - const { stderr } = await runVitest({ coverage: { enabled: true }, browser: { enabled: true, name: 'chrome' } }) + const { stderr } = await runVitest({ coverage: { enabled: true, provider: 'c8' }, browser: { enabled: true, name: 'chrome' } }) expect(stderr).toMatch('Error: @vitest/coverage-c8 does not work with --browser. Use @vitest/coverage-istanbul instead') }) +test('v8 coverage provider cannot be used with browser', async () => { + const { stderr } = await runVitest({ coverage: { enabled: true }, browser: { enabled: true, name: 'chrome' } }) + + expect(stderr).toMatch('Error: @vitest/coverage-v8 does not work with --browser. Use @vitest/coverage-istanbul instead') +}) + test('version number is printed when coverage provider fails to load', async () => { const { stderr, stdout } = await runVitest({ coverage: { diff --git a/test/coverage-test/coverage-report-tests/__snapshots__/v8.report.test.ts.snap b/test/coverage-test/coverage-report-tests/__snapshots__/v8.report.test.ts.snap new file mode 100644 index 000000000000..24a6118f7547 --- /dev/null +++ b/test/coverage-test/coverage-report-tests/__snapshots__/v8.report.test.ts.snap @@ -0,0 +1,3323 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`v8 json report 1`] = ` +{ + "/src/Counter/Counter.component.ts": { + "all": false, + "b": { + "0": [ + 1, + ], + "1": [ + 2, + ], + }, + "branchMap": { + "0": { + "line": 6, + "loc": { + "end": { + "column": 4, + "line": 9, + }, + "start": { + "column": 2, + "line": 6, + }, + }, + "locations": [ + { + "end": { + "column": 4, + "line": 9, + }, + "start": { + "column": 2, + "line": 6, + }, + }, + ], + "type": "branch", + }, + "1": { + "line": 16, + "loc": { + "end": { + "column": 6, + "line": 18, + }, + "start": { + "column": 4, + "line": 16, + }, + }, + "locations": [ + { + "end": { + "column": 6, + "line": 18, + }, + "start": { + "column": 4, + "line": 16, + }, + }, + ], + "type": "branch", + }, + }, + "f": { + "0": 1, + "1": 0, + "2": 2, + "3": 0, + }, + "fnMap": { + "0": { + "decl": { + "end": { + "column": 4, + "line": 9, + }, + "start": { + "column": 2, + "line": 6, + }, + }, + "line": 6, + "loc": { + "end": { + "column": 4, + "line": 9, + }, + "start": { + "column": 2, + "line": 6, + }, + }, + "name": "setup", + }, + "1": { + "decl": { + "end": { + "column": 6, + "line": 14, + }, + "start": { + "column": 4, + "line": 12, + }, + }, + "line": 12, + "loc": { + "end": { + "column": 6, + "line": 14, + }, + "start": { + "column": 4, + "line": 12, + }, + }, + "name": "uncoveredMethod", + }, + "2": { + "decl": { + "end": { + "column": 6, + "line": 18, + }, + "start": { + "column": 4, + "line": 16, + }, + }, + "line": 16, + "loc": { + "end": { + "column": 6, + "line": 18, + }, + "start": { + "column": 4, + "line": 16, + }, + }, + "name": "coveredMethod", + }, + "3": { + "decl": { + "end": { + "column": 6, + "line": 22, + }, + "start": { + "column": 4, + "line": 20, + }, + }, + "line": 20, + "loc": { + "end": { + "column": 6, + "line": 22, + }, + "start": { + "column": 4, + "line": 20, + }, + }, + "name": "uncoveredMethodUsingImportMeta", + }, + }, + "path": "/src/Counter/Counter.component.ts", + "s": { + "0": 1, + "1": 1, + "10": 1, + "11": 1, + "12": 0, + "13": 0, + "14": 1, + "15": 1, + "16": 2, + "17": 2, + "18": 1, + "19": 1, + "2": 1, + "20": 0, + "21": 0, + "22": 1, + "23": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + }, + "statementMap": { + "0": { + "end": { + "column": 42, + "line": 1, + }, + "start": { + "column": 0, + "line": 1, + }, + }, + "1": { + "end": { + "column": 0, + "line": 2, + }, + "start": { + "column": 0, + "line": 2, + }, + }, + "10": { + "end": { + "column": 12, + "line": 11, + }, + "start": { + "column": 0, + "line": 11, + }, + }, + "11": { + "end": { + "column": 23, + "line": 12, + }, + "start": { + "column": 0, + "line": 12, + }, + }, + "12": { + "end": { + "column": 46, + "line": 13, + }, + "start": { + "column": 0, + "line": 13, + }, + }, + "13": { + "end": { + "column": 6, + "line": 14, + }, + "start": { + "column": 0, + "line": 14, + }, + }, + "14": { + "end": { + "column": 0, + "line": 15, + }, + "start": { + "column": 0, + "line": 15, + }, + }, + "15": { + "end": { + "column": 21, + "line": 16, + }, + "start": { + "column": 0, + "line": 16, + }, + }, + "16": { + "end": { + "column": 42, + "line": 17, + }, + "start": { + "column": 0, + "line": 17, + }, + }, + "17": { + "end": { + "column": 6, + "line": 18, + }, + "start": { + "column": 0, + "line": 18, + }, + }, + "18": { + "end": { + "column": 0, + "line": 19, + }, + "start": { + "column": 0, + "line": 19, + }, + }, + "19": { + "end": { + "column": 38, + "line": 20, + }, + "start": { + "column": 0, + "line": 20, + }, + }, + "2": { + "end": { + "column": 32, + "line": 3, + }, + "start": { + "column": 0, + "line": 3, + }, + }, + "20": { + "end": { + "column": 94, + "line": 21, + }, + "start": { + "column": 0, + "line": 21, + }, + }, + "21": { + "end": { + "column": 6, + "line": 22, + }, + "start": { + "column": 0, + "line": 22, + }, + }, + "22": { + "end": { + "column": 4, + "line": 23, + }, + "start": { + "column": 0, + "line": 23, + }, + }, + "23": { + "end": { + "column": 2, + "line": 24, + }, + "start": { + "column": 0, + "line": 24, + }, + }, + "3": { + "end": { + "column": 18, + "line": 4, + }, + "start": { + "column": 0, + "line": 4, + }, + }, + "4": { + "end": { + "column": 0, + "line": 5, + }, + "start": { + "column": 0, + "line": 5, + }, + }, + "5": { + "end": { + "column": 11, + "line": 6, + }, + "start": { + "column": 0, + "line": 6, + }, + }, + "6": { + "end": { + "column": 24, + "line": 7, + }, + "start": { + "column": 0, + "line": 7, + }, + }, + "7": { + "end": { + "column": 20, + "line": 8, + }, + "start": { + "column": 0, + "line": 8, + }, + }, + "8": { + "end": { + "column": 4, + "line": 9, + }, + "start": { + "column": 0, + "line": 9, + }, + }, + "9": { + "end": { + "column": 0, + "line": 10, + }, + "start": { + "column": 0, + "line": 10, + }, + }, + }, + }, + "/src/Counter/Counter.vue": { + "all": false, + "b": { + "0": [ + 1, + ], + "1": [ + 1, + ], + }, + "branchMap": { + "0": { + "line": 8, + "loc": { + "end": { + "column": 25, + "line": 8, + }, + "start": { + "column": 18, + "line": 8, + }, + }, + "locations": [ + { + "end": { + "column": 25, + "line": 8, + }, + "start": { + "column": 18, + "line": 8, + }, + }, + ], + "type": "branch", + }, + "1": { + "line": 8, + "loc": { + "end": { + "column": 25, + "line": 8, + }, + "start": { + "column": 18, + "line": 8, + }, + }, + "locations": [ + { + "end": { + "column": 25, + "line": 8, + }, + "start": { + "column": 18, + "line": 8, + }, + }, + ], + "type": "branch", + }, + }, + "f": { + "0": 1, + }, + "fnMap": { + "0": { + "decl": { + "end": { + "column": 25, + "line": 8, + }, + "start": { + "column": 18, + "line": 8, + }, + }, + "line": 8, + "loc": { + "end": { + "column": 25, + "line": 8, + }, + "start": { + "column": 18, + "line": 8, + }, + }, + "name": "__vite_ssr_import_1__.createElementBlock.__vite_ssr_import_1__.createElementVNode.onClick._cache.._cache.", + }, + }, + "path": "/src/Counter/Counter.vue", + "s": { + "0": 1, + "1": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + }, + "statementMap": { + "0": { + "end": { + "column": 56, + "line": 1, + }, + "start": { + "column": 0, + "line": 1, + }, + }, + "1": { + "end": { + "column": 0, + "line": 2, + }, + "start": { + "column": 0, + "line": 2, + }, + }, + "10": { + "end": { + "column": 14, + "line": 11, + }, + "start": { + "column": 0, + "line": 11, + }, + }, + "11": { + "end": { + "column": 25, + "line": 12, + }, + "start": { + "column": 0, + "line": 12, + }, + }, + "12": { + "end": { + "column": 8, + "line": 13, + }, + "start": { + "column": 0, + "line": 13, + }, + }, + "13": { + "end": { + "column": 11, + "line": 14, + }, + "start": { + "column": 0, + "line": 14, + }, + }, + "2": { + "end": { + "column": 10, + "line": 3, + }, + "start": { + "column": 0, + "line": 3, + }, + }, + "3": { + "end": { + "column": 27, + "line": 4, + }, + "start": { + "column": 0, + "line": 4, + }, + }, + "4": { + "end": { + "column": 15, + "line": 5, + }, + "start": { + "column": 0, + "line": 5, + }, + }, + "5": { + "end": { + "column": 11, + "line": 6, + }, + "start": { + "column": 0, + "line": 6, + }, + }, + "6": { + "end": { + "column": 0, + "line": 7, + }, + "start": { + "column": 0, + "line": 7, + }, + }, + "7": { + "end": { + "column": 25, + "line": 8, + }, + "start": { + "column": 0, + "line": 8, + }, + }, + "8": { + "end": { + "column": 27, + "line": 9, + }, + "start": { + "column": 0, + "line": 9, + }, + }, + "9": { + "end": { + "column": 8, + "line": 10, + }, + "start": { + "column": 0, + "line": 10, + }, + }, + }, + }, + "/src/Counter/index.ts": { + "all": false, + "b": {}, + "branchMap": {}, + "f": {}, + "fnMap": {}, + "path": "/src/Counter/index.ts", + "s": { + "0": 1, + "1": 1, + "2": 1, + "3": 1, + }, + "statementMap": { + "0": { + "end": { + "column": 50, + "line": 1, + }, + "start": { + "column": 0, + "line": 1, + }, + }, + "1": { + "end": { + "column": 38, + "line": 2, + }, + "start": { + "column": 0, + "line": 2, + }, + }, + "2": { + "end": { + "column": 0, + "line": 3, + }, + "start": { + "column": 0, + "line": 3, + }, + }, + "3": { + "end": { + "column": 39, + "line": 4, + }, + "start": { + "column": 0, + "line": 4, + }, + }, + }, + }, + "/src/Defined.vue": { + "all": false, + "b": { + "0": [ + 0, + ], + }, + "branchMap": { + "0": { + "line": 6, + "loc": { + "end": { + "column": 36, + "line": 8, + }, + "start": { + "column": 33, + "line": 6, + }, + }, + "locations": [ + { + "end": { + "column": 36, + "line": 8, + }, + "start": { + "column": 33, + "line": 6, + }, + }, + ], + "type": "branch", + }, + }, + "f": {}, + "fnMap": {}, + "path": "/src/Defined.vue", + "s": { + "0": 1, + "1": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 1, + "17": 1, + "18": 1, + "19": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 0, + "7": 0, + "8": 1, + "9": 1, + }, + "statementMap": { + "0": { + "end": { + "column": 24, + "line": 1, + }, + "start": { + "column": 0, + "line": 1, + }, + }, + "1": { + "end": { + "column": 27, + "line": 2, + }, + "start": { + "column": 0, + "line": 2, + }, + }, + "10": { + "end": { + "column": 10, + "line": 11, + }, + "start": { + "column": 0, + "line": 11, + }, + }, + "11": { + "end": { + "column": 15, + "line": 12, + }, + "start": { + "column": 0, + "line": 12, + }, + }, + "12": { + "end": { + "column": 11, + "line": 13, + }, + "start": { + "column": 0, + "line": 13, + }, + }, + "13": { + "end": { + "column": 0, + "line": 14, + }, + "start": { + "column": 0, + "line": 14, + }, + }, + "14": { + "end": { + "column": 90, + "line": 15, + }, + "start": { + "column": 0, + "line": 15, + }, + }, + "15": { + "end": { + "column": 25, + "line": 16, + }, + "start": { + "column": 0, + "line": 16, + }, + }, + "16": { + "end": { + "column": 6, + "line": 17, + }, + "start": { + "column": 0, + "line": 17, + }, + }, + "17": { + "end": { + "column": 25, + "line": 18, + }, + "start": { + "column": 0, + "line": 18, + }, + }, + "18": { + "end": { + "column": 1, + "line": 19, + }, + "start": { + "column": 0, + "line": 19, + }, + }, + "19": { + "end": { + "column": 8, + "line": 20, + }, + "start": { + "column": 0, + "line": 20, + }, + }, + "2": { + "end": { + "column": 0, + "line": 3, + }, + "start": { + "column": 0, + "line": 3, + }, + }, + "3": { + "end": { + "column": 31, + "line": 4, + }, + "start": { + "column": 0, + "line": 4, + }, + }, + "4": { + "end": { + "column": 12, + "line": 5, + }, + "start": { + "column": 0, + "line": 5, + }, + }, + "5": { + "end": { + "column": 34, + "line": 6, + }, + "start": { + "column": 0, + "line": 6, + }, + }, + "6": { + "end": { + "column": 4, + "line": 7, + }, + "start": { + "column": 0, + "line": 7, + }, + }, + "7": { + "end": { + "column": 36, + "line": 8, + }, + "start": { + "column": 0, + "line": 8, + }, + }, + "8": { + "end": { + "column": 9, + "line": 9, + }, + "start": { + "column": 0, + "line": 9, + }, + }, + "9": { + "end": { + "column": 0, + "line": 10, + }, + "start": { + "column": 0, + "line": 10, + }, + }, + }, + }, + "/src/Hello.vue": { + "all": false, + "b": { + "0": [ + 3, + ], + }, + "branchMap": { + "0": { + "line": 7, + "loc": { + "end": { + "column": 55, + "line": 7, + }, + "start": { + "column": 24, + "line": 7, + }, + }, + "locations": [ + { + "end": { + "column": 55, + "line": 7, + }, + "start": { + "column": 24, + "line": 7, + }, + }, + ], + "type": "branch", + }, + }, + "f": {}, + "fnMap": {}, + "path": "/src/Hello.vue", + "s": { + "0": 1, + "1": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + }, + "statementMap": { + "0": { + "end": { + "column": 24, + "line": 1, + }, + "start": { + "column": 0, + "line": 1, + }, + }, + "1": { + "end": { + "column": 35, + "line": 2, + }, + "start": { + "column": 0, + "line": 2, + }, + }, + "10": { + "end": { + "column": 0, + "line": 11, + }, + "start": { + "column": 0, + "line": 11, + }, + }, + "11": { + "end": { + "column": 10, + "line": 12, + }, + "start": { + "column": 0, + "line": 12, + }, + }, + "12": { + "end": { + "column": 53, + "line": 13, + }, + "start": { + "column": 0, + "line": 13, + }, + }, + "13": { + "end": { + "column": 30, + "line": 14, + }, + "start": { + "column": 0, + "line": 14, + }, + }, + "14": { + "end": { + "column": 6, + "line": 15, + }, + "start": { + "column": 0, + "line": 15, + }, + }, + "15": { + "end": { + "column": 11, + "line": 16, + }, + "start": { + "column": 0, + "line": 16, + }, + }, + "16": { + "end": { + "column": 11, + "line": 17, + }, + "start": { + "column": 0, + "line": 17, + }, + }, + "2": { + "end": { + "column": 0, + "line": 3, + }, + "start": { + "column": 0, + "line": 3, + }, + }, + "3": { + "end": { + "column": 46, + "line": 4, + }, + "start": { + "column": 0, + "line": 4, + }, + }, + "4": { + "end": { + "column": 0, + "line": 5, + }, + "start": { + "column": 0, + "line": 5, + }, + }, + "5": { + "end": { + "column": 20, + "line": 6, + }, + "start": { + "column": 0, + "line": 6, + }, + }, + "6": { + "end": { + "column": 56, + "line": 7, + }, + "start": { + "column": 0, + "line": 7, + }, + }, + "7": { + "end": { + "column": 0, + "line": 8, + }, + "start": { + "column": 0, + "line": 8, + }, + }, + "8": { + "end": { + "column": 19, + "line": 9, + }, + "start": { + "column": 0, + "line": 9, + }, + }, + "9": { + "end": { + "column": 9, + "line": 10, + }, + "start": { + "column": 0, + "line": 10, + }, + }, + }, + }, + "/src/function-count.ts": { + "all": false, + "b": { + "0": [ + 1, + ], + "1": [ + 1, + ], + "2": [ + 1, + ], + }, + "branchMap": { + "0": { + "line": 11, + "loc": { + "end": { + "column": 1, + "line": 13, + }, + "start": { + "column": 0, + "line": 11, + }, + }, + "locations": [ + { + "end": { + "column": 1, + "line": 13, + }, + "start": { + "column": 0, + "line": 11, + }, + }, + ], + "type": "branch", + }, + "1": { + "line": 18, + "loc": { + "end": { + "column": 1, + "line": 21, + }, + "start": { + "column": 7, + "line": 18, + }, + }, + "locations": [ + { + "end": { + "column": 1, + "line": 21, + }, + "start": { + "column": 7, + "line": 18, + }, + }, + ], + "type": "branch", + }, + "2": { + "line": 34, + "loc": { + "end": { + "column": 1, + "line": 36, + }, + "start": { + "column": 0, + "line": 34, + }, + }, + "locations": [ + { + "end": { + "column": 1, + "line": 36, + }, + "start": { + "column": 0, + "line": 34, + }, + }, + ], + "type": "branch", + }, + }, + "f": { + "0": 1, + "1": 1, + "2": 0, + "3": 0, + "4": 1, + }, + "fnMap": { + "0": { + "decl": { + "end": { + "column": 1, + "line": 13, + }, + "start": { + "column": 0, + "line": 11, + }, + }, + "line": 11, + "loc": { + "end": { + "column": 1, + "line": 13, + }, + "start": { + "column": 0, + "line": 11, + }, + }, + "name": "first", + }, + "1": { + "decl": { + "end": { + "column": 1, + "line": 21, + }, + "start": { + "column": 7, + "line": 18, + }, + }, + "line": 18, + "loc": { + "end": { + "column": 1, + "line": 21, + }, + "start": { + "column": 7, + "line": 18, + }, + }, + "name": "second", + }, + "2": { + "decl": { + "end": { + "column": 1, + "line": 26, + }, + "start": { + "column": 7, + "line": 24, + }, + }, + "line": 24, + "loc": { + "end": { + "column": 1, + "line": 26, + }, + "start": { + "column": 7, + "line": 24, + }, + }, + "name": "third", + }, + "3": { + "decl": { + "end": { + "column": 1, + "line": 31, + }, + "start": { + "column": 0, + "line": 29, + }, + }, + "line": 29, + "loc": { + "end": { + "column": 1, + "line": 31, + }, + "start": { + "column": 0, + "line": 29, + }, + }, + "name": "fourth", + }, + "4": { + "decl": { + "end": { + "column": 1, + "line": 36, + }, + "start": { + "column": 0, + "line": 34, + }, + }, + "line": 34, + "loc": { + "end": { + "column": 1, + "line": 36, + }, + "start": { + "column": 0, + "line": 34, + }, + }, + "name": "fifth", + }, + }, + "path": "/src/function-count.ts", + "s": { + "0": 1, + "1": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 1, + "17": 1, + "18": 1, + "19": 1, + "2": 1, + "20": 1, + "21": 1, + "22": 1, + "23": 1, + "24": 0, + "25": 0, + "26": 1, + "27": 1, + "28": 0, + "29": 0, + "3": 1, + "30": 0, + "31": 1, + "32": 1, + "33": 1, + "34": 1, + "35": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + }, + "statementMap": { + "0": { + "end": { + "column": 2, + "line": 1, + }, + "start": { + "column": 0, + "line": 1, + }, + }, + "1": { + "end": { + "column": 25, + "line": 2, + }, + "start": { + "column": 0, + "line": 2, + }, + }, + "10": { + "end": { + "column": 18, + "line": 11, + }, + "start": { + "column": 0, + "line": 11, + }, + }, + "11": { + "end": { + "column": 10, + "line": 12, + }, + "start": { + "column": 0, + "line": 12, + }, + }, + "12": { + "end": { + "column": 1, + "line": 13, + }, + "start": { + "column": 0, + "line": 13, + }, + }, + "13": { + "end": { + "column": 0, + "line": 14, + }, + "start": { + "column": 0, + "line": 14, + }, + }, + "14": { + "end": { + "column": 7, + "line": 15, + }, + "start": { + "column": 0, + "line": 15, + }, + }, + "15": { + "end": { + "column": 0, + "line": 16, + }, + "start": { + "column": 0, + "line": 16, + }, + }, + "16": { + "end": { + "column": 27, + "line": 17, + }, + "start": { + "column": 0, + "line": 17, + }, + }, + "17": { + "end": { + "column": 26, + "line": 18, + }, + "start": { + "column": 0, + "line": 18, + }, + }, + "18": { + "end": { + "column": 9, + "line": 19, + }, + "start": { + "column": 0, + "line": 19, + }, + }, + "19": { + "end": { + "column": 10, + "line": 20, + }, + "start": { + "column": 0, + "line": 20, + }, + }, + "2": { + "end": { + "column": 25, + "line": 3, + }, + "start": { + "column": 0, + "line": 3, + }, + }, + "20": { + "end": { + "column": 1, + "line": 21, + }, + "start": { + "column": 0, + "line": 21, + }, + }, + "21": { + "end": { + "column": 0, + "line": 22, + }, + "start": { + "column": 0, + "line": 22, + }, + }, + "22": { + "end": { + "column": 31, + "line": 23, + }, + "start": { + "column": 0, + "line": 23, + }, + }, + "23": { + "end": { + "column": 25, + "line": 24, + }, + "start": { + "column": 0, + "line": 24, + }, + }, + "24": { + "end": { + "column": 46, + "line": 25, + }, + "start": { + "column": 0, + "line": 25, + }, + }, + "25": { + "end": { + "column": 1, + "line": 26, + }, + "start": { + "column": 0, + "line": 26, + }, + }, + "26": { + "end": { + "column": 0, + "line": 27, + }, + "start": { + "column": 0, + "line": 27, + }, + }, + "27": { + "end": { + "column": 31, + "line": 28, + }, + "start": { + "column": 0, + "line": 28, + }, + }, + "28": { + "end": { + "column": 19, + "line": 29, + }, + "start": { + "column": 0, + "line": 29, + }, + }, + "29": { + "end": { + "column": 10, + "line": 30, + }, + "start": { + "column": 0, + "line": 30, + }, + }, + "3": { + "end": { + "column": 24, + "line": 4, + }, + "start": { + "column": 0, + "line": 4, + }, + }, + "30": { + "end": { + "column": 1, + "line": 31, + }, + "start": { + "column": 0, + "line": 31, + }, + }, + "31": { + "end": { + "column": 0, + "line": 32, + }, + "start": { + "column": 0, + "line": 32, + }, + }, + "32": { + "end": { + "column": 27, + "line": 33, + }, + "start": { + "column": 0, + "line": 33, + }, + }, + "33": { + "end": { + "column": 18, + "line": 34, + }, + "start": { + "column": 0, + "line": 34, + }, + }, + "34": { + "end": { + "column": 10, + "line": 35, + }, + "start": { + "column": 0, + "line": 35, + }, + }, + "35": { + "end": { + "column": 1, + "line": 36, + }, + "start": { + "column": 0, + "line": 36, + }, + }, + "4": { + "end": { + "column": 26, + "line": 5, + }, + "start": { + "column": 0, + "line": 5, + }, + }, + "5": { + "end": { + "column": 3, + "line": 6, + }, + "start": { + "column": 0, + "line": 6, + }, + }, + "6": { + "end": { + "column": 0, + "line": 7, + }, + "start": { + "column": 0, + "line": 7, + }, + }, + "7": { + "end": { + "column": 50, + "line": 8, + }, + "start": { + "column": 0, + "line": 8, + }, + }, + "8": { + "end": { + "column": 0, + "line": 9, + }, + "start": { + "column": 0, + "line": 9, + }, + }, + "9": { + "end": { + "column": 27, + "line": 10, + }, + "start": { + "column": 0, + "line": 10, + }, + }, + }, + }, + "/src/implicitElse.ts": { + "all": false, + "b": { + "0": [ + 1, + ], + }, + "branchMap": { + "0": { + "line": 1, + "loc": { + "end": { + "column": 1, + "line": 8, + }, + "start": { + "column": 7, + "line": 1, + }, + }, + "locations": [ + { + "end": { + "column": 1, + "line": 8, + }, + "start": { + "column": 7, + "line": 1, + }, + }, + ], + "type": "branch", + }, + }, + "f": { + "0": 1, + }, + "fnMap": { + "0": { + "decl": { + "end": { + "column": 1, + "line": 8, + }, + "start": { + "column": 7, + "line": 1, + }, + }, + "line": 1, + "loc": { + "end": { + "column": 1, + "line": 8, + }, + "start": { + "column": 7, + "line": 1, + }, + }, + "name": "implicitElse", + }, + }, + "path": "/src/implicitElse.ts", + "s": { + "0": 1, + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + }, + "statementMap": { + "0": { + "end": { + "column": 50, + "line": 1, + }, + "start": { + "column": 0, + "line": 1, + }, + }, + "1": { + "end": { + "column": 11, + "line": 2, + }, + "start": { + "column": 0, + "line": 2, + }, + }, + "2": { + "end": { + "column": 0, + "line": 3, + }, + "start": { + "column": 0, + "line": 3, + }, + }, + "3": { + "end": { + "column": 16, + "line": 4, + }, + "start": { + "column": 0, + "line": 4, + }, + }, + "4": { + "end": { + "column": 9, + "line": 5, + }, + "start": { + "column": 0, + "line": 5, + }, + }, + "5": { + "end": { + "column": 0, + "line": 6, + }, + "start": { + "column": 0, + "line": 6, + }, + }, + "6": { + "end": { + "column": 10, + "line": 7, + }, + "start": { + "column": 0, + "line": 7, + }, + }, + "7": { + "end": { + "column": 1, + "line": 8, + }, + "start": { + "column": 0, + "line": 8, + }, + }, + }, + }, + "/src/importEnv.ts": { + "all": false, + "b": { + "0": [ + 1, + ], + }, + "branchMap": { + "0": { + "line": 1, + "loc": { + "end": { + "column": 1, + "line": 3, + }, + "start": { + "column": 7, + "line": 1, + }, + }, + "locations": [ + { + "end": { + "column": 1, + "line": 3, + }, + "start": { + "column": 7, + "line": 1, + }, + }, + ], + "type": "branch", + }, + }, + "f": { + "0": 1, + }, + "fnMap": { + "0": { + "decl": { + "end": { + "column": 1, + "line": 3, + }, + "start": { + "column": 7, + "line": 1, + }, + }, + "line": 1, + "loc": { + "end": { + "column": 1, + "line": 3, + }, + "start": { + "column": 7, + "line": 1, + }, + }, + "name": "useImportEnv", + }, + }, + "path": "/src/importEnv.ts", + "s": { + "0": 1, + "1": 1, + "2": 1, + }, + "statementMap": { + "0": { + "end": { + "column": 32, + "line": 1, + }, + "start": { + "column": 0, + "line": 1, + }, + }, + "1": { + "end": { + "column": 46, + "line": 2, + }, + "start": { + "column": 0, + "line": 2, + }, + }, + "2": { + "end": { + "column": 1, + "line": 3, + }, + "start": { + "column": 0, + "line": 3, + }, + }, + }, + }, + "/src/index.mts": { + "all": false, + "b": { + "0": [ + 1, + ], + }, + "branchMap": { + "0": { + "line": 5, + "loc": { + "end": { + "column": 1, + "line": 7, + }, + "start": { + "column": 7, + "line": 5, + }, + }, + "locations": [ + { + "end": { + "column": 1, + "line": 7, + }, + "start": { + "column": 7, + "line": 5, + }, + }, + ], + "type": "branch", + }, + }, + "f": { + "0": 1, + }, + "fnMap": { + "0": { + "decl": { + "end": { + "column": 1, + "line": 7, + }, + "start": { + "column": 7, + "line": 5, + }, + }, + "line": 5, + "loc": { + "end": { + "column": 1, + "line": 7, + }, + "start": { + "column": 7, + "line": 5, + }, + }, + "name": "pythagoras", + }, + }, + "path": "/src/index.mts", + "s": { + "0": 1, + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + }, + "statementMap": { + "0": { + "end": { + "column": 45, + "line": 1, + }, + "start": { + "column": 0, + "line": 1, + }, + }, + "1": { + "end": { + "column": 0, + "line": 2, + }, + "start": { + "column": 0, + "line": 2, + }, + }, + "2": { + "end": { + "column": 23, + "line": 3, + }, + "start": { + "column": 0, + "line": 3, + }, + }, + "3": { + "end": { + "column": 0, + "line": 4, + }, + "start": { + "column": 0, + "line": 4, + }, + }, + "4": { + "end": { + "column": 50, + "line": 5, + }, + "start": { + "column": 0, + "line": 5, + }, + }, + "5": { + "end": { + "column": 50, + "line": 6, + }, + "start": { + "column": 0, + "line": 6, + }, + }, + "6": { + "end": { + "column": 1, + "line": 7, + }, + "start": { + "column": 0, + "line": 7, + }, + }, + }, + }, + "/src/untested-file.ts": { + "all": true, + "b": { + "0": [ + 0, + ], + }, + "branchMap": { + "0": { + "line": 1, + "loc": { + "end": { + "column": 1, + "line": 33, + }, + "start": { + "column": 0, + "line": 1, + }, + }, + "locations": [ + { + "end": { + "column": 1, + "line": 33, + }, + "start": { + "column": 0, + "line": 1, + }, + }, + ], + "type": "branch", + }, + }, + "f": { + "0": 0, + }, + "fnMap": { + "0": { + "decl": { + "end": { + "column": 1, + "line": 33, + }, + "start": { + "column": 0, + "line": 1, + }, + }, + "line": 1, + "loc": { + "end": { + "column": 1, + "line": 33, + }, + "start": { + "column": 0, + "line": 1, + }, + }, + "name": "(empty-report)", + }, + }, + "path": "/src/untested-file.ts", + "s": { + "0": 0, + "1": 0, + "10": 0, + "11": 0, + "12": 0, + "13": 0, + "14": 0, + "15": 0, + "16": 0, + "17": 0, + "18": 0, + "19": 0, + "2": 0, + "20": 0, + "21": 0, + "22": 0, + "23": 0, + "24": 0, + "25": 0, + "26": 0, + "27": 0, + "28": 0, + "29": 0, + "3": 0, + "30": 0, + "31": 0, + "32": 0, + "4": 0, + "5": 0, + "6": 0, + "7": 0, + "8": 0, + "9": 0, + }, + "statementMap": { + "0": { + "end": { + "column": 2, + "line": 1, + }, + "start": { + "column": 0, + "line": 1, + }, + }, + "1": { + "end": { + "column": 68, + "line": 2, + }, + "start": { + "column": 0, + "line": 2, + }, + }, + "10": { + "end": { + "column": 36, + "line": 11, + }, + "start": { + "column": 0, + "line": 11, + }, + }, + "11": { + "end": { + "column": 14, + "line": 12, + }, + "start": { + "column": 0, + "line": 12, + }, + }, + "12": { + "end": { + "column": 1, + "line": 13, + }, + "start": { + "column": 0, + "line": 13, + }, + }, + "13": { + "end": { + "column": 0, + "line": 14, + }, + "start": { + "column": 0, + "line": 14, + }, + }, + "14": { + "end": { + "column": 41, + "line": 15, + }, + "start": { + "column": 0, + "line": 15, + }, + }, + "15": { + "end": { + "column": 36, + "line": 16, + }, + "start": { + "column": 0, + "line": 16, + }, + }, + "16": { + "end": { + "column": 14, + "line": 17, + }, + "start": { + "column": 0, + "line": 17, + }, + }, + "17": { + "end": { + "column": 1, + "line": 18, + }, + "start": { + "column": 0, + "line": 18, + }, + }, + "18": { + "end": { + "column": 0, + "line": 19, + }, + "start": { + "column": 0, + "line": 19, + }, + }, + "19": { + "end": { + "column": 65, + "line": 20, + }, + "start": { + "column": 0, + "line": 20, + }, + }, + "2": { + "end": { + "column": 25, + "line": 3, + }, + "start": { + "column": 0, + "line": 3, + }, + }, + "20": { + "end": { + "column": 25, + "line": 21, + }, + "start": { + "column": 0, + "line": 21, + }, + }, + "21": { + "end": { + "column": 38, + "line": 22, + }, + "start": { + "column": 0, + "line": 22, + }, + }, + "22": { + "end": { + "column": 25, + "line": 23, + }, + "start": { + "column": 0, + "line": 23, + }, + }, + "23": { + "end": { + "column": 3, + "line": 24, + }, + "start": { + "column": 0, + "line": 24, + }, + }, + "24": { + "end": { + "column": 0, + "line": 25, + }, + "start": { + "column": 0, + "line": 25, + }, + }, + "25": { + "end": { + "column": 25, + "line": 26, + }, + "start": { + "column": 0, + "line": 26, + }, + }, + "26": { + "end": { + "column": 38, + "line": 27, + }, + "start": { + "column": 0, + "line": 27, + }, + }, + "27": { + "end": { + "column": 20, + "line": 28, + }, + "start": { + "column": 0, + "line": 28, + }, + }, + "28": { + "end": { + "column": 3, + "line": 29, + }, + "start": { + "column": 0, + "line": 29, + }, + }, + "29": { + "end": { + "column": 0, + "line": 30, + }, + "start": { + "column": 0, + "line": 30, + }, + }, + "3": { + "end": { + "column": 2, + "line": 4, + }, + "start": { + "column": 0, + "line": 4, + }, + }, + "30": { + "end": { + "column": 36, + "line": 31, + }, + "start": { + "column": 0, + "line": 31, + }, + }, + "31": { + "end": { + "column": 41, + "line": 32, + }, + "start": { + "column": 0, + "line": 32, + }, + }, + "32": { + "end": { + "column": 1, + "line": 33, + }, + "start": { + "column": 0, + "line": 33, + }, + }, + "4": { + "end": { + "column": 0, + "line": 5, + }, + "start": { + "column": 0, + "line": 5, + }, + }, + "5": { + "end": { + "column": 40, + "line": 6, + }, + "start": { + "column": 0, + "line": 6, + }, + }, + "6": { + "end": { + "column": 72, + "line": 7, + }, + "start": { + "column": 0, + "line": 7, + }, + }, + "7": { + "end": { + "column": 1, + "line": 8, + }, + "start": { + "column": 0, + "line": 8, + }, + }, + "8": { + "end": { + "column": 0, + "line": 9, + }, + "start": { + "column": 0, + "line": 9, + }, + }, + "9": { + "end": { + "column": 36, + "line": 10, + }, + "start": { + "column": 0, + "line": 10, + }, + }, + }, + }, + "/src/utils.ts": { + "all": false, + "b": { + "0": [ + 1, + ], + "1": [ + 2, + ], + "2": [ + 1, + ], + "3": [ + 0, + ], + }, + "branchMap": { + "0": { + "line": 1, + "loc": { + "end": { + "column": 1, + "line": 3, + }, + "start": { + "column": 7, + "line": 1, + }, + }, + "locations": [ + { + "end": { + "column": 1, + "line": 3, + }, + "start": { + "column": 7, + "line": 1, + }, + }, + ], + "type": "branch", + }, + "1": { + "line": 5, + "loc": { + "end": { + "column": 1, + "line": 7, + }, + "start": { + "column": 7, + "line": 5, + }, + }, + "locations": [ + { + "end": { + "column": 1, + "line": 7, + }, + "start": { + "column": 7, + "line": 5, + }, + }, + ], + "type": "branch", + }, + "2": { + "line": 14, + "loc": { + "end": { + "column": 1, + "line": 19, + }, + "start": { + "column": 7, + "line": 14, + }, + }, + "locations": [ + { + "end": { + "column": 1, + "line": 19, + }, + "start": { + "column": 7, + "line": 14, + }, + }, + ], + "type": "branch", + }, + "3": { + "line": 16, + "loc": { + "end": { + "column": 51, + "line": 16, + }, + "start": { + "column": 4, + "line": 16, + }, + }, + "locations": [ + { + "end": { + "column": 51, + "line": 16, + }, + "start": { + "column": 4, + "line": 16, + }, + }, + ], + "type": "branch", + }, + }, + "f": { + "0": 1, + "1": 2, + "2": 0, + "3": 1, + "4": 0, + "5": 1, + }, + "fnMap": { + "0": { + "decl": { + "end": { + "column": 1, + "line": 3, + }, + "start": { + "column": 7, + "line": 1, + }, + }, + "line": 1, + "loc": { + "end": { + "column": 1, + "line": 3, + }, + "start": { + "column": 7, + "line": 1, + }, + }, + "name": "add", + }, + "1": { + "decl": { + "end": { + "column": 1, + "line": 7, + }, + "start": { + "column": 7, + "line": 5, + }, + }, + "line": 5, + "loc": { + "end": { + "column": 1, + "line": 7, + }, + "start": { + "column": 7, + "line": 5, + }, + }, + "name": "multiply", + }, + "2": { + "decl": { + "end": { + "column": 1, + "line": 12, + }, + "start": { + "column": 7, + "line": 9, + }, + }, + "line": 9, + "loc": { + "end": { + "column": 1, + "line": 12, + }, + "start": { + "column": 7, + "line": 9, + }, + }, + "name": "divide", + }, + "3": { + "decl": { + "end": { + "column": 1, + "line": 19, + }, + "start": { + "column": 7, + "line": 14, + }, + }, + "line": 14, + "loc": { + "end": { + "column": 1, + "line": 19, + }, + "start": { + "column": 7, + "line": 14, + }, + }, + "name": "sqrt", + }, + "4": { + "decl": { + "end": { + "column": 1, + "line": 24, + }, + "start": { + "column": 7, + "line": 21, + }, + }, + "line": 21, + "loc": { + "end": { + "column": 1, + "line": 24, + }, + "start": { + "column": 7, + "line": 21, + }, + }, + "name": "run", + }, + "5": { + "decl": { + "end": { + "column": 1, + "line": 30, + }, + "start": { + "column": 7, + "line": 28, + }, + }, + "line": 28, + "loc": { + "end": { + "column": 1, + "line": 30, + }, + "start": { + "column": 7, + "line": 28, + }, + }, + "name": "ignoredFunction", + }, + }, + "path": "/src/utils.ts", + "s": { + "0": 1, + "1": 1, + "10": 0, + "11": 0, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 1, + "17": 1, + "18": 1, + "19": 1, + "2": 1, + "20": 1, + "21": 0, + "22": 0, + "23": 0, + "24": 1, + "25": 1, + "26": 1, + "27": 1, + "28": 1, + "29": 1, + "3": 1, + "4": 1, + "5": 2, + "6": 2, + "7": 1, + "8": 1, + "9": 0, + }, + "statementMap": { + "0": { + "end": { + "column": 43, + "line": 1, + }, + "start": { + "column": 0, + "line": 1, + }, + }, + "1": { + "end": { + "column": 14, + "line": 2, + }, + "start": { + "column": 0, + "line": 2, + }, + }, + "10": { + "end": { + "column": 14, + "line": 11, + }, + "start": { + "column": 0, + "line": 11, + }, + }, + "11": { + "end": { + "column": 1, + "line": 12, + }, + "start": { + "column": 0, + "line": 12, + }, + }, + "12": { + "end": { + "column": 0, + "line": 13, + }, + "start": { + "column": 0, + "line": 13, + }, + }, + "13": { + "end": { + "column": 33, + "line": 14, + }, + "start": { + "column": 0, + "line": 14, + }, + }, + "14": { + "end": { + "column": 12, + "line": 15, + }, + "start": { + "column": 0, + "line": 15, + }, + }, + "15": { + "end": { + "column": 51, + "line": 16, + }, + "start": { + "column": 0, + "line": 16, + }, + }, + "16": { + "end": { + "column": 0, + "line": 17, + }, + "start": { + "column": 0, + "line": 17, + }, + }, + "17": { + "end": { + "column": 21, + "line": 18, + }, + "start": { + "column": 0, + "line": 18, + }, + }, + "18": { + "end": { + "column": 1, + "line": 19, + }, + "start": { + "column": 0, + "line": 19, + }, + }, + "19": { + "end": { + "column": 0, + "line": 20, + }, + "start": { + "column": 0, + "line": 20, + }, + }, + "2": { + "end": { + "column": 1, + "line": 3, + }, + "start": { + "column": 0, + "line": 3, + }, + }, + "20": { + "end": { + "column": 23, + "line": 21, + }, + "start": { + "column": 0, + "line": 21, + }, + }, + "21": { + "end": { + "column": 31, + "line": 22, + }, + "start": { + "column": 0, + "line": 22, + }, + }, + "22": { + "end": { + "column": 14, + "line": 23, + }, + "start": { + "column": 0, + "line": 23, + }, + }, + "23": { + "end": { + "column": 1, + "line": 24, + }, + "start": { + "column": 0, + "line": 24, + }, + }, + "24": { + "end": { + "column": 0, + "line": 25, + }, + "start": { + "column": 0, + "line": 25, + }, + }, + "25": { + "end": { + "column": 22, + "line": 26, + }, + "start": { + "column": 0, + "line": 26, + }, + }, + "26": { + "end": { + "column": 39, + "line": 27, + }, + "start": { + "column": 0, + "line": 27, + }, + }, + "27": { + "end": { + "column": 35, + "line": 28, + }, + "start": { + "column": 0, + "line": 28, + }, + }, + "28": { + "end": { + "column": 62, + "line": 29, + }, + "start": { + "column": 0, + "line": 29, + }, + }, + "29": { + "end": { + "column": 1, + "line": 30, + }, + "start": { + "column": 0, + "line": 30, + }, + }, + "3": { + "end": { + "column": 0, + "line": 4, + }, + "start": { + "column": 0, + "line": 4, + }, + }, + "4": { + "end": { + "column": 48, + "line": 5, + }, + "start": { + "column": 0, + "line": 5, + }, + }, + "5": { + "end": { + "column": 14, + "line": 6, + }, + "start": { + "column": 0, + "line": 6, + }, + }, + "6": { + "end": { + "column": 1, + "line": 7, + }, + "start": { + "column": 0, + "line": 7, + }, + }, + "7": { + "end": { + "column": 0, + "line": 8, + }, + "start": { + "column": 0, + "line": 8, + }, + }, + "8": { + "end": { + "column": 46, + "line": 9, + }, + "start": { + "column": 0, + "line": 9, + }, + }, + "9": { + "end": { + "column": 31, + "line": 10, + }, + "start": { + "column": 0, + "line": 10, + }, + }, + }, + }, +} +`; diff --git a/test/coverage-test/coverage-report-tests/v8.report.test.ts b/test/coverage-test/coverage-report-tests/v8.report.test.ts new file mode 100644 index 000000000000..30d8c64873ce --- /dev/null +++ b/test/coverage-test/coverage-report-tests/v8.report.test.ts @@ -0,0 +1,31 @@ +/* + * V8 coverage provider specific test cases + */ + +import { expect, test } from 'vitest' +import { readCoverageJson } from './utils' + +test('v8 json report', async () => { + const jsonReport = await readCoverageJson() + + // If this fails, you can use "npx live-server@1.2.1 ./coverage" to see coverage report + expect(jsonReport).toMatchSnapshot() +}) + +test('ignored code is marked as covered in the report', async () => { + const functionName = 'ignoredFunction' + const filename = '/src/utils.ts' + + const coverageMap = await readCoverageJson() + const fileCoverage = coverageMap[filename] + + const [functionKey] = Object.entries(fileCoverage.fnMap).find(([, fn]) => fn.name === functionName)! + const functionCallCount = fileCoverage.f[functionKey] + + // v8-to-istanbul marks excluded lines as covered, instead of removing them from report completely + expect(functionCallCount).toBe(1) + + // Function should still be found from the actual sources + const utils = await import('../src/utils') + expect(utils[functionName]).toBeTypeOf('function') +}) diff --git a/test/coverage-test/package.json b/test/coverage-test/package.json index 4350eb240295..fb82f0c9c7d5 100644 --- a/test/coverage-test/package.json +++ b/test/coverage-test/package.json @@ -2,8 +2,9 @@ "name": "@vitest/test-coverage", "private": true, "scripts": { - "test": "pnpm test:c8 && pnpm test:istanbul && pnpm test:custom && pnpm test:browser && pnpm test:types", + "test": "pnpm test:c8 && pnpm test:v8 && pnpm test:istanbul && pnpm test:custom && pnpm test:browser && pnpm test:types", "test:c8": "node ./testing.mjs --provider c8", + "test:v8": "node ./testing.mjs --provider v8", "test:custom": "node ./testing.mjs --provider custom", "test:istanbul": "node ./testing.mjs --provider istanbul", "test:browser": "node ./testing.mjs --browser --provider istanbul", @@ -13,6 +14,9 @@ "@types/istanbul-lib-coverage": "^2.0.4", "@vitejs/plugin-vue": "latest", "@vitest/browser": "workspace:*", + "@vitest/coverage-c8": "workspace:*", + "@vitest/coverage-istanbul": "workspace:*", + "@vitest/coverage-v8": "workspace:*", "@vue/test-utils": "latest", "happy-dom": "latest", "istanbul-lib-coverage": "^3.2.0", diff --git a/test/coverage-test/test/configuration-options.test-d.ts b/test/coverage-test/test/configuration-options.test-d.ts index d9f17e107fa6..144fdc266fce 100644 --- a/test/coverage-test/test/configuration-options.test-d.ts +++ b/test/coverage-test/test/configuration-options.test-d.ts @@ -8,6 +8,7 @@ type Coverage = NonNullable test('providers, built-in', () => { assertType({ provider: 'c8' }) + assertType({ provider: 'v8' }) assertType({ provider: 'istanbul' }) // @ts-expect-error -- String options must be known ones only @@ -32,6 +33,16 @@ test('provider options, generic', () => { }, }) + assertType({ + provider: 'v8', + enabled: true, + include: ['string'], + watermarks: { + functions: [80, 95], + lines: [80, 95], + }, + }) + assertType({ provider: 'istanbul', enabled: true, @@ -58,6 +69,19 @@ test('provider specific options, c8', () => { }) }) +test('provider specific options, v8', () => { + assertType({ + provider: 'v8', + 100: true, + }) + + assertType({ + provider: 'v8', + // @ts-expect-error -- Istanbul-only option is not allowed + ignoreClassMethods: ['string'], + }) +}) + test('provider specific options, istanbul', () => { assertType({ provider: 'istanbul', @@ -66,8 +90,8 @@ test('provider specific options, istanbul', () => { assertType({ provider: 'istanbul', - // @ts-expect-error -- C8-only option is not allowed - src: ['string'], + // @ts-expect-error -- V8-only option is not allowed + 100: true, }) }) diff --git a/test/coverage-test/testing.mjs b/test/coverage-test/testing.mjs index 059b7aeb274d..8689470a16c4 100644 --- a/test/coverage-test/testing.mjs +++ b/test/coverage-test/testing.mjs @@ -32,7 +32,7 @@ const configs = [ // Run tests for checking coverage report contents. ['coverage-report-tests', { include: [ - ['c8', 'istanbul'].includes(provider) && './coverage-report-tests/generic.report.test.ts', + ['c8', 'v8', 'istanbul'].includes(provider) && './coverage-report-tests/generic.report.test.ts', `./coverage-report-tests/${provider}.report.test.ts`, ].filter(Boolean), coverage: { enabled: false, clean: false },