This commit is contained in:
Stephen Franceschelli 2019-07-30 13:41:05 -04:00
parent 596a6da241
commit c1a589c5b6
7078 changed files with 1882834 additions and 319 deletions

275
node_modules/istanbul-reports/lib/html/annotator.js generated vendored Normal file
View file

@ -0,0 +1,275 @@
/*
Copyright 2012-2015, Yahoo Inc.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
'use strict';
const InsertionText = require('./insertion-text');
const lt = '\u0001';
const gt = '\u0002';
const RE_LT = /</g;
const RE_GT = />/g;
const RE_AMP = /&/g;
// eslint-disable-next-line
var RE_lt = /\u0001/g;
// eslint-disable-next-line
var RE_gt = /\u0002/g;
function title(str) {
return ' title="' + str + '" ';
}
function customEscape(text) {
text = String(text);
return text
.replace(RE_AMP, '&amp;')
.replace(RE_LT, '&lt;')
.replace(RE_GT, '&gt;')
.replace(RE_lt, '<')
.replace(RE_gt, '>');
}
function annotateLines(fileCoverage, structuredText) {
const lineStats = fileCoverage.getLineCoverage();
if (!lineStats) {
return;
}
Object.keys(lineStats).forEach(lineNumber => {
const count = lineStats[lineNumber];
if (structuredText[lineNumber]) {
structuredText[lineNumber].covered = count > 0 ? 'yes' : 'no';
structuredText[lineNumber].hits = count;
}
});
}
function annotateStatements(fileCoverage, structuredText) {
const statementStats = fileCoverage.s;
const statementMeta = fileCoverage.statementMap;
Object.keys(statementStats).forEach(stName => {
const count = statementStats[stName];
const meta = statementMeta[stName];
const type = count > 0 ? 'yes' : 'no';
const startCol = meta.start.column;
let endCol = meta.end.column + 1;
const startLine = meta.start.line;
const endLine = meta.end.line;
const openSpan =
lt +
'span class="' +
(meta.skip ? 'cstat-skip' : 'cstat-no') +
'"' +
title('statement not covered') +
gt;
const closeSpan = lt + '/span' + gt;
let text;
if (type === 'no' && structuredText[startLine]) {
if (endLine !== startLine) {
endCol = structuredText[startLine].text.originalLength();
}
text = structuredText[startLine].text;
text.wrap(
startCol,
openSpan,
startCol < endCol ? endCol : text.originalLength(),
closeSpan
);
}
});
}
function annotateFunctions(fileCoverage, structuredText) {
const fnStats = fileCoverage.f;
const fnMeta = fileCoverage.fnMap;
if (!fnStats) {
return;
}
Object.keys(fnStats).forEach(fName => {
const count = fnStats[fName];
const meta = fnMeta[fName];
const type = count > 0 ? 'yes' : 'no';
const startCol = meta.decl.start.column;
let endCol = meta.decl.end.column + 1;
const startLine = meta.decl.start.line;
const endLine = meta.decl.end.line;
const openSpan =
lt +
'span class="' +
(meta.skip ? 'fstat-skip' : 'fstat-no') +
'"' +
title('function not covered') +
gt;
const closeSpan = lt + '/span' + gt;
let text;
if (type === 'no' && structuredText[startLine]) {
if (endLine !== startLine) {
endCol = structuredText[startLine].text.originalLength();
}
text = structuredText[startLine].text;
text.wrap(
startCol,
openSpan,
startCol < endCol ? endCol : text.originalLength(),
closeSpan
);
}
});
}
function annotateBranches(fileCoverage, structuredText) {
const branchStats = fileCoverage.b;
const branchMeta = fileCoverage.branchMap;
if (!branchStats) {
return;
}
Object.keys(branchStats).forEach(branchName => {
const branchArray = branchStats[branchName];
const sumCount = branchArray.reduce((p, n) => p + n, 0);
const metaArray = branchMeta[branchName].locations;
let i;
let count;
let meta;
let startCol;
let endCol;
let startLine;
let endLine;
let openSpan;
let closeSpan;
let text;
// only highlight if partial branches are missing or if there is a
// single uncovered branch.
if (sumCount > 0 || (sumCount === 0 && branchArray.length === 1)) {
for (
i = 0;
i < branchArray.length && i < metaArray.length;
i += 1
) {
count = branchArray[i];
meta = metaArray[i];
startCol = meta.start.column;
endCol = meta.end.column + 1;
startLine = meta.start.line;
endLine = meta.end.line;
openSpan =
lt +
'span class="branch-' +
i +
' ' +
(meta.skip ? 'cbranch-skip' : 'cbranch-no') +
'"' +
title('branch not covered') +
gt;
closeSpan = lt + '/span' + gt;
if (count === 0 && structuredText[startLine]) {
//skip branches taken
if (endLine !== startLine) {
endCol = structuredText[
startLine
].text.originalLength();
}
text = structuredText[startLine].text;
if (branchMeta[branchName].type === 'if') {
// 'if' is a special case
// since the else branch might not be visible, being non-existent
text.insertAt(
startCol,
lt +
'span class="' +
(meta.skip
? 'skip-if-branch'
: 'missing-if-branch') +
'"' +
title(
(i === 0 ? 'if' : 'else') +
' path not taken'
) +
gt +
(i === 0 ? 'I' : 'E') +
lt +
'/span' +
gt,
true,
false
);
} else {
text.wrap(
startCol,
openSpan,
startCol < endCol ? endCol : text.originalLength(),
closeSpan
);
}
}
}
}
});
}
function annotateSourceCode(fileCoverage, sourceStore) {
let codeArray;
let lineCoverageArray;
try {
const sourceText = sourceStore.getSource(fileCoverage.path);
const code = sourceText.split(/(?:\r?\n)|\r/);
let count = 0;
const structured = code.map(str => {
count += 1;
return {
line: count,
covered: 'neutral',
hits: 0,
text: new InsertionText(str, true)
};
});
structured.unshift({
line: 0,
covered: null,
text: new InsertionText('')
});
annotateLines(fileCoverage, structured);
//note: order is important, since statements typically result in spanning the whole line and doing branches late
//causes mismatched tags
annotateBranches(fileCoverage, structured);
annotateFunctions(fileCoverage, structured);
annotateStatements(fileCoverage, structured);
structured.shift();
codeArray = structured.map(
item => customEscape(item.text.toString()) || '&nbsp;'
);
lineCoverageArray = structured.map(item => ({
covered: item.covered,
hits: item.hits > 0 ? item.hits + 'x' : '&nbsp;'
}));
return {
annotatedCode: codeArray,
lineCoverage: lineCoverageArray,
maxLines: structured.length
};
} catch (ex) {
codeArray = [ex.message];
lineCoverageArray = [{ covered: 'no', hits: 0 }];
String(ex.stack || '')
.split(/\r?\n/)
.forEach(line => {
codeArray.push(line);
lineCoverageArray.push({ covered: 'no', hits: 0 });
});
return {
annotatedCode: codeArray,
lineCoverage: lineCoverageArray,
maxLines: codeArray.length
};
}
}
module.exports = {
annotateSourceCode
};

224
node_modules/istanbul-reports/lib/html/assets/base.css generated vendored Normal file
View file

@ -0,0 +1,224 @@
body, html {
margin:0; padding: 0;
height: 100%;
}
body {
font-family: Helvetica Neue, Helvetica, Arial;
font-size: 14px;
color:#333;
}
.small { font-size: 12px; }
*, *:after, *:before {
-webkit-box-sizing:border-box;
-moz-box-sizing:border-box;
box-sizing:border-box;
}
h1 { font-size: 20px; margin: 0;}
h2 { font-size: 14px; }
pre {
font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
margin: 0;
padding: 0;
-moz-tab-size: 2;
-o-tab-size: 2;
tab-size: 2;
}
a { color:#0074D9; text-decoration:none; }
a:hover { text-decoration:underline; }
.strong { font-weight: bold; }
.space-top1 { padding: 10px 0 0 0; }
.pad2y { padding: 20px 0; }
.pad1y { padding: 10px 0; }
.pad2x { padding: 0 20px; }
.pad2 { padding: 20px; }
.pad1 { padding: 10px; }
.space-left2 { padding-left:55px; }
.space-right2 { padding-right:20px; }
.center { text-align:center; }
.clearfix { display:block; }
.clearfix:after {
content:'';
display:block;
height:0;
clear:both;
visibility:hidden;
}
.fl { float: left; }
@media only screen and (max-width:640px) {
.col3 { width:100%; max-width:100%; }
.hide-mobile { display:none!important; }
}
.quiet {
color: #7f7f7f;
color: rgba(0,0,0,0.5);
}
.quiet a { opacity: 0.7; }
.fraction {
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
font-size: 10px;
color: #555;
background: #E8E8E8;
padding: 4px 5px;
border-radius: 3px;
vertical-align: middle;
}
div.path a:link, div.path a:visited { color: #333; }
table.coverage {
border-collapse: collapse;
margin: 10px 0 0 0;
padding: 0;
}
table.coverage td {
margin: 0;
padding: 0;
vertical-align: top;
}
table.coverage td.line-count {
text-align: right;
padding: 0 5px 0 20px;
}
table.coverage td.line-coverage {
text-align: right;
padding-right: 10px;
min-width:20px;
}
table.coverage td span.cline-any {
display: inline-block;
padding: 0 5px;
width: 100%;
}
.missing-if-branch {
display: inline-block;
margin-right: 5px;
border-radius: 3px;
position: relative;
padding: 0 4px;
background: #333;
color: yellow;
}
.skip-if-branch {
display: none;
margin-right: 10px;
position: relative;
padding: 0 4px;
background: #ccc;
color: white;
}
.missing-if-branch .typ, .skip-if-branch .typ {
color: inherit !important;
}
.coverage-summary {
border-collapse: collapse;
width: 100%;
}
.coverage-summary tr { border-bottom: 1px solid #bbb; }
.keyline-all { border: 1px solid #ddd; }
.coverage-summary td, .coverage-summary th { padding: 10px; }
.coverage-summary tbody { border: 1px solid #bbb; }
.coverage-summary td { border-right: 1px solid #bbb; }
.coverage-summary td:last-child { border-right: none; }
.coverage-summary th {
text-align: left;
font-weight: normal;
white-space: nowrap;
}
.coverage-summary th.file { border-right: none !important; }
.coverage-summary th.pct { }
.coverage-summary th.pic,
.coverage-summary th.abs,
.coverage-summary td.pct,
.coverage-summary td.abs { text-align: right; }
.coverage-summary td.file { white-space: nowrap; }
.coverage-summary td.pic { min-width: 120px !important; }
.coverage-summary tfoot td { }
.coverage-summary .sorter {
height: 10px;
width: 7px;
display: inline-block;
margin-left: 0.5em;
background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
}
.coverage-summary .sorted .sorter {
background-position: 0 -20px;
}
.coverage-summary .sorted-desc .sorter {
background-position: 0 -10px;
}
.status-line { height: 10px; }
/* yellow */
.cbranch-no { background: yellow !important; color: #111; }
/* dark red */
.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
.low .chart { border:1px solid #C21F39 }
.highlighted,
.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
background: #C21F39 !important;
}
/* medium red */
.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
/* light red */
.low, .cline-no { background:#FCE1E5 }
/* light green */
.high, .cline-yes { background:rgb(230,245,208) }
/* medium green */
.cstat-yes { background:rgb(161,215,106) }
/* dark green */
.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
.high .chart { border:1px solid rgb(77,146,33) }
/* dark yellow (gold) */
.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
.medium .chart { border:1px solid #f9cd0b; }
/* light yellow */
.medium { background: #fff4c2; }
.cstat-skip { background: #ddd; color: #111; }
.fstat-skip { background: #ddd; color: #111 !important; }
.cbranch-skip { background: #ddd !important; color: #111; }
span.cline-neutral { background: #eaeaea; }
.coverage-summary td.empty {
opacity: .5;
padding-top: 4px;
padding-bottom: 4px;
line-height: 1;
color: #888;
}
.cover-fill, .cover-empty {
display:inline-block;
height: 12px;
}
.chart {
line-height: 0;
}
.cover-empty {
background: white;
}
.cover-full {
border-right: none !important;
}
pre.prettyprint {
border: none !important;
padding: 0 !important;
margin: 0 !important;
}
.com { color: #999 !important; }
.ignore-none { color: #999; font-weight: normal; }
.wrapper {
min-height: 100%;
height: auto !important;
height: 100%;
margin: 0 auto -48px;
}
.footer, .push {
height: 48px;
}

View file

@ -0,0 +1,78 @@
var jumpToCode = (function init() {
// Classes of code we would like to highlight in the file view
var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
// Elements to highlight in the file listing view
var fileListingElements = ['td.pct.low'];
// We don't want to select elements that are direct descendants of another match
var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
// Selecter that finds elements on the page to which we can jump
var selector =
fileListingElements.join(', ') +
', ' +
notSelector +
missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
// The NodeList of matching elements
var missingCoverageElements = document.querySelectorAll(selector);
var currentIndex;
function toggleClass(index) {
missingCoverageElements
.item(currentIndex)
.classList.remove('highlighted');
missingCoverageElements.item(index).classList.add('highlighted');
}
function makeCurrent(index) {
toggleClass(index);
currentIndex = index;
missingCoverageElements.item(index).scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center'
});
}
function goToPrevious() {
var nextIndex = 0;
if (typeof currentIndex !== 'number' || currentIndex === 0) {
nextIndex = missingCoverageElements.length - 1;
} else if (missingCoverageElements.length > 1) {
nextIndex = currentIndex - 1;
}
makeCurrent(nextIndex);
}
function goToNext() {
var nextIndex = 0;
if (
typeof currentIndex === 'number' &&
currentIndex < missingCoverageElements.length - 1
) {
nextIndex = currentIndex + 1;
}
makeCurrent(nextIndex);
}
return function jump(event) {
switch (event.which) {
case 78: // n
case 74: // j
goToNext();
break;
case 66: // b
case 75: // k
case 80: // p
goToPrevious();
break;
}
};
})();
window.addEventListener('keydown', jumpToCode);

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

169
node_modules/istanbul-reports/lib/html/assets/sorter.js generated vendored Normal file
View file

@ -0,0 +1,169 @@
var addSorting = (function() {
'use strict';
var cols,
currentSort = {
index: 0,
desc: false
};
// returns the summary table element
function getTable() {
return document.querySelector('.coverage-summary');
}
// returns the thead element of the summary table
function getTableHeader() {
return getTable().querySelector('thead tr');
}
// returns the tbody element of the summary table
function getTableBody() {
return getTable().querySelector('tbody');
}
// returns the th element for nth column
function getNthColumn(n) {
return getTableHeader().querySelectorAll('th')[n];
}
// loads all columns
function loadColumns() {
var colNodes = getTableHeader().querySelectorAll('th'),
colNode,
cols = [],
col,
i;
for (i = 0; i < colNodes.length; i += 1) {
colNode = colNodes[i];
col = {
key: colNode.getAttribute('data-col'),
sortable: !colNode.getAttribute('data-nosort'),
type: colNode.getAttribute('data-type') || 'string'
};
cols.push(col);
if (col.sortable) {
col.defaultDescSort = col.type === 'number';
colNode.innerHTML =
colNode.innerHTML + '<span class="sorter"></span>';
}
}
return cols;
}
// attaches a data attribute to every tr element with an object
// of data values keyed by column name
function loadRowData(tableRow) {
var tableCols = tableRow.querySelectorAll('td'),
colNode,
col,
data = {},
i,
val;
for (i = 0; i < tableCols.length; i += 1) {
colNode = tableCols[i];
col = cols[i];
val = colNode.getAttribute('data-value');
if (col.type === 'number') {
val = Number(val);
}
data[col.key] = val;
}
return data;
}
// loads all row data
function loadData() {
var rows = getTableBody().querySelectorAll('tr'),
i;
for (i = 0; i < rows.length; i += 1) {
rows[i].data = loadRowData(rows[i]);
}
}
// sorts the table using the data for the ith column
function sortByIndex(index, desc) {
var key = cols[index].key,
sorter = function(a, b) {
a = a.data[key];
b = b.data[key];
return a < b ? -1 : a > b ? 1 : 0;
},
finalSorter = sorter,
tableBody = document.querySelector('.coverage-summary tbody'),
rowNodes = tableBody.querySelectorAll('tr'),
rows = [],
i;
if (desc) {
finalSorter = function(a, b) {
return -1 * sorter(a, b);
};
}
for (i = 0; i < rowNodes.length; i += 1) {
rows.push(rowNodes[i]);
tableBody.removeChild(rowNodes[i]);
}
rows.sort(finalSorter);
for (i = 0; i < rows.length; i += 1) {
tableBody.appendChild(rows[i]);
}
}
// removes sort indicators for current column being sorted
function removeSortIndicators() {
var col = getNthColumn(currentSort.index),
cls = col.className;
cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
col.className = cls;
}
// adds sort indicators for current column being sorted
function addSortIndicators() {
getNthColumn(currentSort.index).className += currentSort.desc
? ' sorted-desc'
: ' sorted';
}
// adds event listeners for all sorter widgets
function enableUI() {
var i,
el,
ithSorter = function ithSorter(i) {
var col = cols[i];
return function() {
var desc = col.defaultDescSort;
if (currentSort.index === i) {
desc = !currentSort.desc;
}
sortByIndex(i, desc);
removeSortIndicators();
currentSort.index = i;
currentSort.desc = desc;
addSortIndicators();
};
};
for (i = 0; i < cols.length; i += 1) {
if (cols[i].sortable) {
// add the click event handler on the th so users
// dont have to click on those tiny arrows
el = getNthColumn(i).querySelector('.sorter').parentElement;
if (el.addEventListener) {
el.addEventListener('click', ithSorter(i));
} else {
el.attachEvent('onclick', ithSorter(i));
}
}
}
}
// adds sorting functionality to the UI
return function() {
if (!getTable()) {
return;
}
cols = loadColumns();
loadData();
addSortIndicators();
enableUI();
};
})();
window.addEventListener('load', addSorting);

View file

@ -0,0 +1 @@
.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}

File diff suppressed because one or more lines are too long

106
node_modules/istanbul-reports/lib/html/helpers.js generated vendored Normal file
View file

@ -0,0 +1,106 @@
/*
Copyright 2012-2015, Yahoo Inc.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
function registerHelpers(handlebars) {
handlebars.registerHelper('show_picture', function(opts) {
let num = Number(opts.fn(this));
let rest;
let cls = '';
if (isFinite(num)) {
if (num === 100) {
cls = ' cover-full';
}
num = Math.floor(num);
rest = 100 - num;
return (
'<div class="cover-fill' +
cls +
'" style="width: ' +
num +
'%;"></div>' +
'<div class="cover-empty" style="width:' +
rest +
'%;"></div>'
);
} else {
return '';
}
});
handlebars.registerHelper('if_has_ignores', function(metrics, opts) {
return metrics.statements.skipped +
metrics.functions.skipped +
metrics.branches.skipped ===
0
? ''
: opts.fn(this);
});
handlebars.registerHelper('show_ignores', metrics => {
const statements = metrics.statements.skipped;
const functions = metrics.functions.skipped;
const branches = metrics.branches.skipped;
if (statements === 0 && functions === 0 && branches === 0) {
return '<span class="ignore-none">none</span>';
}
const result = [];
if (statements > 0) {
result.push(
statements === 1 ? '1 statement' : statements + ' statements'
);
}
if (functions > 0) {
result.push(
functions === 1 ? '1 function' : functions + ' functions'
);
}
if (branches > 0) {
result.push(branches === 1 ? '1 branch' : branches + ' branches');
}
return result.join(', ');
});
handlebars.registerHelper('show_lines', function(opts) {
const maxLines = Number(opts.fn(this));
let i;
const array = [];
for (i = 0; i < maxLines; i += 1) {
const nextNum = i + 1;
array[i] =
"<a name='L" +
nextNum +
"'></a><a href='#L" +
nextNum +
"'>" +
nextNum +
'</a>';
}
return array.join('\n');
});
handlebars.registerHelper('show_line_execution_counts', context => {
const array = [];
context.forEach(data => {
array.push(
'<span class="cline-any cline-' +
data.covered +
'">' +
data.hits +
'</span>'
);
});
return array.join('\n');
});
handlebars.registerHelper('show_code', (context /*, opts */) =>
context.join('\n')
);
}
module.exports = {
registerHelpers
};

274
node_modules/istanbul-reports/lib/html/index.js generated vendored Normal file
View file

@ -0,0 +1,274 @@
/*
Copyright 2012-2015, Yahoo Inc.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
const fs = require('fs');
const path = require('path');
const handlebars = require('handlebars').create();
const annotator = require('./annotator');
const helpers = require('./helpers');
const templateFor = function(name) {
return handlebars.compile(
fs.readFileSync(
path.resolve(__dirname, 'templates', name + '.txt'),
'utf8'
)
);
};
const headerTemplate = templateFor('head');
const footerTemplate = templateFor('foot');
const detailTemplate = handlebars.compile(
[
'<tr>',
'<td class="line-count quiet">{{#show_lines}}{{maxLines}}{{/show_lines}}</td>',
'<td class="line-coverage quiet">{{#show_line_execution_counts lineCoverage}}{{maxLines}}{{/show_line_execution_counts}}</td>',
'<td class="text"><pre class="prettyprint lang-js">{{#show_code annotatedCode}}{{/show_code}}</pre></td>',
'</tr>\n'
].join('')
);
const summaryTableHeader = [
'<div class="pad1">',
'<table class="coverage-summary">',
'<thead>',
'<tr>',
' <th data-col="file" data-fmt="html" data-html="true" class="file">File</th>',
' <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>',
' <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>',
' <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>',
' <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>',
' <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>',
' <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>',
' <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>',
' <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>',
' <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>',
'</tr>',
'</thead>',
'<tbody>'
].join('\n');
const summaryLineTemplate = handlebars.compile(
[
'<tr>',
'<td class="file {{reportClasses.statements}}" data-value="{{file}}"><a href="{{output}}">{{file}}</a></td>',
'<td data-value="{{metrics.statements.pct}}" class="pic {{reportClasses.statements}}"><div class="chart">{{#show_picture}}{{metrics.statements.pct}}{{/show_picture}}</div></td>',
'<td data-value="{{metrics.statements.pct}}" class="pct {{reportClasses.statements}}">{{metrics.statements.pct}}%</td>',
'<td data-value="{{metrics.statements.total}}" class="abs {{reportClasses.statements}}">{{metrics.statements.covered}}/{{metrics.statements.total}}</td>',
'<td data-value="{{metrics.branches.pct}}" class="pct {{reportClasses.branches}}">{{metrics.branches.pct}}%</td>',
'<td data-value="{{metrics.branches.total}}" class="abs {{reportClasses.branches}}">{{metrics.branches.covered}}/{{metrics.branches.total}}</td>',
'<td data-value="{{metrics.functions.pct}}" class="pct {{reportClasses.functions}}">{{metrics.functions.pct}}%</td>',
'<td data-value="{{metrics.functions.total}}" class="abs {{reportClasses.functions}}">{{metrics.functions.covered}}/{{metrics.functions.total}}</td>',
'<td data-value="{{metrics.lines.pct}}" class="pct {{reportClasses.lines}}">{{metrics.lines.pct}}%</td>',
'<td data-value="{{metrics.lines.total}}" class="abs {{reportClasses.lines}}">{{metrics.lines.covered}}/{{metrics.lines.total}}</td>',
'</tr>\n'
].join('\n\t')
);
const summaryTableFooter = ['</tbody>', '</table>', '</div>'].join('\n');
const emptyClasses = {
statements: 'empty',
lines: 'empty',
functions: 'empty',
branches: 'empty'
};
helpers.registerHelpers(handlebars);
const standardLinkMapper = {
getPath(node) {
if (typeof node === 'string') {
return node;
}
let filePath = node.getQualifiedName();
if (node.isSummary()) {
if (filePath !== '') {
filePath += '/index.html';
} else {
filePath = 'index.html';
}
} else {
filePath += '.html';
}
return filePath;
},
relativePath(source, target) {
const targetPath = this.getPath(target);
const sourcePath = path.dirname(this.getPath(source));
return path.relative(sourcePath, targetPath);
},
assetPath(node, name) {
return this.relativePath(this.getPath(node), name);
}
};
function fixPct(metrics) {
Object.keys(emptyClasses).forEach(key => {
metrics[key].pct = 0;
});
return metrics;
}
class HtmlReport {
constructor(opts) {
this.verbose = opts.verbose;
this.linkMapper = opts.linkMapper || standardLinkMapper;
this.subdir = opts.subdir || '';
this.date = Date();
this.skipEmpty = opts.skipEmpty;
}
getBreadcrumbHtml(node) {
let parent = node.getParent();
const nodePath = [];
while (parent) {
nodePath.push(parent);
parent = parent.getParent();
}
const linkPath = nodePath.map(ancestor => {
const target = this.linkMapper.relativePath(node, ancestor);
const name = ancestor.getRelativeName() || 'All files';
return '<a href="' + target + '">' + name + '</a>';
});
linkPath.reverse();
return linkPath.length > 0
? linkPath.join(' / ') + ' ' + node.getRelativeName()
: 'All files';
}
fillTemplate(node, templateData, context) {
const linkMapper = this.linkMapper;
const summary = node.getCoverageSummary();
templateData.entity = node.getQualifiedName() || 'All files';
templateData.metrics = summary;
templateData.reportClass = context.classForPercent(
'statements',
summary.statements.pct
);
templateData.pathHtml = this.getBreadcrumbHtml(node);
templateData.base = {
css: linkMapper.assetPath(node, 'base.css')
};
templateData.sorter = {
js: linkMapper.assetPath(node, 'sorter.js'),
image: linkMapper.assetPath(node, 'sort-arrow-sprite.png')
};
templateData.blockNavigation = {
js: linkMapper.assetPath(node, 'block-navigation.js')
};
templateData.prettify = {
js: linkMapper.assetPath(node, 'prettify.js'),
css: linkMapper.assetPath(node, 'prettify.css')
};
}
getTemplateData() {
return { datetime: this.date };
}
getWriter(context) {
if (!this.subdir) {
return context.writer;
}
return context.writer.writerForDir(this.subdir);
}
onStart(root, context) {
const assetHeaders = {
'.js': '/* eslint-disable */\n'
};
['.', 'vendor'].forEach(subdir => {
const writer = this.getWriter(context);
const srcDir = path.resolve(__dirname, 'assets', subdir);
fs.readdirSync(srcDir).forEach(f => {
const resolvedSource = path.resolve(srcDir, f);
const resolvedDestination = '.';
const stat = fs.statSync(resolvedSource);
let dest;
if (stat.isFile()) {
dest = resolvedDestination + '/' + f;
if (this.verbose) {
console.log('Write asset: ' + dest);
}
writer.copyFile(
resolvedSource,
dest,
assetHeaders[path.extname(f)]
);
}
});
});
}
onSummary(node, context) {
const linkMapper = this.linkMapper;
const templateData = this.getTemplateData();
const children = node.getChildren();
const skipEmpty = this.skipEmpty;
this.fillTemplate(node, templateData, context);
const cw = this.getWriter(context).writeFile(linkMapper.getPath(node));
cw.write(headerTemplate(templateData));
cw.write(summaryTableHeader);
children.forEach(child => {
const metrics = child.getCoverageSummary();
const isEmpty = metrics.isEmpty();
if (skipEmpty && isEmpty) {
return;
}
const reportClasses = isEmpty
? emptyClasses
: {
statements: context.classForPercent(
'statements',
metrics.statements.pct
),
lines: context.classForPercent(
'lines',
metrics.lines.pct
),
functions: context.classForPercent(
'functions',
metrics.functions.pct
),
branches: context.classForPercent(
'branches',
metrics.branches.pct
)
};
const data = {
metrics: isEmpty ? fixPct(metrics) : metrics,
reportClasses,
file: child.getRelativeName(),
output: linkMapper.relativePath(node, child)
};
cw.write(summaryLineTemplate(data) + '\n');
});
cw.write(summaryTableFooter);
cw.write(footerTemplate(templateData));
cw.close();
}
onDetail(node, context) {
const linkMapper = this.linkMapper;
const templateData = this.getTemplateData();
this.fillTemplate(node, templateData, context);
const cw = this.getWriter(context).writeFile(linkMapper.getPath(node));
cw.write(headerTemplate(templateData));
cw.write('<pre><table class="coverage">\n');
cw.write(
detailTemplate(
annotator.annotateSourceCode(node.getFileCoverage(), context)
)
);
cw.write('</table></pre>\n');
cw.write(footerTemplate(templateData));
cw.close();
}
}
module.exports = HtmlReport;

View file

@ -0,0 +1,113 @@
/*
Copyright 2012-2015, Yahoo Inc.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
function InsertionText(text, consumeBlanks) {
this.text = text;
this.origLength = text.length;
this.offsets = [];
this.consumeBlanks = consumeBlanks;
this.startPos = this.findFirstNonBlank();
this.endPos = this.findLastNonBlank();
}
const WHITE_RE = /[ \f\n\r\t\v\u00A0\u2028\u2029]/;
InsertionText.prototype = {
findFirstNonBlank() {
let pos = -1;
const text = this.text;
const len = text.length;
let i;
for (i = 0; i < len; i += 1) {
if (!text.charAt(i).match(WHITE_RE)) {
pos = i;
break;
}
}
return pos;
},
findLastNonBlank() {
const text = this.text;
const len = text.length;
let pos = text.length + 1;
let i;
for (i = len - 1; i >= 0; i -= 1) {
if (!text.charAt(i).match(WHITE_RE)) {
pos = i;
break;
}
}
return pos;
},
originalLength() {
return this.origLength;
},
insertAt(col, str, insertBefore, consumeBlanks) {
consumeBlanks =
typeof consumeBlanks === 'undefined'
? this.consumeBlanks
: consumeBlanks;
col = col > this.originalLength() ? this.originalLength() : col;
col = col < 0 ? 0 : col;
if (consumeBlanks) {
if (col <= this.startPos) {
col = 0;
}
if (col > this.endPos) {
col = this.origLength;
}
}
const len = str.length;
const offset = this.findOffset(col, len, insertBefore);
const realPos = col + offset;
const text = this.text;
this.text = text.substring(0, realPos) + str + text.substring(realPos);
return this;
},
findOffset(pos, len, insertBefore) {
const offsets = this.offsets;
let offsetObj;
let cumulativeOffset = 0;
let i;
for (i = 0; i < offsets.length; i += 1) {
offsetObj = offsets[i];
if (
offsetObj.pos < pos ||
(offsetObj.pos === pos && !insertBefore)
) {
cumulativeOffset += offsetObj.len;
}
if (offsetObj.pos >= pos) {
break;
}
}
if (offsetObj && offsetObj.pos === pos) {
offsetObj.len += len;
} else {
offsets.splice(i, 0, { pos, len });
}
return cumulativeOffset;
},
wrap(startPos, startText, endPos, endText, consumeBlanks) {
this.insertAt(startPos, startText, true, consumeBlanks);
this.insertAt(endPos, endText, false, consumeBlanks);
return this;
},
wrapLine(startText, endText) {
this.wrap(0, startText, this.originalLength(), endText);
},
toString() {
return this.text;
}
};
module.exports = InsertionText;

View file

@ -0,0 +1,21 @@
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage
generated by <a href="https://istanbul.js.org/" target="_blank">istanbul</a> at {{datetime}}
</div>
</div>
{{#if prettify}}
<script src="{{prettify.js}}"></script>
<script>
window.onload = function () {
if (typeof prettyPrint === 'function') {
prettyPrint();
}
};
</script>
{{/if}}
<script src="{{sorter.js}}"></script>
<script src="{{blockNavigation.js}}"></script>
</body>
</html>

View file

@ -0,0 +1,63 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for {{entity}}</title>
<meta charset="utf-8" />
{{#if prettify}}
<link rel="stylesheet" href="{{prettify.css}}" />
{{/if}}
<link rel="stylesheet" href="{{base.css}}" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type='text/css'>
.coverage-summary .sorter {
background-image: url({{sorter.image}});
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1>
{{{pathHtml}}}
</h1>
<div class='clearfix'>
{{#with metrics.statements}}
<div class='fl pad1y space-right2'>
<span class="strong">{{pct}}% </span>
<span class="quiet">Statements</span>
<span class='fraction'>{{covered}}/{{total}}</span>
</div>
{{/with}}
{{#with metrics.branches}}
<div class='fl pad1y space-right2'>
<span class="strong">{{pct}}% </span>
<span class="quiet">Branches</span>
<span class='fraction'>{{covered}}/{{total}}</span>
</div>
{{/with}}
{{#with metrics.functions}}
<div class='fl pad1y space-right2'>
<span class="strong">{{pct}}% </span>
<span class="quiet">Functions</span>
<span class='fraction'>{{covered}}/{{total}}</span>
</div>
{{/with}}
{{#with metrics.lines}}
<div class='fl pad1y space-right2'>
<span class="strong">{{pct}}% </span>
<span class="quiet">Lines</span>
<span class='fraction'>{{covered}}/{{total}}</span>
</div>
{{/with}}
{{#if_has_ignores metrics}}
<div class='fl pad1y'>
<span class="strong">{{#show_ignores metrics}}{{/show_ignores}}</span>
<span class="quiet">Ignored</span> &nbsp;&nbsp;&nbsp;&nbsp;
</div>
{{/if_has_ignores}}
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
</div>
<div class='status-line {{reportClass}}'></div>