vitest-coverage-merge
v0.2.0
Published
Merge Vitest unit and browser component test coverage with automatic normalization
Maintainers
Readme
vitest-coverage-merge
Merge Vitest coverage from unit tests (jsdom) and browser component tests.
📝 UPDATE (January 2025):
As of v0.2.0, normalization is now OFF by default. Use
--normalizeif you need to strip import statements and directives.
The Problem
When running Vitest with both jsdom (unit tests) and browser mode (component tests), the coverage reports have different statement counts:
| Environment | Import Handling | |-------------|-----------------| | jsdom | V8 doesn't count imports as statements | | Real browser | V8 counts imports as executable statements |
This makes it impossible to accurately merge coverage without normalization.
The Solution
vitest-coverage-merge provides smart merging of coverage data. When you encounter statement count mismatches, you can use the --normalize flag to strip import statements and Next.js directives ('use client', 'use server') before merging.
Installation
npm install -D vitest-coverage-mergeUsage
CLI
# Merge unit and component coverage
npx vitest-coverage-merge coverage/unit coverage/component -o coverage/merged
# Merge multiple sources
npx vitest-coverage-merge coverage/unit coverage/component coverage/e2e -o coverage/all
# Merge with normalization (strips imports/directives)
npx vitest-coverage-merge coverage/unit coverage/component -o coverage/merged --normalizeOptions
vitest-coverage-merge <dir1> <dir2> [dir3...] -o <output>
Arguments:
<dir1> <dir2> Coverage directories to merge (at least 2 required)
Each directory should contain coverage-final.json
Options:
-o, --output Output directory for merged coverage (required)
--normalize Strip import statements and directives before merging
-h, --help Show help
-v, --version Show versionProgrammatic API
import { mergeCoverage, normalizeCoverage } from 'vitest-coverage-merge'
// Merge coverage directories
const result = await mergeCoverage({
inputDirs: ['coverage/unit', 'coverage/component'],
outputDir: 'coverage/merged',
normalize: false, // default (set to true to strip imports/directives)
reporters: ['json', 'lcov', 'html'], // default
})
console.log(result.statements.pct) // e.g., 85.5Example Vitest Setup
vitest.config.ts (unit tests)
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'jsdom',
include: ['src/**/*.test.{ts,tsx}'],
exclude: ['src/**/*.browser.test.{ts,tsx}'],
coverage: {
enabled: true,
provider: 'v8',
reportsDirectory: './coverage/unit',
reporter: ['json', 'lcov', 'html'],
},
},
})vitest.component.config.ts (browser tests)
import { defineConfig } from 'vitest/config'
import { playwright } from '@vitest/browser-playwright'
export default defineConfig({
test: {
include: ['src/**/*.browser.test.{ts,tsx}'],
coverage: {
enabled: true,
provider: 'v8',
reportsDirectory: './coverage/component',
reporter: ['json', 'lcov', 'html'],
},
browser: {
enabled: true,
provider: playwright(),
instances: [{ browser: 'chromium' }],
},
},
})package.json scripts
{
"scripts": {
"test": "npm run test:unit && npm run test:component",
"test:unit": "vitest run",
"test:component": "vitest run --config vitest.component.config.ts",
"coverage:merge": "vitest-coverage-merge coverage/unit coverage/component -o coverage/merged"
}
}Output
The tool generates:
coverage-final.json- Istanbul coverage datalcov.info- LCOV format for CI toolsindex.html- HTML report (in lcov-report folder)
How It Works
- Load coverage-final.json from each input directory
- Normalize (optional, with
--normalizeflag) by stripping:- ESM import statements (
import ... from '...') - React/Next.js directives (
'use client','use server') - if present
- ESM import statements (
- Smart merge using one of two strategies:
- Default (no
--normalize): "More items wins" - prefers source with more coverage items, giving you the union of all structures - With
--normalize: "Fewer items wins" - prefers sources without directive statements (browser-style coverage)
- Default (no
- Merge execution counts using max strategy (takes highest count for each item)
- Generate reports (JSON, LCOV, HTML)
Note: This tool works with any ESM-based Vitest project (React, Vue, Svelte, vanilla JS/TS, etc.). The React/Next.js directive stripping only applies if those directives are present in your codebase - for non-React projects, it simply has no effect.
Why Not Use Vitest's Built-in Merge?
Vitest's --merge-reports is designed for sharded test runs, not for merging coverage from different environments (jsdom vs browser). It doesn't handle the statement count differences caused by how V8 treats imports differently in each environment.
Related Tools
- nextcov - E2E coverage collection for Next.js with Playwright
- @vitest/coverage-v8 - V8 coverage provider for Vitest
License
MIT
