vitest-coverage-merge 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Steve Zhang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # vitest-coverage-merge
2
+
3
+ Merge Vitest coverage from unit tests (jsdom) and browser component tests with automatic normalization.
4
+
5
+ ## The Problem
6
+
7
+ When running Vitest with both jsdom (unit tests) and browser mode (component tests), the coverage reports have different statement counts:
8
+
9
+ | Environment | Import Handling |
10
+ |-------------|-----------------|
11
+ | jsdom | V8 doesn't count imports as statements |
12
+ | Real browser | V8 counts imports as executable statements |
13
+
14
+ This makes it impossible to accurately merge coverage without normalization.
15
+
16
+ ## The Solution
17
+
18
+ `vitest-coverage-merge` automatically strips import statements and Next.js directives (`'use client'`, `'use server'`) from coverage data before merging, ensuring consistent statement counts across all sources.
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install -D vitest-coverage-merge
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ### CLI
29
+
30
+ ```bash
31
+ # Merge unit and component coverage
32
+ npx vitest-coverage-merge coverage/unit coverage/component -o coverage/merged
33
+
34
+ # Merge multiple sources
35
+ npx vitest-coverage-merge coverage/unit coverage/component coverage/e2e -o coverage/all
36
+ ```
37
+
38
+ ### Options
39
+
40
+ ```
41
+ vitest-coverage-merge <dir1> <dir2> [dir3...] -o <output>
42
+
43
+ Arguments:
44
+ <dir1> <dir2> Coverage directories to merge (at least 2 required)
45
+ Each directory should contain coverage-final.json
46
+
47
+ Options:
48
+ -o, --output Output directory for merged coverage (required)
49
+ --no-normalize Skip import/directive stripping (not recommended)
50
+ -h, --help Show help
51
+ -v, --version Show version
52
+ ```
53
+
54
+ ### Programmatic API
55
+
56
+ ```typescript
57
+ import { mergeCoverage, normalizeCoverage } from 'vitest-coverage-merge'
58
+
59
+ // Merge coverage directories
60
+ const result = await mergeCoverage({
61
+ inputDirs: ['coverage/unit', 'coverage/component'],
62
+ outputDir: 'coverage/merged',
63
+ normalize: true, // default
64
+ reporters: ['json', 'lcov', 'html'], // default
65
+ })
66
+
67
+ console.log(result.statements.pct) // e.g., 85.5
68
+ ```
69
+
70
+ ## Example Vitest Setup
71
+
72
+ ### vitest.config.ts (unit tests)
73
+
74
+ ```typescript
75
+ import { defineConfig } from 'vitest/config'
76
+
77
+ export default defineConfig({
78
+ test: {
79
+ environment: 'jsdom',
80
+ include: ['src/**/*.test.{ts,tsx}'],
81
+ exclude: ['src/**/*.browser.test.{ts,tsx}'],
82
+ coverage: {
83
+ enabled: true,
84
+ provider: 'v8',
85
+ reportsDirectory: './coverage/unit',
86
+ reporter: ['json', 'lcov', 'html'],
87
+ },
88
+ },
89
+ })
90
+ ```
91
+
92
+ ### vitest.component.config.ts (browser tests)
93
+
94
+ ```typescript
95
+ import { defineConfig } from 'vitest/config'
96
+ import { playwright } from '@vitest/browser-playwright'
97
+
98
+ export default defineConfig({
99
+ test: {
100
+ include: ['src/**/*.browser.test.{ts,tsx}'],
101
+ coverage: {
102
+ enabled: true,
103
+ provider: 'v8',
104
+ reportsDirectory: './coverage/component',
105
+ reporter: ['json', 'lcov', 'html'],
106
+ },
107
+ browser: {
108
+ enabled: true,
109
+ provider: playwright(),
110
+ instances: [{ browser: 'chromium' }],
111
+ },
112
+ },
113
+ })
114
+ ```
115
+
116
+ ### package.json scripts
117
+
118
+ ```json
119
+ {
120
+ "scripts": {
121
+ "test": "npm run test:unit && npm run test:component",
122
+ "test:unit": "vitest run",
123
+ "test:component": "vitest run --config vitest.component.config.ts",
124
+ "coverage:merge": "vitest-coverage-merge coverage/unit coverage/component -o coverage/merged"
125
+ }
126
+ }
127
+ ```
128
+
129
+ ## Output
130
+
131
+ The tool generates:
132
+ - `coverage-final.json` - Istanbul coverage data
133
+ - `lcov.info` - LCOV format for CI tools
134
+ - `index.html` - HTML report (in lcov-report folder)
135
+
136
+ ## How It Works
137
+
138
+ 1. **Load** coverage-final.json from each input directory
139
+ 2. **Normalize** by stripping:
140
+ - ESM import statements (`import ... from '...'`)
141
+ - React/Next.js directives (`'use client'`, `'use server'`) - if present
142
+ 3. **Smart merge** - selects the best coverage structure (browser tests preferred) and merges execution counts
143
+ 4. **Generate** reports (JSON, LCOV, HTML)
144
+
145
+ > **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. CommonJS `require()` statements are not stripped because V8 treats them consistently in both jsdom and browser environments.
146
+
147
+ ## Why Not Use Vitest's Built-in Merge?
148
+
149
+ 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.
150
+
151
+ ## Related Tools
152
+
153
+ - [nextcov](https://github.com/stevez/nextcov) - E2E coverage collection for Next.js with Playwright
154
+ - [@vitest/coverage-v8](https://www.npmjs.com/package/@vitest/coverage-v8) - V8 coverage provider for Vitest
155
+
156
+ ## License
157
+
158
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync } from 'fs';
3
+ import { resolve } from 'path';
4
+ import { mergeCoverage } from './index.js';
5
+ function printUsage() {
6
+ console.log(`
7
+ vitest-coverage-merge - Merge Vitest coverage from unit and browser tests
8
+
9
+ Usage:
10
+ vitest-coverage-merge <dir1> <dir2> [dir3...] -o <output>
11
+
12
+ Arguments:
13
+ <dir1> <dir2> Coverage directories to merge (at least 2 required)
14
+ Each directory should contain coverage-final.json
15
+
16
+ Options:
17
+ -o, --output Output directory for merged coverage (required)
18
+ --no-normalize Skip import/directive stripping (not recommended)
19
+ -h, --help Show this help message
20
+ -v, --version Show version
21
+
22
+ Examples:
23
+ vitest-coverage-merge coverage/unit coverage/component -o coverage/merged
24
+ vitest-coverage-merge coverage/unit coverage/browser coverage/e2e -o coverage/all
25
+
26
+ The tool automatically normalizes coverage by:
27
+ - Stripping import statements (counted differently in jsdom vs browser)
28
+ - Stripping 'use client'/'use server' directives
29
+ - Merging statement, branch, and function coverage
30
+ `);
31
+ }
32
+ function printVersion() {
33
+ console.log('vitest-coverage-merge v0.1.0');
34
+ }
35
+ function parseArgs(args) {
36
+ const result = {
37
+ inputDirs: [],
38
+ outputDir: null,
39
+ normalize: true,
40
+ help: false,
41
+ version: false,
42
+ error: null,
43
+ };
44
+ let i = 0;
45
+ while (i < args.length) {
46
+ const arg = args[i];
47
+ if (arg === '-h' || arg === '--help') {
48
+ result.help = true;
49
+ return result;
50
+ }
51
+ if (arg === '-v' || arg === '--version') {
52
+ result.version = true;
53
+ return result;
54
+ }
55
+ if (arg === '-o' || arg === '--output') {
56
+ i++;
57
+ if (i >= args.length) {
58
+ result.error = 'Missing output directory after -o/--output';
59
+ return result;
60
+ }
61
+ result.outputDir = args[i];
62
+ }
63
+ else if (arg === '--no-normalize') {
64
+ result.normalize = false;
65
+ }
66
+ else if (arg.startsWith('-')) {
67
+ result.error = `Unknown option: ${arg}`;
68
+ return result;
69
+ }
70
+ else {
71
+ result.inputDirs.push(arg);
72
+ }
73
+ i++;
74
+ }
75
+ return result;
76
+ }
77
+ async function main() {
78
+ const args = process.argv.slice(2);
79
+ if (args.length === 0) {
80
+ printUsage();
81
+ process.exit(0);
82
+ }
83
+ const parsed = parseArgs(args);
84
+ if (parsed.error) {
85
+ console.error(`Error: ${parsed.error}`);
86
+ process.exit(1);
87
+ }
88
+ if (parsed.help) {
89
+ printUsage();
90
+ process.exit(0);
91
+ }
92
+ if (parsed.version) {
93
+ printVersion();
94
+ process.exit(0);
95
+ }
96
+ if (parsed.inputDirs.length < 2) {
97
+ console.error('Error: At least 2 coverage directories are required');
98
+ process.exit(1);
99
+ }
100
+ if (!parsed.outputDir) {
101
+ console.error('Error: Output directory is required (-o <dir>)');
102
+ process.exit(1);
103
+ }
104
+ // Validate input directories
105
+ const validDirs = [];
106
+ const skippedDirs = [];
107
+ for (const dir of parsed.inputDirs) {
108
+ const resolvedDir = resolve(dir);
109
+ const coverageFile = resolve(resolvedDir, 'coverage-final.json');
110
+ if (!existsSync(resolvedDir)) {
111
+ console.log(`Skipped (not found): ${dir}`);
112
+ skippedDirs.push(dir);
113
+ }
114
+ else if (!existsSync(coverageFile)) {
115
+ console.log(`Skipped (no coverage-final.json): ${dir}`);
116
+ skippedDirs.push(dir);
117
+ }
118
+ else {
119
+ validDirs.push(resolvedDir);
120
+ }
121
+ }
122
+ if (validDirs.length < 2) {
123
+ console.error('Error: Need at least 2 valid coverage directories to merge');
124
+ process.exit(1);
125
+ }
126
+ const outputDir = resolve(parsed.outputDir);
127
+ try {
128
+ await mergeCoverage({
129
+ inputDirs: validDirs,
130
+ outputDir,
131
+ normalize: parsed.normalize,
132
+ });
133
+ console.log(`\nMerged coverage written to: ${outputDir}`);
134
+ }
135
+ catch (error) {
136
+ console.error('Error merging coverage:', error);
137
+ process.exit(1);
138
+ }
139
+ }
140
+ main();
141
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ export interface MergeOptions {
2
+ inputDirs: string[];
3
+ outputDir: string;
4
+ normalize?: boolean;
5
+ reporters?: string[];
6
+ }
7
+ export interface MergeResult {
8
+ totalFiles: number;
9
+ statements: {
10
+ covered: number;
11
+ total: number;
12
+ pct: number;
13
+ };
14
+ branches: {
15
+ covered: number;
16
+ total: number;
17
+ pct: number;
18
+ };
19
+ functions: {
20
+ covered: number;
21
+ total: number;
22
+ pct: number;
23
+ };
24
+ lines: {
25
+ covered: number;
26
+ total: number;
27
+ pct: number;
28
+ };
29
+ }
30
+ /**
31
+ * Merge coverage from multiple Vitest runs.
32
+ *
33
+ * This handles the jsdom vs browser statement count difference by
34
+ * normalizing (stripping imports/directives) before merging.
35
+ */
36
+ export declare function mergeCoverage(options: MergeOptions): Promise<MergeResult>;
37
+ export { normalizeCoverage } from './normalize.js';
38
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IAC3D,QAAQ,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IACzD,SAAS,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1D,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CACvD;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA2G/E;AAGD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,103 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import libCoverage from 'istanbul-lib-coverage';
4
+ import libReport from 'istanbul-lib-report';
5
+ import reports from 'istanbul-reports';
6
+ import { normalizeCoverage } from './normalize.js';
7
+ import { smartMergeCoverage } from './smart-merge.js';
8
+ /**
9
+ * Merge coverage from multiple Vitest runs.
10
+ *
11
+ * This handles the jsdom vs browser statement count difference by
12
+ * normalizing (stripping imports/directives) before merging.
13
+ */
14
+ export async function mergeCoverage(options) {
15
+ const { inputDirs, outputDir, normalize = true, reporters = ['json', 'lcov', 'html'], } = options;
16
+ // Load all coverage data
17
+ const coverageMaps = [];
18
+ let totalImportsRemoved = 0;
19
+ let totalDirectivesRemoved = 0;
20
+ for (const dir of inputDirs) {
21
+ const coverageFile = join(dir, 'coverage-final.json');
22
+ if (!existsSync(coverageFile)) {
23
+ console.log(`Skipped (no coverage-final.json): ${dir}`);
24
+ continue;
25
+ }
26
+ console.log(`Loading: ${coverageFile}`);
27
+ const rawData = readFileSync(coverageFile, 'utf-8');
28
+ let coverageData = JSON.parse(rawData);
29
+ if (normalize) {
30
+ const result = normalizeCoverage(coverageData);
31
+ coverageData = result.coverageMap;
32
+ totalImportsRemoved += result.importsRemoved;
33
+ totalDirectivesRemoved += result.directivesRemoved;
34
+ }
35
+ coverageMaps.push(coverageData);
36
+ }
37
+ if (normalize && (totalImportsRemoved > 0 || totalDirectivesRemoved > 0)) {
38
+ console.log(`Normalized: removed ${totalImportsRemoved} import(s), ${totalDirectivesRemoved} directive(s)`);
39
+ }
40
+ // Smart merge: use source with fewer items as baseline
41
+ const mergedData = smartMergeCoverage(coverageMaps);
42
+ const mergedMap = libCoverage.createCoverageMap(mergedData);
43
+ // Create output directory
44
+ if (!existsSync(outputDir)) {
45
+ mkdirSync(outputDir, { recursive: true });
46
+ }
47
+ // Write coverage-final.json
48
+ const coverageJson = JSON.stringify(mergedMap.toJSON(), null, 2);
49
+ writeFileSync(join(outputDir, 'coverage-final.json'), coverageJson);
50
+ // Generate reports
51
+ const context = libReport.createContext({
52
+ dir: outputDir,
53
+ defaultSummarizer: 'nested',
54
+ coverageMap: mergedMap,
55
+ });
56
+ // Use Set to avoid duplicate reporters
57
+ const uniqueReporters = [...new Set(reporters)];
58
+ for (const reporter of uniqueReporters) {
59
+ try {
60
+ const report = reports.create(reporter);
61
+ report.execute(context);
62
+ }
63
+ catch (error) {
64
+ console.warn(`Warning: Failed to generate ${reporter} report:`, error);
65
+ }
66
+ }
67
+ // Calculate summary
68
+ const summary = mergedMap.getCoverageSummary();
69
+ const result = {
70
+ totalFiles: Object.keys(mergedMap.toJSON()).length,
71
+ statements: {
72
+ covered: summary.statements.covered,
73
+ total: summary.statements.total,
74
+ pct: summary.statements.pct,
75
+ },
76
+ branches: {
77
+ covered: summary.branches.covered,
78
+ total: summary.branches.total,
79
+ pct: summary.branches.pct,
80
+ },
81
+ functions: {
82
+ covered: summary.functions.covered,
83
+ total: summary.functions.total,
84
+ pct: summary.functions.pct,
85
+ },
86
+ lines: {
87
+ covered: summary.lines.covered,
88
+ total: summary.lines.total,
89
+ pct: summary.lines.pct,
90
+ },
91
+ };
92
+ // Print summary
93
+ console.log('\n=============================== Coverage summary ===============================');
94
+ console.log(`Statements : ${result.statements.pct.toFixed(2)}% ( ${result.statements.covered}/${result.statements.total} )`);
95
+ console.log(`Branches : ${result.branches.pct.toFixed(2)}% ( ${result.branches.covered}/${result.branches.total} )`);
96
+ console.log(`Functions : ${result.functions.pct.toFixed(2)}% ( ${result.functions.covered}/${result.functions.total} )`);
97
+ console.log(`Lines : ${result.lines.pct.toFixed(2)}% ( ${result.lines.covered}/${result.lines.total} )`);
98
+ console.log('================================================================================');
99
+ return result;
100
+ }
101
+ // Re-export for programmatic use
102
+ export { normalizeCoverage } from './normalize.js';
103
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { CoverageMapData } from 'istanbul-lib-coverage';
2
+ export interface NormalizeResult {
3
+ coverageMap: CoverageMapData;
4
+ importsRemoved: number;
5
+ directivesRemoved: number;
6
+ }
7
+ /**
8
+ * Normalize coverage data by stripping import statements and Next.js directives.
9
+ *
10
+ * This is necessary because:
11
+ * - jsdom V8 doesn't count import statements as statements
12
+ * - Real browser V8 counts import statements as executable statements
13
+ * - Next.js bundled code has different statement structure
14
+ *
15
+ * By stripping these from all coverage sources, we can merge them accurately.
16
+ */
17
+ export declare function normalizeCoverage(coverageMap: CoverageMapData): NormalizeResult;
18
+ //# sourceMappingURL=normalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAoB,MAAM,uBAAuB,CAAA;AAE9E,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,eAAe,CAAA;IAC5B,cAAc,EAAE,MAAM,CAAA;IACtB,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,eAAe,GAAG,eAAe,CAW/E"}
@@ -0,0 +1,76 @@
1
+ import { readFileSync, existsSync } from 'fs';
2
+ /**
3
+ * Normalize coverage data by stripping import statements and Next.js directives.
4
+ *
5
+ * This is necessary because:
6
+ * - jsdom V8 doesn't count import statements as statements
7
+ * - Real browser V8 counts import statements as executable statements
8
+ * - Next.js bundled code has different statement structure
9
+ *
10
+ * By stripping these from all coverage sources, we can merge them accurately.
11
+ */
12
+ export function normalizeCoverage(coverageMap) {
13
+ let importsRemoved = 0;
14
+ let directivesRemoved = 0;
15
+ for (const [filePath, fileData] of Object.entries(coverageMap)) {
16
+ const result = normalizeFileCoverage(filePath, fileData);
17
+ importsRemoved += result.importsRemoved;
18
+ directivesRemoved += result.directivesRemoved;
19
+ }
20
+ return { coverageMap, importsRemoved, directivesRemoved };
21
+ }
22
+ function normalizeFileCoverage(filePath, fileData) {
23
+ let importsRemoved = 0;
24
+ let directivesRemoved = 0;
25
+ // Read source file to check line content
26
+ let lines = [];
27
+ try {
28
+ if (existsSync(filePath)) {
29
+ lines = readFileSync(filePath, 'utf-8').split('\n');
30
+ }
31
+ }
32
+ catch {
33
+ // File not found, skip normalization for this file
34
+ return { importsRemoved: 0, directivesRemoved: 0 };
35
+ }
36
+ if (lines.length === 0) {
37
+ return { importsRemoved: 0, directivesRemoved: 0 };
38
+ }
39
+ // Find statement keys to remove
40
+ const keysToRemove = [];
41
+ for (const [key, stmt] of Object.entries(fileData.statementMap || {})) {
42
+ const lineNum = stmt.start.line;
43
+ const lineContent = lines[lineNum - 1]?.trim() || '';
44
+ // Check if line is an import statement
45
+ if (lineContent.startsWith('import ') || lineContent.startsWith('import{')) {
46
+ keysToRemove.push({ key, type: 'import' });
47
+ }
48
+ // Check if line is a 'use server' or 'use client' directive
49
+ else if (isDirective(lineContent)) {
50
+ keysToRemove.push({ key, type: 'directive' });
51
+ }
52
+ }
53
+ // Remove statements
54
+ for (const { key, type } of keysToRemove) {
55
+ delete fileData.statementMap[key];
56
+ delete fileData.s[key];
57
+ if (type === 'import') {
58
+ importsRemoved++;
59
+ }
60
+ else {
61
+ directivesRemoved++;
62
+ }
63
+ }
64
+ return { importsRemoved, directivesRemoved };
65
+ }
66
+ function isDirective(line) {
67
+ return (line === "'use server'" ||
68
+ line === '"use server"' ||
69
+ line === "'use server';" ||
70
+ line === '"use server";' ||
71
+ line === "'use client'" ||
72
+ line === '"use client"' ||
73
+ line === "'use client';" ||
74
+ line === '"use client";');
75
+ }
76
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { CoverageMapData } from 'istanbul-lib-coverage';
2
+ /**
3
+ * Smart merge multiple coverage maps.
4
+ *
5
+ * This uses a "fewer items wins" strategy for structure selection,
6
+ * which preserves the statement counts from the source without
7
+ * import statement inflation.
8
+ */
9
+ export declare function smartMergeCoverage(coverageMaps: CoverageMapData[]): CoverageMapData;
10
+ //# sourceMappingURL=smart-merge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smart-merge.d.ts","sourceRoot":"","sources":["../src/smart-merge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAoB,MAAM,uBAAuB,CAAA;AAuN9E;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,eAAe,EAAE,GAAG,eAAe,CAgCnF"}
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Create a unique key for a location (exact match)
3
+ */
4
+ function locationKey(loc) {
5
+ return `${loc.start.line}:${loc.start.column}`;
6
+ }
7
+ /**
8
+ * Get the line number from a location (for line-based fallback matching)
9
+ */
10
+ function lineKey(loc) {
11
+ return loc.start.line;
12
+ }
13
+ /**
14
+ * Build lookup maps from file coverage data for efficient merging.
15
+ */
16
+ function buildLookups(data) {
17
+ const stmts = new Map();
18
+ const stmtsByLine = new Map();
19
+ for (const [key, loc] of Object.entries(data.statementMap || {})) {
20
+ const count = data.s[key] || 0;
21
+ if (count > 0) {
22
+ stmts.set(locationKey(loc), count);
23
+ const line = lineKey(loc);
24
+ stmtsByLine.set(line, Math.max(stmtsByLine.get(line) || 0, count));
25
+ }
26
+ }
27
+ const fns = new Map();
28
+ const fnsByLine = new Map();
29
+ for (const [key, fn] of Object.entries(data.fnMap || {})) {
30
+ const count = data.f[key] || 0;
31
+ if (count > 0) {
32
+ fns.set(locationKey(fn.loc), count);
33
+ const line = lineKey(fn.loc);
34
+ fnsByLine.set(line, Math.max(fnsByLine.get(line) || 0, count));
35
+ }
36
+ }
37
+ const branches = new Map();
38
+ const branchesByLine = new Map();
39
+ for (const [key, branch] of Object.entries(data.branchMap || {})) {
40
+ const counts = data.b[key] || [];
41
+ if (counts.some((c) => c > 0)) {
42
+ branches.set(locationKey(branch.loc), counts);
43
+ const line = lineKey(branch.loc);
44
+ if (!branchesByLine.has(line)) {
45
+ branchesByLine.set(line, counts);
46
+ }
47
+ }
48
+ }
49
+ return { stmts, stmtsByLine, fns, fnsByLine, branches, branchesByLine };
50
+ }
51
+ /**
52
+ * Select the best source coverage for structure.
53
+ * Prefers coverage WITHOUT L1:0 directive statements (browser/E2E-style).
54
+ * Browser coverage is more accurate because it doesn't count non-executable directives.
55
+ *
56
+ * Rules:
57
+ * 1. Filter out sources with no coverage data (0 items)
58
+ * 2. Among remaining sources, prefer those without L1:0 directives
59
+ * 3. Among sources without directives, prefer the LAST one (component tests by convention)
60
+ * 4. If all sources have directives, pick the one with fewer items
61
+ */
62
+ function selectBestSource(coverages) {
63
+ const getTotalItems = (cov) => {
64
+ return (Object.keys(cov.statementMap || {}).length +
65
+ Object.keys(cov.branchMap || {}).length +
66
+ Object.keys(cov.fnMap || {}).length);
67
+ };
68
+ // Filter out sources with no coverage data at all
69
+ // Preserve original indices for "prefer last" logic
70
+ const nonEmptyWithIndex = coverages
71
+ .map((cov, idx) => ({ cov, idx }))
72
+ .filter(({ cov }) => getTotalItems(cov) > 0);
73
+ if (nonEmptyWithIndex.length === 0) {
74
+ return coverages[0];
75
+ }
76
+ if (nonEmptyWithIndex.length === 1) {
77
+ return nonEmptyWithIndex[0].cov;
78
+ }
79
+ // Check which coverages have L1:0 directive statements
80
+ const withDirective = [];
81
+ const withoutDirective = [];
82
+ for (const item of nonEmptyWithIndex) {
83
+ const hasDirective = Object.values(item.cov.statementMap || {}).some((loc) => {
84
+ const typedLoc = loc;
85
+ return typedLoc.start.line === 1 && (typedLoc.start.column === 0 || typedLoc.start.column === null);
86
+ });
87
+ if (hasDirective) {
88
+ withDirective.push(item);
89
+ }
90
+ else {
91
+ withoutDirective.push(item);
92
+ }
93
+ }
94
+ // Prefer coverage without directive (browser-style)
95
+ if (withoutDirective.length > 0) {
96
+ // Among sources without directives, prefer the LAST one in the original array
97
+ // By convention, component tests are passed last when merging
98
+ const lastItem = withoutDirective.reduce((best, current) => current.idx > best.idx ? current : best);
99
+ return lastItem.cov;
100
+ }
101
+ // All non-empty sources have directives - pick the one with fewer items
102
+ return nonEmptyWithIndex.reduce((best, current) => getTotalItems(current.cov) < getTotalItems(best.cov) ? current : best).cov;
103
+ }
104
+ /**
105
+ * Smart merge of multiple file coverages.
106
+ * Uses the source with fewer items as the baseline structure,
107
+ * then merges execution counts from all sources.
108
+ */
109
+ function mergeFileCoverages(coverages) {
110
+ if (coverages.length === 0) {
111
+ throw new Error('No coverages to merge');
112
+ }
113
+ if (coverages.length === 1) {
114
+ return JSON.parse(JSON.stringify(coverages[0]));
115
+ }
116
+ // Select best structure (fewer items)
117
+ const bestSource = selectBestSource(coverages);
118
+ // Build lookup maps for all coverages
119
+ const allLookups = coverages.map(buildLookups);
120
+ // Start with best structure (deep copy)
121
+ const merged = {
122
+ path: coverages[0].path,
123
+ statementMap: JSON.parse(JSON.stringify(bestSource.statementMap)),
124
+ s: JSON.parse(JSON.stringify(bestSource.s)),
125
+ fnMap: JSON.parse(JSON.stringify(bestSource.fnMap)),
126
+ f: JSON.parse(JSON.stringify(bestSource.f)),
127
+ branchMap: JSON.parse(JSON.stringify(bestSource.branchMap)),
128
+ b: JSON.parse(JSON.stringify(bestSource.b)),
129
+ };
130
+ // Merge statement counts from all sources
131
+ for (const [key, loc] of Object.entries(merged.statementMap)) {
132
+ const locKey = locationKey(loc);
133
+ const line = lineKey(loc);
134
+ let maxCount = merged.s[key] || 0;
135
+ for (const lookup of allLookups) {
136
+ const count = lookup.stmts.get(locKey) ?? lookup.stmtsByLine.get(line);
137
+ if (count !== undefined && count > maxCount) {
138
+ maxCount = count;
139
+ }
140
+ }
141
+ merged.s[key] = maxCount;
142
+ }
143
+ // Merge function counts from all sources
144
+ for (const [key, fn] of Object.entries(merged.fnMap)) {
145
+ const locKey = locationKey(fn.loc);
146
+ const line = lineKey(fn.loc);
147
+ let maxCount = merged.f[key] || 0;
148
+ for (const lookup of allLookups) {
149
+ const count = lookup.fns.get(locKey) ?? lookup.fnsByLine.get(line);
150
+ if (count !== undefined && count > maxCount) {
151
+ maxCount = count;
152
+ }
153
+ }
154
+ merged.f[key] = maxCount;
155
+ }
156
+ // Merge branch counts from all sources
157
+ for (const [key, branch] of Object.entries(merged.branchMap)) {
158
+ const locKey = locationKey(branch.loc);
159
+ const line = lineKey(branch.loc);
160
+ const baseCounts = merged.b[key] || [];
161
+ for (const lookup of allLookups) {
162
+ const counts = lookup.branches.get(locKey) ?? lookup.branchesByLine.get(line);
163
+ if (counts !== undefined) {
164
+ merged.b[key] = baseCounts.map((c, i) => Math.max(c, counts[i] || 0));
165
+ }
166
+ }
167
+ }
168
+ return merged;
169
+ }
170
+ /**
171
+ * Smart merge multiple coverage maps.
172
+ *
173
+ * This uses a "fewer items wins" strategy for structure selection,
174
+ * which preserves the statement counts from the source without
175
+ * import statement inflation.
176
+ */
177
+ export function smartMergeCoverage(coverageMaps) {
178
+ if (coverageMaps.length === 0) {
179
+ return {};
180
+ }
181
+ if (coverageMaps.length === 1) {
182
+ return JSON.parse(JSON.stringify(coverageMaps[0]));
183
+ }
184
+ // Collect all files from all maps
185
+ const allFiles = new Set();
186
+ for (const map of coverageMaps) {
187
+ for (const file of Object.keys(map)) {
188
+ allFiles.add(file);
189
+ }
190
+ }
191
+ const merged = {};
192
+ for (const file of allFiles) {
193
+ const fileCoverages = coverageMaps
194
+ .filter((m) => file in m)
195
+ .map((m) => m[file]);
196
+ if (fileCoverages.length === 1) {
197
+ merged[file] = JSON.parse(JSON.stringify(fileCoverages[0]));
198
+ }
199
+ else {
200
+ merged[file] = mergeFileCoverages(fileCoverages);
201
+ }
202
+ }
203
+ return merged;
204
+ }
205
+ //# sourceMappingURL=smart-merge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smart-merge.js","sourceRoot":"","sources":["../src/smart-merge.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "vitest-coverage-merge",
3
+ "version": "0.1.0",
4
+ "description": "Merge Vitest unit and browser component test coverage with automatic normalization",
5
+ "author": "Steve Zhang",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "bin": {
12
+ "vitest-coverage-merge": "./dist/cli.js"
13
+ },
14
+ "exports": {
15
+ ".": {
16
+ "import": "./dist/index.js",
17
+ "types": "./dist/index.d.ts"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "dev": "tsc --watch",
26
+ "test": "vitest run",
27
+ "test:watch": "vitest",
28
+ "lint": "eslint src",
29
+ "prepublishOnly": "npm run build"
30
+ },
31
+ "keywords": [
32
+ "vitest",
33
+ "coverage",
34
+ "merge",
35
+ "istanbul",
36
+ "v8",
37
+ "unit-test",
38
+ "component-test",
39
+ "browser-test"
40
+ ],
41
+ "dependencies": {
42
+ "istanbul-lib-coverage": "^3.2.2",
43
+ "istanbul-lib-report": "^3.0.1",
44
+ "istanbul-reports": "^3.1.7"
45
+ },
46
+ "devDependencies": {
47
+ "@types/istanbul-lib-coverage": "^2.0.6",
48
+ "@types/istanbul-lib-report": "^3.0.3",
49
+ "@types/istanbul-reports": "^3.0.4",
50
+ "@types/node": "^22.10.0",
51
+ "@vitest/coverage-v8": "^4.0.16",
52
+ "typescript": "^5.7.0",
53
+ "vitest": "^4.0.15"
54
+ },
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "https://github.com/stevez/vitest-coverage-merge.git"
58
+ },
59
+ "engines": {
60
+ "node": ">=18"
61
+ }
62
+ }