CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-blanket

Seamless JavaScript code coverage library for both browser and Node.js environments

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

reporting.mddocs/

Reporting

Coverage report generation, custom reporter support, and output formatting for coverage statistics.

Capabilities

Report Generation

Generate coverage reports using the configured reporter.

/**
 * Generate coverage report using configured reporter
 * Called automatically by onTestsDone() or manually for custom reporting
 * @param coverage_data - Coverage statistics and file data
 */
function report(coverage_data);

/**
 * Get the coverage variable name used for tracking
 * @returns Coverage variable name ("window._$blanket" in browser, "_$jscoverage" in Node.js, or custom)
 */
function getCovVar();

Usage Examples:

// Automatic reporting (called by onTestsDone)
blanket.onTestsDone(); // Triggers blanket.report(coverageData)

// Manual reporting
const coverageData = window._$blanket || global._$jscoverage;
blanket.report({
  files: coverageData,
  stats: blanket.coverageInfo.stats
});

// Get coverage variable name
const covVar = blanket.getCovVar(); // "_$jscoverage" or custom name
const rawCoverage = global[covVar];

Custom Reporters

Set custom reporter functions or paths for specialized output formats.

/**
 * Custom reporter property that can be assigned a reporting function
 * @type {Function}
 */
customReporter;

/**
 * Default reporter property containing built-in HTML reporter
 * @type {Function} 
 */
defaultReporter;

Built-in Reporters

LCOV Reporter

Generates LCOV format output for integration with coverage tools.

Location: src/reporters/lcov_reporter.js

// LCOV reporter usage
blanket.options("reporter", "./reporters/lcov_reporter.js");

// Or use directly
blanket.customReporter = function(coverageData, options) {
  var toHTML = options && options.toHTML !== undefined ? options.toHTML : true;
  
  for (var filename in coverageData.files) {
    var data = coverageData.files[filename];
    var lcovStr = 'SF:' + filename + '\n';
    
    data.source.forEach(function(line, num) {
      num++; // LCOV uses 1-based line numbers
      if (data[num] !== undefined) {
        lcovStr += 'DA:' + num + ',' + data[num] + '\n';
      }
    });
    
    lcovStr += 'end_of_record\n';
    
    if (toHTML) {
      // Display in browser
      var div = document.createElement('div');
      div.className = "blanket_lcov_reporter";
      div.innerText = lcovStr;
      document.body.appendChild(div);
    } else {
      // Store for programmatic access
      window._$blanket_LCOV = (window._$blanket_LCOV || '') + lcovStr;
    }
  }
};

LCOV Output Format:

SF:src/app.js
DA:1,5
DA:2,3
DA:3,0
DA:4,2
end_of_record
SF:src/utils.js
DA:1,1
DA:2,1
end_of_record

Simple JSON Reporter

Outputs coverage data in JSON format.

Location: src/reporters/simple_json_reporter.js

// JSON reporter outputs coverage data as formatted JSON
blanket.customReporter = function(coverageData) {
  console.log(JSON.stringify(coverageData, null, 2));
};

Coverage Data Structure

File Coverage Data

Each instrumented file has coverage data in this structure:

interface FileCoverage {
  source: string[];           // Original source code lines
  [lineNumber: number]: number; // Line execution counts (1-based indexing)
  branchData?: {              // Branch coverage data (when branchTracking enabled)
    [lineNumber: number]: {
      [branchIndex: number]: {
        consequent: BranchInfo;
        alternate: BranchInfo;
      };
    };
  };
}

interface BranchInfo {
  start: { line: number; column: number; };
  end: { line: number; column: number; };
}

Coverage Statistics

Test execution statistics collected during test runs:

interface CoverageStats {
  suites: number;      // Number of test suites executed
  tests: number;       // Total number of tests run
  passes: number;      // Number of tests that passed
  pending: number;     // Number of tests currently pending
  failures: number;    // Number of tests that failed
  start: Date;         // Test execution start timestamp
  end: Date;          // Test execution end timestamp
}

Complete Coverage Data

The full coverage data object passed to reporters:

interface CoverageData {
  files: {
    [filename: string]: FileCoverage;
  };
  stats: CoverageStats;
}

Custom Reporter Development

Basic Custom Reporter

function myCustomReporter(coverageData, options) {
  console.log("=== Coverage Report ===");
  console.log("Tests run:", coverageData.stats.tests);
  console.log("Pass rate:", 
    (coverageData.stats.passes / coverageData.stats.tests * 100).toFixed(2) + "%");
  
  console.log("\n=== File Coverage ===");
  for (const filename in coverageData.files) {
    const fileData = coverageData.files[filename];
    const totalLines = fileData.source.length;
    let coveredLines = 0;
    
    for (let i = 1; i <= totalLines; i++) {
      if (fileData[i] > 0) coveredLines++;
    }
    
    const coverage = (coveredLines / totalLines * 100).toFixed(2);
    console.log(`${filename}: ${coverage}% (${coveredLines}/${totalLines} lines)`);
  }
}

// Use the custom reporter
blanket.options("reporter", myCustomReporter);

Advanced Custom Reporter with Branch Coverage

function advancedReporter(coverageData, options) {
  const results = {
    summary: {
      files: 0,
      lines: { total: 0, covered: 0, percentage: 0 },
      branches: { total: 0, covered: 0, percentage: 0 }
    },
    files: {}
  };
  
  for (const filename in coverageData.files) {
    const fileData = coverageData.files[filename];
    results.summary.files++;
    
    // Line coverage analysis
    const totalLines = fileData.source.length;
    let coveredLines = 0;
    
    for (let i = 1; i <= totalLines; i++) {
      if (fileData[i] !== undefined) {
        results.summary.lines.total++;
        if (fileData[i] > 0) {
          coveredLines++;
          results.summary.lines.covered++;
        }
      }
    }
    
    // Branch coverage analysis
    let totalBranches = 0;
    let coveredBranches = 0;
    
    if (fileData.branchData) {
      for (const lineNum in fileData.branchData) {
        for (const branchIndex in fileData.branchData[lineNum]) {
          const branch = fileData.branchData[lineNum][branchIndex];
          totalBranches += 2; // consequent + alternate
          
          // Check if branches were executed
          if (branch.consequent && branch.consequent.length > 0) coveredBranches++;
          if (branch.alternate && branch.alternate.length > 0) coveredBranches++;
        }
      }
    }
    
    results.summary.branches.total += totalBranches;
    results.summary.branches.covered += coveredBranches;
    
    // Store file-specific results
    results.files[filename] = {
      lines: {
        total: totalLines,
        covered: coveredLines,
        percentage: (coveredLines / totalLines * 100).toFixed(2)
      },
      branches: {
        total: totalBranches,
        covered: coveredBranches,
        percentage: totalBranches > 0 ? (coveredBranches / totalBranches * 100).toFixed(2) : "N/A"
      }
    };
  }
  
  // Calculate summary percentages
  results.summary.lines.percentage = 
    (results.summary.lines.covered / results.summary.lines.total * 100).toFixed(2);
  results.summary.branches.percentage = 
    results.summary.branches.total > 0 
      ? (results.summary.branches.covered / results.summary.branches.total * 100).toFixed(2)
      : "N/A";
  
  // Output results
  console.log("Coverage Summary:");
  console.log(`Files: ${results.summary.files}`);
  console.log(`Lines: ${results.summary.lines.covered}/${results.summary.lines.total} (${results.summary.lines.percentage}%)`);
  console.log(`Branches: ${results.summary.branches.covered}/${results.summary.branches.total} (${results.summary.branches.percentage}%)`);
  
  return results;
}

blanket.options("reporter", advancedReporter);

Reporter Options

Configuration

Reporters can receive options through the reporter_options configuration:

blanket.options("reporter_options", {
  shortnames: true,        // Use short filenames
  relativepath: true,      // Use relative paths
  basepath: "/project",    // Base path to remove from filenames
  threshold: 80,           // Minimum coverage threshold
  outputFile: "coverage.json" // Output file path
});

Path Processing Options

  • shortnames: Remove directory path, show only filename
  • relativepath: Show paths relative to current working directory
  • basepath: Remove specified base path from all filenames

Examples:

// Original filename: "/project/src/utils/helper.js"

// With shortnames: true
// Result: "helper.js"

// With relativepath: true (cwd = "/project")  
// Result: "src/utils/helper.js"

// With basepath: "/project/src"
// Result: "utils/helper.js"

Integration with CI/CD

Node.js Reporter Integration

// Custom reporter for CI environments
function ciReporter(coverageData) {
  const summary = calculateCoverage(coverageData);
  
  // Exit with error code if coverage below threshold
  const threshold = blanket.options("reporter_options").threshold || 80;
  if (summary.percentage < threshold) {
    console.error(`Coverage ${summary.percentage}% below threshold ${threshold}%`);
    process.exit(1);
  }
  
  console.log(`Coverage: ${summary.percentage}% - PASSED`);
}

blanket.options("reporter", ciReporter);
blanket.options("reporter_options", { threshold: 85 });

File Output

// Reporter that writes to file
function fileReporter(coverageData, options) {
  const fs = require('fs');
  const outputFile = options.outputFile || 'coverage.json';
  
  const report = {
    timestamp: new Date().toISOString(),
    coverage: coverageData
  };
  
  fs.writeFileSync(outputFile, JSON.stringify(report, null, 2));
  console.log(`Coverage report written to ${outputFile}`);
}

blanket.options("reporter", fileReporter);
blanket.options("reporter_options", { outputFile: "build/coverage.json" });

Environment-Specific Behavior

Browser Environment

In browsers, blanket.report() is called automatically by onTestsDone():

// Browser reporting flow
function onTestsDone() {
  coverageInfo.stats.end = new Date();
  this.report(coverageInfo); // Calls configured reporter
}

Node.js Environment

In Node.js, the configured reporter function is called directly:

// Node.js reporting flow
function onTestsDone() {
  coverageInfo.stats.end = new Date();
  
  // Clean up branch tracking if disabled
  if (!blanket.options("branchTracking")) {
    delete global[blanket.getCovVar()].branchFcn;
  }
  
  // Call reporter function
  blanket.options("reporter").call(blanket, coverageInfo);
}

This allows for different reporting strategies in each environment while maintaining a consistent API.

docs

browser-utilities.md

configuration.md

index.md

instrumentation.md

pattern-matching.md

reporting.md

test-integration.md

tile.json