or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

browser-utilities.mdconfiguration.mdindex.mdinstrumentation.mdpattern-matching.mdreporting.mdtest-integration.md
tile.json

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.