Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | #!/usr/bin/env node /** * Generate a unified coverage report from all packages */ import { readFileSync, writeFileSync, readdirSync, statSync, mkdirSync, existsSync } from 'fs'; import { join, dirname } from 'path'; import { execSync } from 'child_process'; console.log('🔍 Generating unified coverage report...'); // Ensure coverage directory exists if (!existsSync('coverage')) { mkdirSync('coverage', { recursive: true }); } const packagesDir = 'packages'; const packageDirs = readdirSync(packagesDir).filter(dir => { const dirPath = join(packagesDir, dir); return statSync(dirPath).isDirectory(); }); const totalFunctions = 0; const coveredFunctions = 0; const totalBranches = 0; const coveredBranches = 0; const totalLines = 0; const coveredLines = 0; const packageCoverage = []; let combinedLcov = ''; // Collect coverage from each package for (const packageDir of packageDirs) { const packagePath = join(packagesDir, packageDir); const coveragePath = join(packagePath, 'coverage'); const lcovPath = join(coveragePath, 'lcov.info'); if (existsSync(lcovPath)) { console.log(`📊 Found coverage for ${packageDir}`); // Read LCOV data const lcovContent = readFileSync(lcovPath, 'utf-8'); combinedLcov += `${lcovContent}\n`; // Parse coverage summary (basic parsing) const lines = lcovContent.split('\n'); const packageStats = { name: packageDir, statements: { covered: 0, total: 0, pct: 0 }, functions: { covered: 0, total: 0, pct: 0 }, branches: { covered: 0, total: 0, pct: 0 }, lines: { covered: 0, total: 0, pct: 0 } }; for (const line of lines) { if (line.startsWith('LF:')) { packageStats.lines.total += parseInt(line.split(':')[1]); totalLines += parseInt(line.split(':')[1]); } else if (line.startsWith('LH:')) { packageStats.lines.covered += parseInt(line.split(':')[1]); coveredLines += parseInt(line.split(':')[1]); } else if (line.startsWith('BRF:')) { packageStats.branches.total += parseInt(line.split(':')[1]); totalBranches += parseInt(line.split(':')[1]); } else if (line.startsWith('BRH:')) { packageStats.branches.covered += parseInt(line.split(':')[1]); coveredBranches += parseInt(line.split(':')[1]); } else if (line.startsWith('FNF:')) { packageStats.functions.total += parseInt(line.split(':')[1]); totalFunctions += parseInt(line.split(':')[1]); } else if (line.startsWith('FNH:')) { packageStats.functions.covered += parseInt(line.split(':')[1]); coveredFunctions += parseInt(line.split(':')[1]); } } // Calculate percentages packageStats.lines.pct = packageStats.lines.total > 0 ? Math.round((packageStats.lines.covered / packageStats.lines.total) * 100) : 0; packageStats.branches.pct = packageStats.branches.total > 0 ? Math.round((packageStats.branches.covered / packageStats.branches.total) * 100) : 0; packageStats.functions.pct = packageStats.functions.total > 0 ? Math.round((packageStats.functions.covered / packageStats.functions.total) * 100) : 0; packageCoverage.push(packageStats); } else { console.log(`⚠️ No coverage found for ${packageDir}`); } } // Calculate overall percentages const overallLinesPct = totalLines > 0 ? Math.round((coveredLines / totalLines) * 100) : 0; const overallBranchesPct = totalBranches > 0 ? Math.round((coveredBranches / totalBranches) * 100) : 0; const overallFunctionsPct = totalFunctions > 0 ? Math.round((coveredFunctions / totalFunctions) * 100) : 0; // Write combined LCOV file writeFileSync('coverage/lcov.info', combinedLcov); // Generate coverage summary JSON const coverageSummary = { total: { lines: { covered: coveredLines, total: totalLines, pct: overallLinesPct }, functions: { covered: coveredFunctions, total: totalFunctions, pct: overallFunctionsPct }, branches: { covered: coveredBranches, total: totalBranches, pct: overallBranchesPct }, statements: { covered: coveredLines, total: totalLines, pct: overallLinesPct } }, packages: packageCoverage }; writeFileSync('coverage-summary.json', JSON.stringify(coverageSummary, null, 2)); // Generate human-readable report let report = `# Test Coverage Report Generated: ${new Date().toISOString()} ## Overall Coverage - **Lines**: ${overallLinesPct}% (${coveredLines}/${totalLines}) - **Functions**: ${overallFunctionsPct}% (${coveredFunctions}/${totalFunctions}) - **Branches**: ${overallBranchesPct}% (${coveredBranches}/${totalBranches}) ## Package Coverage | Package | Lines | Functions | Branches | |---------|-------|-----------|----------| `; for (const pkg of packageCoverage) { report += `| ${pkg.name} | ${pkg.lines.pct}% (${pkg.lines.covered}/${pkg.lines.total}) | ${pkg.functions.pct}% (${pkg.functions.covered}/${pkg.functions.total}) | ${pkg.branches.pct}% (${pkg.branches.covered}/${pkg.branches.total}) |\n`; } writeFileSync('coverage/coverage-report.md', report); // Generate badge data const badgeColor = overallLinesPct >= 80 ? 'brightgreen' : overallLinesPct >= 60 ? 'yellow' : 'red'; const badgeData = { schemaVersion: 1, label: 'coverage', message: `${overallLinesPct}%`, color: badgeColor }; writeFileSync('coverage/badge.json', JSON.stringify(badgeData, null, 2)); console.log(`✅ Coverage report generated successfully!`); console.log(`📊 Overall coverage: ${overallLinesPct}% lines, ${overallFunctionsPct}% functions, ${overallBranchesPct}% branches`); console.log(`📁 Reports saved to coverage/ directory`); |