Last active
August 29, 2015 14:12
-
-
Save geowarin/d86c7ff39ac43ee730a7 to your computer and use it in GitHub Desktop.
Blanket and mocha reporters to enable generation of a html report when doing in browser testing with phantomjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Author: Geoffroy Warin (http://geowarin.github.io) | |
* | |
* Reporter for blanket. Outputs the coverage html to the console. | |
* This is basically a copy of https://github.com/alex-seville/blanket/blob/master/src/qunit/reporter.js | |
* with a few adaptations | |
*/ | |
(function (){ | |
var reporter = function(coverage){ | |
var cssSytle = "#blanket-main {margin:2px;background:#EEE;color:#333;clear:both;font-family:'Helvetica Neue Light', 'HelveticaNeue-Light', 'Helvetica Neue', Calibri, Helvetica, Arial, sans-serif; font-size:17px;} #blanket-main a {color:#333;text-decoration:none;} #blanket-main a:hover {text-decoration:underline;} .blanket {margin:0;padding:5px;clear:both;border-bottom: 1px solid #FFFFFF;} .bl-error {color:red;}.bl-success {color:#5E7D00;} .bl-file{width:auto;} .bl-cl{float:left;} .blanket div.rs {margin-left:50px; width:150px; float:right} .bl-nb {padding-right:10px;} #blanket-main a.bl-logo {color: #EB1764;cursor: pointer;font-weight: bold;text-decoration: none} .bl-source{ overflow-x:scroll; background-color: #FFFFFF; border: 1px solid #CBCBCB; color: #363636; margin: 25px 20px; width: 80%;} .bl-source div{white-space: pre;font-family: monospace;} .bl-source > div > span:first-child{background-color: #EAEAEA;color: #949494;display: inline-block;padding: 0 10px;text-align: center;width: 30px;} .bl-source .miss{background-color:#e6c3c7} .bl-source span.branchWarning{color:#000;background-color:yellow;} .bl-source span.branchOkay{color:#000;background-color:transparent;}", | |
successRate = 60, | |
fileNumber = 0, | |
headerContent, | |
hasBranchTracking = Object.keys(coverage.files).some(function(elem){ | |
return typeof coverage.files[elem].branchData !== 'undefined'; | |
}), | |
bodyContent = "<div id='blanket-main'><div class='blanket bl-title'><div class='bl-cl bl-file'><a href='http://alex-seville.github.com/blanket/' target='_blank' class='bl-logo'>Blanket.js</a> results</div><div class='bl-cl rs'>Coverage (%)</div><div class='bl-cl rs'>Covered/Total Smts.</div>"+(hasBranchTracking ? "<div class='bl-cl rs'>Covered/Total Branches</div>":"")+"<div style='clear:both;'></div></div>", | |
fileTemplate = "<div class='blanket {{statusclass}}'><div class='bl-cl bl-file'><span class='bl-nb'>{{fileNumber}}.</span><a href='javascript:blanket_toggleSource(\"file-{{fileNumber}}\")'>{{file}}</a></div><div class='bl-cl rs'>{{percentage}} %</div><div class='bl-cl rs'>{{numberCovered}}/{{totalSmts}}</div>"+( hasBranchTracking ? "<div class='bl-cl rs'>{{passedBranches}}/{{totalBranches}}</div>" : "" )+"<div id='file-{{fileNumber}}' class='bl-source' style='display:none;'>{{source}}</div><div style='clear:both;'></div></div>"; | |
grandTotalTemplate = "<div class='blanket grand-total {{statusclass}}'><div class='bl-cl'>{{rowTitle}}</div><div class='bl-cl rs'>{{percentage}} %</div><div class='bl-cl rs'>{{numberCovered}}/{{totalSmts}}</div>"+( hasBranchTracking ? "<div class='bl-cl rs'>{{passedBranches}}/{{totalBranches}}</div>" : "" ) + "<div style='clear:both;'></div></div>"; | |
function blanket_toggleSource(id) { | |
var element = document.getElementById(id); | |
if(element.style.display === 'block') { | |
element.style.display = 'none'; | |
} else { | |
element.style.display = 'block'; | |
} | |
} | |
var percentage = function(number, total) { | |
return (Math.round(((number/total) * 100)*100)/100); | |
}; | |
var writeTag = function (type, str) { | |
console.log('<' + type + '>' + str + '</' + type + '>'); | |
}; | |
function escapeInvalidXmlChars(str) { | |
return str.replace(/\&/g, "&") | |
.replace(/</g, "<") | |
.replace(/\>/g, ">") | |
.replace(/\"/g, """) | |
.replace(/\'/g, "'"); | |
} | |
function isBranchFollowed(data,bool){ | |
var mode = bool ? 0 : 1; | |
if (typeof data === 'undefined' || | |
typeof data === null || | |
typeof data[mode] === 'undefined'){ | |
return false; | |
} | |
return data[mode].length > 0; | |
} | |
var branchStack = []; | |
function branchReport(colsIndex,src,cols,offset,lineNum){ | |
var newsrc=""; | |
var postfix=""; | |
if (branchStack.length > 0){ | |
newsrc += "<span class='" + (isBranchFollowed(branchStack[0][1],branchStack[0][1].consequent === branchStack[0][0]) ? 'branchOkay' : 'branchWarning') + "'>"; | |
if (branchStack[0][0].end.line === lineNum){ | |
newsrc += escapeInvalidXmlChars(src.slice(0,branchStack[0][0].end.column)) + "</span>"; | |
src = src.slice(branchStack[0][0].end.column); | |
branchStack.shift(); | |
if (branchStack.length > 0){ | |
newsrc += "<span class='" + (isBranchFollowed(branchStack[0][1],false) ? 'branchOkay' : 'branchWarning') + "'>"; | |
if (branchStack[0][0].end.line === lineNum){ | |
newsrc += escapeInvalidXmlChars(src.slice(0,branchStack[0][0].end.column)) + "</span>"; | |
src = src.slice(branchStack[0][0].end.column); | |
branchStack.shift(); | |
if (!cols){ | |
return {src: newsrc + escapeInvalidXmlChars(src) ,cols:cols}; | |
} | |
} | |
else if (!cols){ | |
return {src: newsrc + escapeInvalidXmlChars(src) + "</span>",cols:cols}; | |
} | |
else{ | |
postfix = "</span>"; | |
} | |
}else if (!cols){ | |
return {src: newsrc + escapeInvalidXmlChars(src) ,cols:cols}; | |
} | |
}else if(!cols){ | |
return {src: newsrc + escapeInvalidXmlChars(src) + "</span>",cols:cols}; | |
}else{ | |
postfix = "</span>"; | |
} | |
} | |
var thisline = cols[colsIndex]; | |
//consequent | |
var cons = thisline.consequent; | |
if (cons.start.line > lineNum){ | |
branchStack.unshift([thisline.alternate,thisline]); | |
branchStack.unshift([cons,thisline]); | |
src = escapeInvalidXmlChars(src); | |
}else{ | |
var style = "<span class='" + (isBranchFollowed(thisline,true) ? 'branchOkay' : 'branchWarning') + "'>"; | |
newsrc += escapeInvalidXmlChars(src.slice(0,cons.start.column-offset)) + style; | |
if (cols.length > colsIndex+1 && | |
cols[colsIndex+1].consequent.start.line === lineNum && | |
cols[colsIndex+1].consequent.start.column-offset < cols[colsIndex].consequent.end.column-offset) | |
{ | |
var res = branchReport(colsIndex+1,src.slice(cons.start.column-offset,cons.end.column-offset),cols,cons.start.column-offset,lineNum); | |
newsrc += res.src; | |
cols = res.cols; | |
cols[colsIndex+1] = cols[colsIndex+2]; | |
cols.length--; | |
}else{ | |
newsrc += escapeInvalidXmlChars(src.slice(cons.start.column-offset,cons.end.column-offset)); | |
} | |
newsrc += "</span>"; | |
var alt = thisline.alternate; | |
if (alt.start.line > lineNum){ | |
newsrc += escapeInvalidXmlChars(src.slice(cons.end.column-offset)); | |
branchStack.unshift([alt,thisline]); | |
}else{ | |
newsrc += escapeInvalidXmlChars(src.slice(cons.end.column-offset,alt.start.column-offset)); | |
style = "<span class='" + (isBranchFollowed(thisline,false) ? 'branchOkay' : 'branchWarning') + "'>"; | |
newsrc += style; | |
if (cols.length > colsIndex+1 && | |
cols[colsIndex+1].consequent.start.line === lineNum && | |
cols[colsIndex+1].consequent.start.column-offset < cols[colsIndex].alternate.end.column-offset) | |
{ | |
var res2 = branchReport(colsIndex+1,src.slice(alt.start.column-offset,alt.end.column-offset),cols,alt.start.column-offset,lineNum); | |
newsrc += res2.src; | |
cols = res2.cols; | |
cols[colsIndex+1] = cols[colsIndex+2]; | |
cols.length--; | |
}else{ | |
newsrc += escapeInvalidXmlChars(src.slice(alt.start.column-offset,alt.end.column-offset)); | |
} | |
newsrc += "</span>"; | |
newsrc += escapeInvalidXmlChars(src.slice(alt.end.column-offset)); | |
src = newsrc; | |
} | |
} | |
return {src:src+postfix, cols:cols}; | |
} | |
var isUndefined = function(item){ | |
return typeof item !== 'undefined'; | |
}; | |
var files = coverage.files; | |
var totals = { | |
totalSmts: 0, | |
numberOfFilesCovered: 0, | |
passedBranches: 0, | |
totalBranches: 0, | |
moduleTotalStatements : {}, | |
moduleTotalCoveredStatements : {}, | |
moduleTotalBranches : {}, | |
moduleTotalCoveredBranches : {} | |
}; | |
// check if a data-cover-modulepattern was provided for per-module coverage reporting | |
var modulePattern = _blanket.options("modulePattern"); | |
var modulePatternRegex = ( modulePattern ? new RegExp(modulePattern) : null ); | |
for(var file in files) | |
{ | |
fileNumber++; | |
var statsForFile = files[file], | |
totalSmts = 0, | |
numberOfFilesCovered = 0, | |
code = [], | |
i; | |
var end = []; | |
for(i = 0; i < statsForFile.source.length; i +=1){ | |
var src = statsForFile.source[i]; | |
if (branchStack.length > 0 || | |
typeof statsForFile.branchData !== 'undefined') | |
{ | |
if (typeof statsForFile.branchData[i+1] !== 'undefined') | |
{ | |
var cols = statsForFile.branchData[i+1].filter(isUndefined); | |
var colsIndex=0; | |
src = branchReport(colsIndex,src,cols,0,i+1).src; | |
}else if (branchStack.length){ | |
src = branchReport(0,src,null,0,i+1).src; | |
}else{ | |
src = escapeInvalidXmlChars(src); | |
} | |
}else{ | |
src = escapeInvalidXmlChars(src); | |
} | |
var lineClass=""; | |
if(statsForFile[i+1]) { | |
numberOfFilesCovered += 1; | |
totalSmts += 1; | |
lineClass = 'hit'; | |
}else{ | |
if(statsForFile[i+1] === 0){ | |
totalSmts++; | |
lineClass = 'miss'; | |
} | |
} | |
code[i + 1] = "<div class='"+lineClass+"'><span class=''>"+(i + 1)+"</span>"+src+"</div>"; | |
} | |
totals.totalSmts += totalSmts; | |
totals.numberOfFilesCovered += numberOfFilesCovered; | |
var totalBranches=0; | |
var passedBranches=0; | |
if (typeof statsForFile.branchData !== 'undefined'){ | |
for(var j=0;j<statsForFile.branchData.length;j++){ | |
if (typeof statsForFile.branchData[j] !== 'undefined'){ | |
for(var k=0;k<statsForFile.branchData[j].length;k++){ | |
if (typeof statsForFile.branchData[j][k] !== 'undefined'){ | |
totalBranches++; | |
if (typeof statsForFile.branchData[j][k][0] !== 'undefined' && | |
statsForFile.branchData[j][k][0].length > 0 && | |
typeof statsForFile.branchData[j][k][1] !== 'undefined' && | |
statsForFile.branchData[j][k][1].length > 0){ | |
passedBranches++; | |
} | |
} | |
} | |
} | |
} | |
} | |
totals.passedBranches += passedBranches; | |
totals.totalBranches += totalBranches; | |
// if "data-cover-modulepattern" was provided, | |
// track totals per module name as well as globally | |
if (modulePatternRegex) { | |
var moduleName = file.match(modulePatternRegex)[1]; | |
if(!totals.moduleTotalStatements.hasOwnProperty(moduleName)) { | |
totals.moduleTotalStatements[moduleName] = 0; | |
totals.moduleTotalCoveredStatements[moduleName] = 0; | |
} | |
totals.moduleTotalStatements[moduleName] += totalSmts; | |
totals.moduleTotalCoveredStatements[moduleName] += numberOfFilesCovered; | |
if(!totals.moduleTotalBranches.hasOwnProperty(moduleName)) { | |
totals.moduleTotalBranches[moduleName] = 0; | |
totals.moduleTotalCoveredBranches[moduleName] = 0; | |
} | |
totals.moduleTotalBranches[moduleName] += totalBranches; | |
totals.moduleTotalCoveredBranches[moduleName] += passedBranches; | |
} | |
var result = percentage(numberOfFilesCovered, totalSmts); | |
var output = fileTemplate.replace("{{file}}", file) | |
.replace("{{percentage}}",result) | |
.replace("{{numberCovered}}", numberOfFilesCovered) | |
.replace(/\{\{fileNumber\}\}/g, fileNumber) | |
.replace("{{totalSmts}}", totalSmts) | |
.replace("{{totalBranches}}", totalBranches) | |
.replace("{{passedBranches}}", passedBranches) | |
.replace("{{source}}", code.join(" ")); | |
if(result < successRate) | |
{ | |
output = output.replace("{{statusclass}}", "bl-error"); | |
} else { | |
output = output.replace("{{statusclass}}", "bl-success"); | |
} | |
bodyContent += output; | |
} | |
// create temporary function for use by the global totals reporter, | |
// as well as the per-module totals reporter | |
var createAggregateTotal = function(numSt, numCov, numBranch, numCovBr, moduleName) { | |
var totalPercent = percentage(numCov, numSt); | |
var statusClass = totalPercent < successRate ? "bl-error" : "bl-success"; | |
var rowTitle = ( moduleName ? "Total for module: " + moduleName : "Global total" ); | |
var totalsOutput = grandTotalTemplate.replace("{{rowTitle}}", rowTitle) | |
.replace("{{percentage}}", totalPercent) | |
.replace("{{numberCovered}}", numCov) | |
.replace("{{totalSmts}}", numSt) | |
.replace("{{passedBranches}}", numCovBr) | |
.replace("{{totalBranches}}", numBranch) | |
.replace("{{statusclass}}", statusClass); | |
bodyContent += totalsOutput; | |
}; | |
// if "data-cover-modulepattern" was provided, | |
// output the per-module totals alongside the global totals | |
if (modulePatternRegex) { | |
for (var thisModuleName in totals.moduleTotalStatements) { | |
if (totals.moduleTotalStatements.hasOwnProperty(thisModuleName)) { | |
var moduleTotalSt = totals.moduleTotalStatements[thisModuleName]; | |
var moduleTotalCovSt = totals.moduleTotalCoveredStatements[thisModuleName]; | |
var moduleTotalBr = totals.moduleTotalBranches[thisModuleName]; | |
var moduleTotalCovBr = totals.moduleTotalCoveredBranches[thisModuleName]; | |
createAggregateTotal(moduleTotalSt, moduleTotalCovSt, moduleTotalBr, moduleTotalCovBr, thisModuleName); | |
} | |
} | |
} | |
createAggregateTotal(totals.totalSmts, totals.numberOfFilesCovered, totals.totalBranches, totals.passedBranches, null); | |
bodyContent += "</div>"; //closing main | |
console.log('<!DOCTYPE html>'); | |
console.log('<html>'); | |
console.log('<head>'); | |
writeTag('style', cssSytle); | |
var scriptText = blanket_toggleSource.toString().replace('function ' + blanket_toggleSource.name, 'function blanket_toggleSource'); | |
writeTag('script', scriptText); | |
console.log('</head>'); | |
console.log('<body>'); | |
writeTag('div', bodyContent); | |
console.log('</body>'); | |
console.log('</html>'); | |
}; | |
blanket.customReporter= function(coverageData){ | |
reporter(coverageData); | |
}; | |
})(); | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var gulp = require('gulp'), | |
inject = require('gulp-inject'), | |
bowerFiles = require('main-bower-files'), | |
mochaPhantomJS = require('gulp-mocha-phantomjs'), | |
rename = require('gulp-rename'), | |
preprocess = require('gulp-preprocess'), | |
argv = require('yargs').argv, | |
gutil = require('gulp-util'); | |
var coverage = !!argv.coverage; // true if --coverage flag is used | |
/** | |
* Process test index to interpret the conditions and move it | |
* to the test/ directory | |
*/ | |
gulp.task('processIndex', function () { | |
return gulp.src('build/templates/testIndex.html') | |
.pipe(preprocess({context: {coverage: coverage}})) | |
.pipe(rename('index.html')) | |
.pipe(gulp.dest('test')); | |
}); | |
/** | |
* Inject resources into test index. | |
* Please note the file should be in its final position for relative paths to work. | |
*/ | |
gulp.task('injectIntoIndex', ['processIndex'], function () { | |
return gulp.src('test/index.html') | |
.pipe(inject( | |
gulp.src(bowerFiles({includeDev: true}), {read: false}), | |
{name: 'bower', relative: true} | |
)) | |
.pipe(inject( | |
gulp.src(['src/js/**/*.js'], {read: false}), | |
{name: 'all', relative: true} | |
)) | |
.pipe(inject( | |
gulp.src(['test/suites/**/*.js'], {read: false}), | |
{name: 'suites', relative: true} | |
)) | |
.pipe(gulp.dest('test')); | |
}); | |
/** | |
* Launch mocha phantomjs to run the tests. | |
* By default, all output is redirected to the console and the spec reporter is used. | |
* In coverage mode, output will be redirected to a file and a custom mocha reporter will be used. | |
*/ | |
gulp.task('test', ['injectIntoIndex'], function () { | |
var options = !coverage ? {} : { | |
reporter: 'build/report/mochaBlanketAdapter.js', | |
dump: 'test/coverage.html' | |
}; | |
return gulp.src('test/index.html', {read: false}) | |
.on('data', processWinPath) | |
.pipe(mochaPhantomJS(options)) | |
.on('end', function() { | |
if (coverage) | |
gutil.log('Generated coverage report to', gutil.colors.cyan(options.dump)); | |
}); | |
}); | |
var processWinPath = function (file) { | |
var path = require('path'); | |
if (process.platform === 'win32') { | |
file.path = path.relative('.', file.path); | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var Base = require('./base') | |
, cursor = Base.cursor | |
, color = Base.color; | |
exports = module.exports = BlanketReporter; | |
/** | |
* Author: Geoffroy Warin (http://geowarin.github.io) | |
* | |
* Adapter reporter to wire blanket from mocha. | |
* Essentially taken from https://github.com/alex-seville/blanket/blob/master/src/adapters/mocha-blanket.js | |
*/ | |
function BlanketReporter(runner) { | |
Base.call(this, runner); | |
runner.on('start', function() { | |
blanket.setupCoverage(); | |
}); | |
runner.on('end', function() { | |
blanket.onTestsDone(); | |
}); | |
runner.on('suite', function() { | |
blanket.onModuleStart(); | |
}); | |
runner.on('test', function() { | |
blanket.onTestStart(); | |
}); | |
runner.on('test end', function(test) { | |
blanket.onTestDone(test.parent.tests.length, test.state === 'passed'); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment