···11-import { expect, test } from 'vitest'
22-import libCoverage from 'istanbul-lib-coverage'
33-44-import { readCoverageJson } from './utils'
55-66-test('report contains only the changed files', async () => {
77- const coverageJson = await readCoverageJson('./coverage/coverage-final.json')
88- const coverageMap = libCoverage.createCoverageMap(coverageJson as any)
99-1010- // Note that this test may fail if you have new files in "vitest/test/coverage-test/src"
1111- // and have not yet committed those
1212- expect(coverageMap.files()).toMatchInlineSnapshot(`
1313- [
1414- "<process-cwd>/src/file-to-change.ts",
1515- "<process-cwd>/src/new-uncovered-file.ts",
1616- ]
1717- `)
1818-1919- const uncoveredFile = coverageMap.fileCoverageFor('<process-cwd>/src/new-uncovered-file.ts').toSummary()
2020- expect(uncoveredFile.lines.pct).toBe(0)
2121-2222- const changedFile = coverageMap.fileCoverageFor('<process-cwd>/src/file-to-change.ts').toSummary()
2323- expect(changedFile.lines.pct).toBeGreaterThanOrEqual(50)
2424-})
···11-import { expect, test } from 'vitest'
22-33-import { add } from '../src/empty-lines'
44-55-test('cover some lines', () => {
66- expect(add(10, 20)).toBe(30)
77-})
-8
test/coverage-test/option-tests/fixture.test.ts
···11-// Generic test fixture to generate some coverage
22-33-import { test } from 'vitest'
44-import { add } from '../src/utils'
55-66-test('cover some lines', () => {
77- add(1, 2)
88-})
···11-// eslint-disable-next-line ts/ban-ts-comment -- so that typecheck doesn't include it
22-// @ts-nocheck
33-export class DecoratorsTester {
44- method(@SomeDecorator parameter: Something) {
55- if (parameter) {
66- // Covered line
77- noop(parameter)
88- }
99-1010- if (parameter === 'uncovered') {
1111- // Uncovered line
1212- noop(parameter)
1313- }
1414- }
1515-}
1616-1717-function SomeDecorator(
1818- _target: Object,
1919- _propertyKey: string | symbol,
2020- _parameterIndex: number,
2121-) {}
2222-2323-type Something = unknown
2424-2525-function noop(..._args: unknown[]) {
2626-2727-}
-51
test/coverage-test/src/dynamic-files.ts
···11-import { existsSync, rmSync, writeFileSync } from 'node:fs'
22-import { createRequire } from 'node:module'
33-import { fileURLToPath } from 'node:url'
44-55-export async function runDynamicFileESM() {
66- const filename = fileURLToPath(new URL('./dynamic-file-esm.ignore.js', import.meta.url))
77-88- if (existsSync(filename)) {
99- rmSync(filename)
1010- }
1111-1212- writeFileSync(filename, `
1313-// File created by coverage-test/src/dynamic-files.ts
1414-export function run() {
1515- return "Import works"
1616-}
1717-function uncovered() {}
1818- `.trim(), 'utf-8')
1919-2020- const { run } = await import(filename)
2121-2222- if (run() !== 'Import works') {
2323- throw new Error(`Failed to run ${filename}`)
2424- }
2525-2626- rmSync(filename)
2727-}
2828-2929-export async function runDynamicFileCJS() {
3030- const filename = fileURLToPath(new URL('./dynamic-file-cjs.ignore.cjs', import.meta.url))
3131-3232- if (existsSync(filename)) {
3333- rmSync(filename)
3434- }
3535-3636- writeFileSync(filename, `
3737-// File created by coverage-test/src/dynamic-files.ts
3838-module.exports.run = function run() {
3939- return "Import works"
4040-}
4141-function uncovered() {}
4242- `.trim(), 'utf-8')
4343-4444- const { run } = createRequire(import.meta.url)(filename)
4545-4646- if (run() !== 'Import works') {
4747- throw new Error(`Failed to run ${filename}`)
4848- }
4949-5050- rmSync(filename)
5151-}
-29
test/coverage-test/src/empty-lines.ts
···11-/* eslint-disable unused-imports/no-unused-vars -- intentional */
22-export function add(a: number, b: number) {
33- /**
44- * Multi
55- * line
66- * comment
77- */
88- if (a === 2 && b === 3) {
99- // This line should NOT be covered
1010- return 5
1111- }
1212-1313- type TypescriptTypings = 1 | 2
1414-1515- if (a === 1 && b === 1) {
1616- // This line should NOT be covered
1717- return 2
1818- }
1919-2020- interface MoreCompileTimeCode {
2121- should: {
2222- be: {
2323- excluded: true
2424- }
2525- }
2626- }
2727-2828- return a + b
2929-}
-7
test/coverage-test/src/file-to-change.ts
···11-export function run() {
22- return 'This file will be modified by test cases'
33-}
44-55-export function uncoveredFunction() {
66- return 1 + 2
77-}
-36
test/coverage-test/src/function-count.ts
···11-/*
22- * This file should have:
33- * - 5 functions in total
44- * - 3 covered functions
55- * - 2 uncovered functions
66- */
77-88-/* eslint-disable unused-imports/no-unused-vars */
99-1010-// This function is covered
1111-function first() {
1212- return 1
1313-}
1414-1515-first()
1616-1717-// This function is covered
1818-export function second() {
1919- fifth()
2020- return 2
2121-}
2222-2323-// This function is NOT covered
2424-export function third() {
2525- throw new Error('Do not call this function')
2626-}
2727-2828-// This function is NOT covered
2929-function fourth() {
3030- return 4
3131-}
3232-3333-// This function is covered
3434-function fifth() {
3535- return 5
3636-}
-9
test/coverage-test/src/implicitElse.ts
···11-export function implicitElse(condition: boolean) {
22- let a = 1
33-44- if (condition) {
55- a = 2
66- }
77-88- return a
99-}
-3
test/coverage-test/src/importEnv.ts
···11-export function useImportEnv() {
22- return import.meta.env.SOME_VARIABLE == null
33-}
-7
test/coverage-test/src/index.mts
···11-import { add, multiply, sqrt } from './utils'
22-33-export * from './utils'
44-55-export function pythagoras(a: number, b: number) {
66- return sqrt(add(multiply(a, a), multiply(b, b)))
77-}
···11-/**
22- * The variable below is modified by custom Vite plugin
33- */
44-export const padding = 'default-padding'
55-66-export function sum(a: number, b: number) {
77- /*
88- * These if-branches should show correctly on coverage report.
99- * Otherwise source maps are off.
1010- */
1111- if (a === 8 && b === 9) {
1212- // This is not covered by any test
1313- return 17
1414- }
1515- // Comment
1616- else if (a === 2 && b === 2) {
1717- // This is covered by SSR test
1818- return 4
1919- }
2020- else if (a === 11 && b === 22) {
2121- // This is not covered by any test
2222- return 33
2323- }
2424- else if (a === 10 && b === 23) {
2525- // This is covered by Web test
2626- return 33
2727- }
2828-2929- // This is covered by SSR and Web test, should show 2x hits
3030- return a + b
3131-}
···11-export type First = 'This' | 'file' | 'should'
22-33-export type Second = 'be' | 'excluded' | 'from' | 'report'
-47
test/coverage-test/src/untested-file.ts
···11-/*
22- * Some top level comment which adds some padding. This helps us see
33- * if sourcemaps are off.
44-*/
55-66-/* eslint-disable unused-imports/no-unused-vars -- intentional */
77-88-export default function untestedFile() {
99- return 'This file should end up in report when {"all": true} is given'
1010-}
1111-1212-function add(a: number, b: number) {
1313- // This line should NOT be covered
1414- return a + b
1515-}
1616-1717-type TypescriptTypings = 1 | 2
1818-1919-function multiply(a: number, b: number) {
2020- // This line should NOT be covered
2121- return a * b
2222-}
2323-2424-export function math(a: number, b: number, operator: '*' | '+') {
2525- interface MoreCompileTimeCode {
2626- should: {
2727- be: {
2828- excluded: true
2929- }
3030- }
3131- }
3232-3333- if (operator === '*') {
3434- // This line should NOT be covered
3535- return multiply(a, b)
3636- }
3737-3838- /* v8 ignore start */
3939- if (operator === '+') {
4040- // This line should be excluded
4141- return add(a, b)
4242- }
4343- /* v8 ignore stop */
4444-4545- // This line should NOT be covered
4646- throw new Error('Unsupported operator')
4747-}
-30
test/coverage-test/src/utils.ts
···11-export function add(a: number, b: number) {
22- return a + b
33-}
44-55-export function multiply(a: number, b: number) {
66- return a * b
77-}
88-99-export function divide(a: number, b: number) {
1010- // this should not be covered
1111- return a / b
1212-}
1313-1414-export function sqrt(a: number) {
1515- if (a < 0) {
1616- return Number.NaN // This should not be covered
1717- }
1818- return Math.sqrt(a)
1919-}
2020-2121-export function run() {
2222- // this should not be covered
2323- divide(1, 1)
2424-}
2525-2626-/* v8 ignore next 4 */
2727-/* istanbul ignore next -- @preserve */
2828-export function ignoredFunction() {
2929- throw new Error('Test files should not call this function!')
3030-}
···11+import { expect } from 'vitest'
22+import { coverageTest, isV8Provider, normalizeURL, readCoverageMap, runVitest, test } from '../utils'
33+import { DecoratorsTester } from '../fixtures/src/decorators'
44+55+test('decorators generated metadata is ignored', async () => {
66+ await runVitest({
77+ include: [normalizeURL(import.meta.url)],
88+ config: 'fixtures/configs/vitest.config.decorators.ts',
99+ coverage: { reporter: 'json', all: false },
1010+ })
1111+1212+ const coverageMap = await readCoverageMap()
1313+ const fileCoverage = coverageMap.fileCoverageFor('<process-cwd>/fixtures/src/decorators.ts')
1414+ const lineCoverage = fileCoverage.getLineCoverage()
1515+ const branchCoverage = fileCoverage.getBranchCoverageByLine()
1616+1717+ // Decorator should not be uncovered - on V8 this is marked as covered, on Istanbul it's excluded from report
1818+ if (isV8Provider()) {
1919+ expect(lineCoverage['4']).toBe(1)
2020+ expect(branchCoverage['4'].coverage).toBe(100)
2121+ }
2222+ else {
2323+ expect(lineCoverage['4']).toBeUndefined()
2424+ expect(branchCoverage['4']).toBeUndefined()
2525+ }
2626+2727+ // Covered branch should be marked correctly
2828+ expect(lineCoverage['7']).toBe(1)
2929+3030+ // Uncovered branch should be marked correctly
3131+ expect(lineCoverage['12']).toBe(0)
3232+})
3333+3434+coverageTest('cover decorators', () => {
3535+ new DecoratorsTester().method('cover line')
3636+})
+30
test/coverage-test/test/dynamic-files.test.ts
···11+import { expect } from 'vitest'
22+import { coverageTest, isV8Provider, normalizeURL, readCoverageMap, runVitest, test } from '../utils'
33+import { runDynamicFileCJS, runDynamicFileESM } from '../fixtures/src/dynamic-files'
44+55+test('does not crash when files are created and removed during test run (#3657)', async () => {
66+ await runVitest({
77+ include: [normalizeURL(import.meta.url)],
88+ coverage: { reporter: 'json' },
99+ })
1010+1111+ const coverageMap = await readCoverageMap()
1212+ const files = coverageMap.files()
1313+1414+ expect(files).toContain('<process-cwd>/fixtures/src/dynamic-files.ts')
1515+ expect(files).toContain('<process-cwd>/fixtures/src/dynamic-file-esm.ignore.js')
1616+1717+ // V8 provider is able to capture CJS as well as it's detected on runtime.
1818+ // Istanbul requires the file to be either processed by Vite, or be present on file system when coverage.all kicks in
1919+ if (isV8Provider()) {
2020+ expect(files).toContain('<process-cwd>/fixtures/src/dynamic-file-cjs.ignore.cjs')
2121+ }
2222+})
2323+2424+coverageTest('run dynamic ESM file', async () => {
2525+ await expect(runDynamicFileESM()).resolves.toBe('Done')
2626+})
2727+2828+coverageTest('run dynamic CJS file', async () => {
2929+ await expect(runDynamicFileCJS()).resolves.toBe('Done')
3030+})
···11+import { expect } from 'vitest'
22+import { runVitest, test } from '../utils'
33+44+test('mocking in JS test file should not crash source map lookup (#3514)', async () => {
55+ const { exitCode } = await runVitest({
66+ include: ['fixtures/test/mocking-in-js-file.test.js'],
77+ coverage: {
88+ reporter: 'json',
99+ all: false,
1010+ },
1111+ })
1212+1313+ expect(exitCode).toBe(0)
1414+})
+30
test/coverage-test/test/multi-environment.test.ts
···11+import { expect } from 'vitest'
22+import { readCoverageMap, runVitest, test } from '../utils'
33+44+test('{ all: true } includes uncovered files', async () => {
55+ await runVitest({
66+ include: ['fixtures/test/multi-environment-fixture-**'],
77+ config: 'fixtures/configs/vitest.config.multi-transform.ts',
88+ coverage: { all: false, reporter: 'json' },
99+ })
1010+1111+ const coverageMap = await readCoverageMap()
1212+1313+ const fileCoverage = coverageMap.fileCoverageFor('<process-cwd>/fixtures/src/multi-environment.ts')
1414+ const lineCoverage = fileCoverage.getLineCoverage()
1515+1616+ // Condition not covered by any test
1717+ expect(lineCoverage[13]).toBe(0)
1818+1919+ // Condition covered by SSR test but not by Web
2020+ expect(lineCoverage[18]).toBe(1)
2121+2222+ // Condition not covered by any test
2323+ expect(lineCoverage[22]).toBe(0)
2424+2525+ // Condition covered by Web test but not by SSR
2626+ expect(lineCoverage[26]).toBe(1)
2727+2828+ // Condition covered by both tests
2929+ expect(lineCoverage[30]).toBe(2)
3030+})
+18
test/coverage-test/test/multi-suite.test.ts
···11+import { expect } from 'vitest'
22+import { readCoverageMap, runVitest, test } from '../utils'
33+44+test('tests with multiple suites are covered (#3514)', async () => {
55+ await runVitest({
66+ include: ['fixtures/test/multi-suite-fixture.test.ts'],
77+ coverage: { reporter: 'json', all: false },
88+ })
99+1010+ const coverageMap = await readCoverageMap()
1111+ const fileCoverage = coverageMap.fileCoverageFor('<process-cwd>/fixtures/src/multi-suite.ts')
1212+1313+ // Assert that all functions are covered
1414+ expect(fileCoverage.f).toMatchObject({
1515+ 0: 1,
1616+ 1: 1,
1717+ })
1818+})
+5
test/coverage-test/test/multi-transform.test.ts
···11+import { test } from '../utils'
22+33+test('files transformed with multiple transform modes work (#3251)', async () => {
44+ // TODO
55+})
···11+import { Plugin, defineConfig, mergeConfig } from 'vitest/config'
22+33+import base from './vitest.config'
44+55+export default mergeConfig(
66+ base,
77+ defineConfig({
88+ plugins: [VirtualFilesPlugin()],
99+ test: {}
1010+ })
1111+)
1212+1313+// Simulates Vite's virtual files: https://vitejs.dev/guide/api-plugin.html#virtual-modules-convention
1414+function VirtualFilesPlugin(): Plugin {
1515+ return {
1616+ name: 'vitest-custom-virtual-files',
1717+ resolveId(id) {
1818+ if (id === 'virtual:vitest-custom-virtual-file-1') {
1919+ return 'src/virtual:vitest-custom-virtual-file-1.ts'
2020+ }
2121+2222+ if (id === '\0vitest-custom-virtual-file-2') {
2323+ return 'src/\0vitest-custom-virtual-file-2.ts'
2424+ }
2525+ },
2626+ load(id) {
2727+ if (id === 'src/virtual:vitest-custom-virtual-file-1.ts') {
2828+ return `
2929+ const virtualFile = "This file should be excluded from coverage report #1"
3030+ export default virtualFile;
3131+ `
3232+ }
3333+3434+ // Vitest browser resolves this as "\x00", Node as "__x00__"
3535+ if (id === 'src/__x00__vitest-custom-virtual-file-2.ts' || id === 'src/\x00vitest-custom-virtual-file-2.ts') {
3636+ return `
3737+ const virtualFile = "This file should be excluded from coverage report #2"
3838+ export default virtualFile;
3939+ `
4040+ }
4141+ },
4242+ }
4343+}
+1
test/coverage-test/fixtures/src/another-setup.ts
···11+console.log('Running another setup in fixtures src')
+27
test/coverage-test/fixtures/src/decorators.ts
···11+// eslint-disable-next-line ts/ban-ts-comment -- so that typecheck doesn't include it
22+// @ts-nocheck
33+export class DecoratorsTester {
44+ method(@SomeDecorator parameter: Something) {
55+ if (parameter) {
66+ // Covered line
77+ noop(parameter)
88+ }
99+1010+ if (parameter === 'uncovered') {
1111+ // Uncovered line
1212+ noop(parameter)
1313+ }
1414+ }
1515+}
1616+1717+function SomeDecorator(
1818+ _target: Object,
1919+ _propertyKey: string | symbol,
2020+ _parameterIndex: number,
2121+) {}
2222+2323+type Something = unknown
2424+2525+function noop(..._args: unknown[]) {
2626+2727+}
+55
test/coverage-test/fixtures/src/dynamic-files.ts
···11+import { existsSync, rmSync, writeFileSync } from 'node:fs'
22+import { createRequire } from 'node:module'
33+import { fileURLToPath } from 'node:url'
44+55+export async function runDynamicFileESM() {
66+ const filename = fileURLToPath(new URL('./dynamic-file-esm.ignore.js', import.meta.url))
77+88+ if (existsSync(filename)) {
99+ rmSync(filename)
1010+ }
1111+1212+ writeFileSync(filename, `
1313+// File created by coverage/fixtures/src/dynamic-files.ts
1414+export function run() {
1515+ return "Import works"
1616+}
1717+function uncovered() {}
1818+ `.trim(), 'utf-8')
1919+2020+ const { run } = await import(filename)
2121+2222+ if (run() !== 'Import works') {
2323+ throw new Error(`Failed to run ${filename}`)
2424+ }
2525+2626+ rmSync(filename)
2727+2828+ return "Done"
2929+}
3030+3131+export async function runDynamicFileCJS() {
3232+ const filename = fileURLToPath(new URL('./dynamic-file-cjs.ignore.cjs', import.meta.url))
3333+3434+ if (existsSync(filename)) {
3535+ rmSync(filename)
3636+ }
3737+3838+ writeFileSync(filename, `
3939+// File created by coverage/fixtures/src/dynamic-files.ts
4040+module.exports.run = function run() {
4141+ return "Import works"
4242+}
4343+function uncovered() {}
4444+ `.trim(), 'utf-8')
4545+4646+ const { run } = createRequire(import.meta.url)(filename)
4747+4848+ if (run() !== 'Import works') {
4949+ throw new Error(`Failed to run ${filename}`)
5050+ }
5151+5252+ rmSync(filename)
5353+5454+ return "Done"
5555+}
+29
test/coverage-test/fixtures/src/empty-lines.ts
···11+/* eslint-disable unused-imports/no-unused-vars -- intentional */
22+export function add(a: number, b: number) {
33+ /**
44+ * Multi
55+ * line
66+ * comment
77+ */
88+ if (a === 2 && b === 3) {
99+ // This line should NOT be covered
1010+ return 5
1111+ }
1212+1313+ type TypescriptTypings = 1 | 2
1414+1515+ if (a === 1 && b === 1) {
1616+ // This line should NOT be covered
1717+ return 2
1818+ }
1919+2020+ interface MoreCompileTimeCode {
2121+ should: {
2222+ be: {
2323+ excluded: true
2424+ }
2525+ }
2626+ }
2727+2828+ return a + b
2929+}
+7
test/coverage-test/fixtures/src/even.ts
···11+export function isEven(a: number) {
22+ return a % 2 === 0
33+}
44+55+export function isOdd(a: number) {
66+ return !isEven(a)
77+}
+7
test/coverage-test/fixtures/src/file-to-change.ts
···11+export function run() {
22+ return 'This file will be modified by test cases'
33+}
44+55+export function uncoveredFunction() {
66+ return 1 + 2
77+}
+18
test/coverage-test/fixtures/src/ignore-hints.ts
···11+/* v8 ignore next 4 */
22+/* istanbul ignore next -- @preserve */
33+export function first() {
44+ return "First"
55+}
66+77+export function second() {
88+ return "Second"
99+}
1010+1111+// Covered line
1212+second()
1313+1414+/* v8 ignore next -- Uncovered line v8 */
1515+second()
1616+1717+/* istanbul ignore next -- @preserve, Uncovered line istanbul */
1818+second()
+9
test/coverage-test/fixtures/src/implicit-else.ts
···11+export function implicitElse(condition: boolean) {
22+ let a = 1
33+44+ if (condition) {
55+ a = 2
66+ }
77+88+ return a
99+}
···11+export function sum(a: number, b: number) {
22+ return a + b
33+}
44+55+export function subtract(a: number, b: number) {
66+ return a - b
77+}
88+99+export function multiply(a: number, b: number) {
1010+ return a * b
1111+}
1212+1313+export function remainder(a: number, b:number) {
1414+ return a % b
1515+}
···11+/**
22+ * The variable below is modified by custom Vite plugin
33+ */
44+export const padding = 'default-padding'
55+66+export function sum(a: number, b: number) {
77+ /*
88+ * These if-branches should show correctly on coverage report.
99+ * Otherwise source maps are off.
1010+ */
1111+ if (a === 8 && b === 9) {
1212+ // This is not covered by any test
1313+ return 17
1414+ }
1515+ // Comment
1616+ else if (a === 2 && b === 2) {
1717+ // This is covered by SSR test
1818+ return 4
1919+ }
2020+ else if (a === 11 && b === 22) {
2121+ // This is not covered by any test
2222+ return 33
2323+ }
2424+ else if (a === 10 && b === 23) {
2525+ // This is covered by Web test
2626+ return 33
2727+ }
2828+2929+ // This is covered by SSR and Web test, should show 2x hits
3030+ return a + b
3131+}
···11+export type First = 'This' | 'file' | 'should'
22+33+export type Second = 'be' | 'excluded' | 'from' | 'report'
+47
test/coverage-test/fixtures/src/untested-file.ts
···11+/*
22+ * Some top level comment which adds some padding. This helps us see
33+ * if sourcemaps are off.
44+*/
55+66+/* eslint-disable unused-imports/no-unused-vars -- intentional */
77+88+export default function untestedFile() {
99+ return 'This file should end up in report when {"all": true} is given'
1010+}
1111+1212+function add(a: number, b: number) {
1313+ // This line should NOT be covered
1414+ return a + b
1515+}
1616+1717+type TypescriptTypings = 1 | 2
1818+1919+function multiply(a: number, b: number) {
2020+ // This line should NOT be covered
2121+ return a * b
2222+}
2323+2424+export function math(a: number, b: number, operator: '*' | '+') {
2525+ interface MoreCompileTimeCode {
2626+ should: {
2727+ be: {
2828+ excluded: true
2929+ }
3030+ }
3131+ }
3232+3333+ if (operator === '*') {
3434+ // This line should NOT be covered
3535+ return multiply(a, b)
3636+ }
3737+3838+ /* v8 ignore start */
3939+ if (operator === '+') {
4040+ // This line should be excluded
4141+ return add(a, b)
4242+ }
4343+ /* v8 ignore stop */
4444+4545+ // This line should NOT be covered
4646+ throw new Error('Unsupported operator')
4747+}
+10
test/coverage-test/fixtures/src/virtual-files.ts
···11+// @ts-expect-error -- untyped virtual file provided by custom plugin
22+import virtualFile1 from 'virtual:vitest-custom-virtual-file-1'
33+44+55+// @ts-expect-error -- untyped virtual file provided by custom plugin
66+import virtualFile2 from '\0vitest-custom-virtual-file-2'
77+88+export function getVirtualFileImports() {
99+ return { virtualFile1, virtualFile2 }
1010+}
+6
test/coverage-test/fixtures/test/even.test.ts
···11+import { expect, test } from 'vitest'
22+import { isEven } from '../src/even'
33+44+test('isEven', () => {
55+ expect(isEven(6)).toBe(true)
66+})