Skip to content

Commit

Permalink
Added preliminary test coverage support. Closes mochajs#5
Browse files Browse the repository at this point in the history
  • Loading branch information
tj committed Feb 24, 2012
1 parent aedaa73 commit d4666c2
Show file tree
Hide file tree
Showing 13 changed files with 599 additions and 3,528 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
coverage.html
lib-cov
.DS_Store
node_modules
*.sock
Expand Down
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ mocha.js: $(SRC) $(SUPPORT)

clean:
rm -f mocha.{js,css}
rm -fr lib-cov
rm -f coverage.html

test-cov: lib-cov
@COV=1 $(MAKE) test REPORTER=html-cov > coverage.html

lib-cov:
@rm -fr ./$@
@jscoverage lib $@

test: test-unit

Expand Down Expand Up @@ -96,4 +105,4 @@ tm:
mkdir -p $(TM_DEST)/$(TM_BUNDLE)
cp -fr editors/$(TM_BUNDLE) $(TM_DEST)/$(TM_BUNDLE)

.PHONY: watch test test-all test-bdd test-tdd test-qunit test-exports test-unit non-tty test-grep tm clean
.PHONY: test-cov watch test test-all test-bdd test-tdd test-qunit test-exports test-unit non-tty test-grep tm clean
4 changes: 3 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@

module.exports = require('./lib/mocha');
module.exports = process.env.COV
? require('./lib-cov/mocha')
: require('./lib/mocha');
44 changes: 44 additions & 0 deletions lib/reporters/html-cov.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

/**
* Module dependencies.
*/

var JSONCov = require('./json-cov')
, fs = require('fs');

/**
* Expose `HTMLCov`.
*/

exports = module.exports = HTMLCov;

/**
* Initialize a new `JsCoverage` reporter.
*
* @param {Runner} runner
* @api public
*/

function HTMLCov(runner) {
var jade = require('jade')
, file = __dirname + '/templates/coverage.jade'
, str = fs.readFileSync(file, 'utf8')
, fn = jade.compile(str, { filename: file })
, self = this;

JSONCov.call(this, runner, false);

runner.on('end', function(){
process.stdout.write(fn({
cov: self.cov
, coverageClass: coverageClass
}));
});
}

function coverageClass(n) {
if (n >= 75) return 'high';
if (n >= 50) return 'medium';
if (n >= 25) return 'low';
return 'terrible';
}
2 changes: 2 additions & 0 deletions lib/reporters/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ exports.List = require('./list');
exports.Spec = require('./spec');
exports.Progress = require('./progress');
exports.Landing = require('./landing');
exports.JSONCov = require('./json-cov');
exports.HTMLCov = require('./html-cov');
exports.JSONStream = require('./json-stream');
exports.XUnit = require('./xunit')
149 changes: 149 additions & 0 deletions lib/reporters/json-cov.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@

/**
* Module dependencies.
*/

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

/**
* Expose `JSONCov`.
*/

exports = module.exports = JSONCov;

/**
* Initialize a new `JsCoverage` reporter.
*
* @param {Runner} runner
* @param {Boolean} output
* @api public
*/

function JSONCov(runner, output) {
var self = this
, output = 1 == arguments.length ? true : output;

Base.call(this, runner);

var tests = []
, failures = []
, 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.
*
* @param {Object} cov
* @return {Object}
* @api private
*/

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

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

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.
*
* @param {String} filename name of the source file
* @param {Object} data jscoverage coverage data
* @return {Object}
* @api private
*/

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.
*
* @param {Object} test
* @return {Object}
* @api private
*/

function clean(test) {
return {
title: test.title
, fullTitle: test.fullTitle()
, duration: test.duration
}
}
50 changes: 50 additions & 0 deletions lib/reporters/templates/coverage.jade
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
!!! 5
html
head
title Coverage
include script.html
include style.html
body
#coverage
h1#overview Coverage
include menu

#stats(class=coverageClass(cov.coverage))
.percentage #{cov.coverage | 0}%
.sloc= cov.sloc
.hits= cov.hits
.misses= cov.misses

#files
for file in cov.files
.file
h2(id=file.filename)= file.filename
#stats(class=coverageClass(file.coverage))
.percentage #{file.coverage | 0}%
.sloc= file.sloc
.hits= file.hits
.misses= file.misses

table#source
thead
tr
th Line
th Hits
th Source
tbody
for line, number in file.source
if line.coverage > 0
tr.hit
td.line= number
td.hits= line.coverage
td.source= line.source
else if 0 === line.coverage
tr.miss
td.line= number
td.hits 0
td.source= line.source
else
tr
td.line= number
td.hits
td.source= line.source || ' '
13 changes: 13 additions & 0 deletions lib/reporters/templates/menu.jade
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#menu
li
a(href='#overview') overview
for file in cov.files
li
span.cov(class=coverageClass(file.coverage)) #{file.coverage | 0}
a(href='##{file.filename}')
segments = file.filename.split('/')
basename = segments.pop()
if segments.length
span.dirname= segments.join('/') + '/'
span.basename= basename
a#logo(href='http://visionmedia.github.com/mocha/') m
34 changes: 34 additions & 0 deletions lib/reporters/templates/script.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<script>

headings = [];

onload = function(){
headings = document.querySelectorAll('h2');
};

onscroll = function(e){
var heading = find(window.scrollY);
if (!heading) return;
var links = document.querySelectorAll('#menu a')
, link;

for (var i = 0, len = links.length; i < len; ++i) {
link = links[i];
link.className = link.getAttribute('href') == '#' + heading.id
? 'active'
: '';
}
};

function find(y) {
var i = headings.length
, heading;

while (i--) {
heading = headings[i];
if (y > heading.offsetTop) {
return heading;
}
}
}
</script>
Loading

0 comments on commit d4666c2

Please sign in to comment.