blanket
Version:
seamless js code coverage
335 lines (293 loc) • 15.6 kB
JavaScript
blanket.defaultReporter = 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 .hit{background-color:#c3e6c7} .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,
head = document.head,
fileNumber = 0,
body = document.body,
headerContent,
hasBranchTracking = Object.keys(coverage.files).some(function(elem){
return typeof coverage.files[elem].branchData !== 'undefined';
}),
bodyContent = "<table id='blanket-main'><tr><th class='blanket bl-title'><th class='bl-cl bl-file'><a href='http://alex-seville.github.com/blanket/' target='_blank' class='bl-logo'>Blanket.js</a> results</th><th class='bl-cl rs'>Coverage (%)</th><th class='bl-cl rs'>Covered/Total Smts.</th>"+(hasBranchTracking ? "<th class='bl-cl rs'>Covered/Total Branches</th>":"")+"<th style='clear:both;'></th></tr></table>",
fileTemplate = "<tr><td class='blanket {{statusclass}}'><td class='bl-cl bl-file'><span class='bl-nb'>{{fileNumber}}.</span><a href='javascript:blanket_toggleSource(\"file-{{fileNumber}}\")'>{{file}}</a></td><td class='bl-cl rs'>{{percentage}} %</td><td class='bl-cl rs'>{{numberCovered}}/{{totalSmts}}</td>"+( hasBranchTracking ? "<td class='bl-cl rs'>{{passedBranches}}/{{totalBranches}}</td>" : "" )+"<td id='file-{{fileNumber}}' class='bl-source' style='display:none;'>{{source}}</td><td style='clear:both;'></td></td></tr>";
grandTotalTemplate = "<tr><td class='blanket grand-total {{statusclass}}'><td class='bl-cl'>{{rowTitle}}</td><td class='bl-cl rs'>{{percentage}} %</td><td class='bl-cl rs'>{{numberCovered}}/{{totalSmts}}</td>"+( hasBranchTracking ? "<td class='bl-cl rs'>{{passedBranches}}/{{totalBranches}}</td>" : "" ) + "<td style='clear:both;'></td></td></tr>";
function blanket_toggleSource(id) {
var element = document.getElementById(id);
if(element.style.display === 'block') {
element.style.display = 'none';
} else {
element.style.display = 'block';
}
}
var script = document.createElement("script");
script.type = "text/javascript";
script.text = blanket_toggleSource.toString().replace('function ' + blanket_toggleSource.name, 'function blanket_toggleSource');
body.appendChild(script);
var percentage = function(number, total) {
return (Math.round(((number/total) * 100)*100)/100);
};
var appendTag = function (type, el, str) {
var dom = document.createElement(type);
dom.innerHTML = str;
el.appendChild(dom);
};
function escapeInvalidXmlChars(str) {
return str.replace(/\&/g, "&")
.replace(/</g, "<")
.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)
{
if (!files.hasOwnProperty(file)) {
continue;
}
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
appendTag('style', head, cssSytle);
//appendStyle(body, headerContent);
if (document.getElementById("blanket-main")){
document.getElementById("blanket-main").innerHTML=
bodyContent.slice(23,-6);
}else{
appendTag('div', body, bodyContent);
}
//appendHtml(body, '</div>');
};