coverage-analysis

Coverage Analysis Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "coverage-analysis" with this command: npx skills add sgcarstrends/sgcarstrends/sgcarstrends-sgcarstrends-coverage-analysis

Coverage Analysis Skill

This skill helps you analyze and improve test coverage across the monorepo using Vitest's V8 coverage provider.

When to Use This Skill

  • Analyzing test coverage across packages

  • Identifying untested code paths

  • Setting coverage thresholds

  • Generating coverage reports

  • Improving test quality

  • Pre-deployment coverage checks

  • Code review coverage validation

Coverage Overview

The project uses Vitest with V8 coverage provider for:

  • Line coverage: Percentage of lines executed

  • Branch coverage: Percentage of conditional branches tested

  • Function coverage: Percentage of functions called

  • Statement coverage: Percentage of statements executed

Configuration

Vitest Coverage Config

// vitest.config.ts (root or package-level) import { defineConfig } from "vitest/config";

export default defineConfig({ test: { globals: true, environment: "node", // or "jsdom" for frontend coverage: { provider: "v8", reporter: ["text", "json", "html", "lcov"], reportsDirectory: "./coverage", exclude: [ "node_modules/", "tests/", "/*.test.ts", "/.spec.ts", "dist/", "build/", ".config.ts", ".config.js", ".next/", ".turbo/", ], include: ["src/**/.ts", "src/**/*.tsx"], all: true, thresholds: { lines: 80, functions: 80, branches: 80, statements: 80, }, }, }, });

Package-Specific Configs

API Package:

// apps/api/vitest.config.ts import { defineConfig } from "vitest/config";

export default defineConfig({ test: { coverage: { provider: "v8", reporter: ["text", "json", "html"], thresholds: { lines: 85, // Higher threshold for backend functions: 85, branches: 80, statements: 85, }, exclude: [ "tests/", "src/index.ts", // Exclude entry point "src/config/**", // Exclude config files ], }, }, });

Web Package:

// apps/web/vitest.config.ts import { defineConfig } from "vitest/config"; import react from "@vitejs/plugin-react";

export default defineConfig({ plugins: [react()], test: { environment: "jsdom", coverage: { provider: "v8", reporter: ["text", "json", "html"], thresholds: { lines: 75, // Frontend may have lower threshold functions: 75, branches: 70, statements: 75, }, exclude: [ "tests/", "src/app/", // Exclude Next.js app directory "/.config.", ], }, }, });

Database Package:

// packages/database/vitest.config.ts import { defineConfig } from "vitest/config";

export default defineConfig({ test: { coverage: { provider: "v8", reporter: ["text", "json", "html"], thresholds: { lines: 90, // Very high threshold for critical package functions: 90, branches: 85, statements: 90, }, include: ["src//*.ts"], exclude: [ "tests/", "migrations/", // Exclude migrations ], }, }, });

Running Coverage

Common Commands

Generate coverage for all packages

pnpm test:coverage

Generate coverage for specific package

pnpm -F @sgcarstrends/api test:coverage pnpm -F @sgcarstrends/web test:coverage pnpm -F @sgcarstrends/database test:coverage

Generate coverage with specific reporters

pnpm test:coverage -- --coverage.reporter=html pnpm test:coverage -- --coverage.reporter=lcov

Run tests and generate coverage in watch mode

pnpm test:watch -- --coverage

Generate coverage for changed files only

pnpm test:coverage -- --changed

Package.json Scripts

{ "scripts": { "test": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage", "test:coverage:ui": "vitest --ui --coverage" } }

Coverage Reports

Text Report

Terminal output

pnpm test:coverage

Example output:

----------|---------|----------|---------|---------|-------------------

File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s

----------|---------|----------|---------|---------|-------------------

All files | 87.5 | 83.33 | 85.71 | 87.5 |

cars.ts | 90 | 85 | 100 | 90 | 45-47

coe.ts | 85 | 80 | 75 | 85 | 23, 56-58

----------|---------|----------|---------|---------|-------------------

HTML Report

Generate HTML report

pnpm test:coverage

Open in browser

open coverage/index.html # macOS xdg-open coverage/index.html # Linux start coverage/index.html # Windows

HTML report features:

- Interactive file browser

- Line-by-line coverage visualization

- Color-coded coverage (green = covered, red = uncovered)

- Branch coverage details

JSON Report

Generate JSON report

pnpm test:coverage -- --coverage.reporter=json

Output: coverage/coverage-final.json

{ "path/to/file.ts": { "lines": { "1": 1, "2": 1, "3": 0 }, "functions": { "functionName": 1 }, "branches": { "0": [1, 0] }, "statements": { "1": 1, "2": 1 } } }

LCOV Report

Generate LCOV format (for CI tools like Codecov, Coveralls)

pnpm test:coverage -- --coverage.reporter=lcov

Output: coverage/lcov.info

Coverage Thresholds

Setting Thresholds

// vitest.config.ts export default defineConfig({ test: { coverage: { thresholds: { // Global thresholds lines: 80, functions: 80, branches: 80, statements: 80,

    // Per-file thresholds
    perFile: true,

    // Fail build if below thresholds
    100: false, // Don't require 100% coverage
  },
},

}, });

Enforcing Thresholds in CI

.github/workflows/test.yml

name: Test

on: [push, pull_request]

jobs: coverage: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v2 - uses: actions/setup-node@v4 with: node-version: 20 cache: "pnpm"

  - run: pnpm install
  - run: pnpm test:coverage

  # Fail if coverage below threshold
  - name: Check coverage
    run: |
      if [ $(jq '.total.lines.pct' coverage/coverage-summary.json | cut -d. -f1) -lt 80 ]; then
        echo "Coverage below 80%"
        exit 1
      fi

Analyzing Coverage

Identify Untested Files

Generate coverage for all files (including untested)

pnpm test:coverage -- --coverage.all=true

Find files with 0% coverage

grep -r '"pct": 0' coverage/coverage-final.json

Find Uncovered Lines

Generate HTML report and inspect

pnpm test:coverage open coverage/index.html

Look for red-highlighted lines in HTML report

These are uncovered lines that need tests

Check Branch Coverage

// Example: Find uncovered branches function processData(data: any) { // Branch 1: if condition if (data.value > 10) { return "high"; }

// Branch 2: else condition (uncovered) return "low"; }

// Test both branches describe("processData", () => { it("should return high for values > 10", () => { expect(processData({ value: 15 })).toBe("high"); });

it("should return low for values <= 10", () => { expect(processData({ value: 5 })).toBe("low"); }); });

Improving Coverage

Strategy 1: Test Untested Functions

// Find untested function export function calculateCOEPrice(quota: number, bids: number): number { // Untested return quota > 0 ? bids / quota : 0; }

// Add test describe("calculateCOEPrice", () => { it("should calculate price when quota is positive", () => { expect(calculateCOEPrice(100, 50000)).toBe(500); });

it("should return 0 when quota is 0", () => { expect(calculateCOEPrice(0, 50000)).toBe(0); }); });

Strategy 2: Test Error Paths

// Original: Only happy path tested export async function fetchCarData(month: string) { const res = await fetch(/api/cars?month=${month}); return res.json(); // What if fetch fails? }

// Improved: Test error path describe("fetchCarData", () => { it("should fetch data successfully", async () => { // Happy path test });

it("should handle network errors", async () => { vi.spyOn(global, "fetch").mockRejectedValue(new Error("Network error"));

await expect(fetchCarData("2024-01")).rejects.toThrow("Network error");

});

it("should handle non-200 responses", async () => { vi.spyOn(global, "fetch").mockResolvedValue({ ok: false, status: 500, } as Response);

await expect(fetchCarData("2024-01")).rejects.toThrow();

}); });

Strategy 3: Test Edge Cases

// Original: Basic test export function formatMonth(date: Date): string { return ${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}; }

// Improved: Test edge cases describe("formatMonth", () => { it("should format single-digit months", () => { expect(formatMonth(new Date("2024-01-01"))).toBe("2024-01"); });

it("should format double-digit months", () => { expect(formatMonth(new Date("2024-12-01"))).toBe("2024-12"); });

it("should handle leap years", () => { expect(formatMonth(new Date("2024-02-29"))).toBe("2024-02"); }); });

Strategy 4: Test Conditional Branches

// Function with multiple branches export function getVehicleCategory(type: string): string { if (type === "car") return "Category A"; if (type === "motorcycle") return "Category B"; if (type === "taxi") return "Category C"; return "Unknown"; // Often forgotten! }

// Test all branches describe("getVehicleCategory", () => { it.each([ ["car", "Category A"], ["motorcycle", "Category B"], ["taxi", "Category C"], ["bus", "Unknown"], ])("should return %s for %s", (type, expected) => { expect(getVehicleCategory(type)).toBe(expected); }); });

Coverage in CI/CD

GitHub Actions Integration

.github/workflows/coverage.yml

name: Coverage

on: [push, pull_request]

jobs: coverage: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v2 - uses: actions/setup-node@v4 with: node-version: 20 cache: "pnpm"

  - run: pnpm install
  - run: pnpm test:coverage

  # Upload coverage to Codecov
  - uses: codecov/codecov-action@v3
    with:
      files: ./coverage/lcov.info
      flags: unittests
      name: codecov-umbrella

  # Upload coverage as artifact
  - uses: actions/upload-artifact@v4
    with:
      name: coverage-report
      path: coverage/

Coverage Badges

<!-- README.md --> Coverage

Coverage Exclusions

Exclude Specific Code

// Exclude line /* v8 ignore next */ console.log("Debug statement");

// Exclude block /* v8 ignore start / if (process.env.NODE_ENV === "development") { console.log("Development only"); } / v8 ignore stop */

// Exclude function /* v8 ignore next 5 */ function debugHelper() { // This entire function is excluded console.log("Debug"); }

Exclude Files/Directories

// vitest.config.ts export default defineConfig({ test: { coverage: { exclude: [ // Test files "/tests/", "/*.test.ts", "/*.spec.ts",

    // Config files
    "**/*.config.ts",
    "**/*.config.js",

    // Build output
    "dist/**",
    "build/**",
    ".next/**",

    // Specific files
    "src/index.ts",
    "src/generated/**",

    // External dependencies
    "node_modules/**",
  ],
},

}, });

Monorepo Coverage

Aggregate Coverage

Generate coverage for all packages

pnpm -r test:coverage

Merge coverage reports (requires custom script)

node scripts/merge-coverage.js

Custom Merge Script

// scripts/merge-coverage.ts import { readFileSync, writeFileSync } from "fs"; import { glob } from "glob";

const coverageFiles = glob.sync("/coverage/coverage-final.json", { ignore: ["node_modules/"], });

const merged: any = {};

for (const file of coverageFiles) { const coverage = JSON.parse(readFileSync(file, "utf-8")); Object.assign(merged, coverage); }

writeFileSync("coverage-merged.json", JSON.stringify(merged, null, 2)); console.log("Coverage merged successfully");

Best Practices

  1. Set Realistic Thresholds

// ❌ Too strict (100% is often impractical) thresholds: { lines: 100, functions: 100, branches: 100, statements: 100, }

// ✅ Realistic and achievable thresholds: { lines: 80, functions: 80, branches: 75, statements: 80, }

  1. Exclude Generated Code

// vitest.config.ts coverage: { exclude: [ "src/generated/", ".config.", "tests/", ], }

  1. Focus on Critical Paths

// Prioritize testing: // 1. Business logic // 2. Data transformations // 3. API endpoints // 4. Error handling

// Less critical: // - UI components (test functionality, not styling) // - Configuration files // - Type definitions

  1. Track Coverage Over Time

Store coverage in git (add to .gitignore exceptions)

!coverage/coverage-summary.json

Track changes

git diff coverage/coverage-summary.json

Troubleshooting

Coverage Not Generated

Issue: No coverage directory created

Solution: Ensure tests are running

pnpm test # First run tests pnpm test:coverage # Then generate coverage

Low Coverage Despite Tests

Issue: Coverage config excludes tested files

Solution: Check exclude patterns

vitest.config.ts

coverage: { exclude: [ // Remove overly broad patterns // "src/**", // ❌ This excludes everything! ], }

Coverage Report Empty

Issue: Tests passing but coverage 0%

Solution: Ensure coverage.all is true

coverage: { all: true, // Include all source files include: ["src/**/*.ts"], }

Threshold Failures

Issue: Coverage below threshold

Solution: Add missing tests or adjust thresholds

Lower threshold temporarily

thresholds: { lines: 70, // Reduced from 80 }

Or add tests to increase coverage

References

Best Practices Summary

  • Set Realistic Thresholds: 80% is good, 100% is often impractical

  • Exclude Non-Critical Code: Config files, generated code, tests

  • Focus on Critical Paths: Business logic, APIs, error handling

  • Test All Branches: Ensure conditional logic is tested

  • Track Over Time: Monitor coverage trends

  • Use HTML Reports: Visualize uncovered lines

  • Integrate with CI: Enforce thresholds in pipelines

  • Don't Game Coverage: Write meaningful tests, not just for coverage

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Research

coverage-analysis

No summary provided by upstream source.

Repository SourceNeeds Review
Research

bundle-analysis

No summary provided by upstream source.

Repository SourceNeeds Review
Research

coverage-analysis

No summary provided by upstream source.

Repository SourceNeeds Review
Research

coverage-analysis

No summary provided by upstream source.

Repository SourceNeeds Review