json-cov.js 2.76 KB
/**
 * Module dependencies.
 */

var Base = require('./base');

/**
 * Expose `JSONCov`.
 */

exports = module.exports = JSONCov;

/**
 * Initialize a new `JsCoverage` reporter.
 *
 * @api public
 * @param {Runner} runner
 * @param {boolean} output
 */
function JSONCov(runner, output) {
  Base.call(this, runner);

  output = arguments.length === 1 || output;
  var self = this;
  var tests = [];
  var failures = [];
  var passes = [];

  runner.on('test end', function(test) {
    tests.push(test);
  });

  runner.on('pass', function(test) {
    passes.push(test);
  });

  runner.on('fail', function(test) {
    failures.push(test);
  });

  runner.on('end', function() {
    var cov = global._$jscoverage || {};
    var result = self.cov = map(cov);
    result.stats = self.stats;
    result.tests = tests.map(clean);
    result.failures = failures.map(clean);
    result.passes = passes.map(clean);
    if (!output) {
      return;
    }
    process.stdout.write(JSON.stringify(result, null, 2));
  });
}

/**
 * Map jscoverage data to a JSON structure
 * suitable for reporting.
 *
 * @api private
 * @param {Object} cov
 * @return {Object}
 */

function map(cov) {
  var ret = {
    instrumentation: 'node-jscoverage',
    sloc: 0,
    hits: 0,
    misses: 0,
    coverage: 0,
    files: []
  };

  for (var filename in cov) {
    if (Object.prototype.hasOwnProperty.call(cov, filename)) {
      var data = coverage(filename, cov[filename]);
      ret.files.push(data);
      ret.hits += data.hits;
      ret.misses += data.misses;
      ret.sloc += data.sloc;
    }
  }

  ret.files.sort(function(a, b) {
    return a.filename.localeCompare(b.filename);
  });

  if (ret.sloc > 0) {
    ret.coverage = (ret.hits / ret.sloc) * 100;
  }

  return ret;
}

/**
 * Map jscoverage data for a single source file
 * to a JSON structure suitable for reporting.
 *
 * @api private
 * @param {string} filename name of the source file
 * @param {Object} data jscoverage coverage data
 * @return {Object}
 */
function coverage(filename, data) {
  var ret = {
    filename: filename,
    coverage: 0,
    hits: 0,
    misses: 0,
    sloc: 0,
    source: {}
  };

  data.source.forEach(function(line, num) {
    num++;

    if (data[num] === 0) {
      ret.misses++;
      ret.sloc++;
    } else if (data[num] !== undefined) {
      ret.hits++;
      ret.sloc++;
    }

    ret.source[num] = {
      source: line,
      coverage: data[num] === undefined ? '' : data[num]
    };
  });

  ret.coverage = ret.hits / ret.sloc * 100;

  return ret;
}

/**
 * Return a plain-object representation of `test`
 * free of cyclic properties etc.
 *
 * @api private
 * @param {Object} test
 * @return {Object}
 */
function clean(test) {
  return {
    duration: test.duration,
    fullTitle: test.fullTitle(),
    title: test.title
  };
}