feat: initial commit of Railtrack Pro prototype with complete test suite
This commit is contained in:
commit
40500bb503
7790 files changed
+986332
No files matched your search
+163
@@ -0,0 +1,163 @@
|
||||
'use strict';
|
||||
/*
|
||||
Copyright 2012-2015, Yahoo Inc.
|
||||
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
|
||||
*/
|
||||
const { ReportBase } = require('istanbul-lib-report');
|
||||
|
||||
class CloverReport extends ReportBase {
|
||||
constructor(opts) {
|
||||
super();
|
||||
|
||||
this.cw = null;
|
||||
this.xml = null;
|
||||
this.file = opts.file || 'clover.xml';
|
||||
}
|
||||
|
||||
onStart(root, context) {
|
||||
this.cw = context.writer.writeFile(this.file);
|
||||
this.xml = context.getXMLWriter(this.cw);
|
||||
this.writeRootStats(root, context);
|
||||
}
|
||||
|
||||
onEnd() {
|
||||
this.xml.closeAll();
|
||||
this.cw.close();
|
||||
}
|
||||
|
||||
getTreeStats(node, context) {
|
||||
const state = {
|
||||
packages: 0,
|
||||
files: 0,
|
||||
classes: 0
|
||||
};
|
||||
const visitor = {
|
||||
onSummary(node, state) {
|
||||
const metrics = node.getCoverageSummary(true);
|
||||
if (metrics) {
|
||||
state.packages += 1;
|
||||
}
|
||||
},
|
||||
onDetail(node, state) {
|
||||
state.classes += 1;
|
||||
state.files += 1;
|
||||
}
|
||||
};
|
||||
node.visit(context.getVisitor(visitor), state);
|
||||
return state;
|
||||
}
|
||||
|
||||
writeRootStats(node, context) {
|
||||
this.cw.println('<?xml version="1.0" encoding="UTF-8"?>');
|
||||
this.xml.openTag('coverage', {
|
||||
generated: Date.now().toString(),
|
||||
clover: '3.2.0'
|
||||
});
|
||||
|
||||
this.xml.openTag('project', {
|
||||
timestamp: Date.now().toString(),
|
||||
name: 'All files'
|
||||
});
|
||||
|
||||
const metrics = node.getCoverageSummary();
|
||||
this.xml.inlineTag('metrics', {
|
||||
statements: metrics.lines.total,
|
||||
coveredstatements: metrics.lines.covered,
|
||||
conditionals: metrics.branches.total,
|
||||
coveredconditionals: metrics.branches.covered,
|
||||
methods: metrics.functions.total,
|
||||
coveredmethods: metrics.functions.covered,
|
||||
elements:
|
||||
metrics.lines.total +
|
||||
metrics.branches.total +
|
||||
metrics.functions.total,
|
||||
coveredelements:
|
||||
metrics.lines.covered +
|
||||
metrics.branches.covered +
|
||||
metrics.functions.covered,
|
||||
complexity: 0,
|
||||
loc: metrics.lines.total,
|
||||
ncloc: metrics.lines.total, // what? copied as-is from old report
|
||||
...this.getTreeStats(node, context)
|
||||
});
|
||||
}
|
||||
|
||||
writeMetrics(metrics) {
|
||||
this.xml.inlineTag('metrics', {
|
||||
statements: metrics.lines.total,
|
||||
coveredstatements: metrics.lines.covered,
|
||||
conditionals: metrics.branches.total,
|
||||
coveredconditionals: metrics.branches.covered,
|
||||
methods: metrics.functions.total,
|
||||
coveredmethods: metrics.functions.covered
|
||||
});
|
||||
}
|
||||
|
||||
onSummary(node) {
|
||||
if (node.isRoot()) {
|
||||
return;
|
||||
}
|
||||
const metrics = node.getCoverageSummary(true);
|
||||
if (!metrics) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.xml.openTag('package', {
|
||||
name: asJavaPackage(node)
|
||||
});
|
||||
this.writeMetrics(metrics);
|
||||
}
|
||||
|
||||
onSummaryEnd(node) {
|
||||
if (node.isRoot()) {
|
||||
return;
|
||||
}
|
||||
this.xml.closeTag(this.xml.stack[this.xml.stack.length - 1]);
|
||||
}
|
||||
|
||||
onDetail(node) {
|
||||
const fileCoverage = node.getFileCoverage();
|
||||
const metrics = node.getCoverageSummary();
|
||||
const branchByLine = fileCoverage.getBranchCoverageByLine();
|
||||
|
||||
this.xml.openTag('file', {
|
||||
name: asClassName(node),
|
||||
path: fileCoverage.path
|
||||
});
|
||||
|
||||
this.writeMetrics(metrics);
|
||||
|
||||
const lines = fileCoverage.getLineCoverage();
|
||||
Object.entries(lines).forEach(([k, count]) => {
|
||||
const attrs = {
|
||||
num: k,
|
||||
count,
|
||||
type: 'stmt'
|
||||
};
|
||||
const branchDetail = branchByLine[k];
|
||||
|
||||
if (branchDetail) {
|
||||
attrs.type = 'cond';
|
||||
attrs.truecount = branchDetail.covered;
|
||||
attrs.falsecount = branchDetail.total - branchDetail.covered;
|
||||
}
|
||||
this.xml.inlineTag('line', attrs);
|
||||
});
|
||||
|
||||
this.xml.closeTag('file');
|
||||
}
|
||||
}
|
||||
|
||||
function asJavaPackage(node) {
|
||||
return node
|
||||
.getRelativeName()
|
||||
.replace(/\//g, '.')
|
||||
.replace(/\\/g, '.')
|
||||
.replace(/\.$/, '');
|
||||
}
|
||||
|
||||
function asClassName(node) {
|
||||
return node.getRelativeName().replace(/.*[\\/]/, '');
|
||||
}
|
||||
|
||||
module.exports = CloverReport;
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
'use strict';
|
||||
/*
|
||||
Copyright 2012-2015, Yahoo Inc.
|
||||
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
|
||||
*/
|
||||
const path = require('path');
|
||||
const { escape } = require('html-escaper');
|
||||
const { ReportBase } = require('istanbul-lib-report');
|
||||
|
||||
class CoberturaReport extends ReportBase {
|
||||
constructor(opts) {
|
||||
super();
|
||||
|
||||
opts = opts || {};
|
||||
|
||||
this.cw = null;
|
||||
this.xml = null;
|
||||
this.timestamp = opts.timestamp || Date.now().toString();
|
||||
this.projectRoot = opts.projectRoot || process.cwd();
|
||||
this.file = opts.file || 'cobertura-coverage.xml';
|
||||
}
|
||||
|
||||
onStart(root, context) {
|
||||
this.cw = context.writer.writeFile(this.file);
|
||||
this.xml = context.getXMLWriter(this.cw);
|
||||
this.writeRootStats(root);
|
||||
}
|
||||
|
||||
onEnd() {
|
||||
this.xml.closeAll();
|
||||
this.cw.close();
|
||||
}
|
||||
|
||||
writeRootStats(node) {
|
||||
const metrics = node.getCoverageSummary();
|
||||
this.cw.println('<?xml version="1.0" ?>');
|
||||
this.cw.println(
|
||||
'<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">'
|
||||
);
|
||||
this.xml.openTag('coverage', {
|
||||
'lines-valid': metrics.lines.total,
|
||||
'lines-covered': metrics.lines.covered,
|
||||
'line-rate': metrics.lines.pct / 100.0,
|
||||
'branches-valid': metrics.branches.total,
|
||||
'branches-covered': metrics.branches.covered,
|
||||
'branch-rate': metrics.branches.pct / 100.0,
|
||||
timestamp: this.timestamp,
|
||||
complexity: '0',
|
||||
version: '0.1'
|
||||
});
|
||||
this.xml.openTag('sources');
|
||||
this.xml.inlineTag('source', null, this.projectRoot);
|
||||
this.xml.closeTag('sources');
|
||||
this.xml.openTag('packages');
|
||||
}
|
||||
|
||||
onSummary(node) {
|
||||
const metrics = node.getCoverageSummary(true);
|
||||
if (!metrics) {
|
||||
return;
|
||||
}
|
||||
this.xml.openTag('package', {
|
||||
name: node.isRoot() ? 'main' : escape(asJavaPackage(node)),
|
||||
'line-rate': metrics.lines.pct / 100.0,
|
||||
'branch-rate': metrics.branches.pct / 100.0
|
||||
});
|
||||
this.xml.openTag('classes');
|
||||
}
|
||||
|
||||
onSummaryEnd(node) {
|
||||
const metrics = node.getCoverageSummary(true);
|
||||
if (!metrics) {
|
||||
return;
|
||||
}
|
||||
this.xml.closeTag('classes');
|
||||
this.xml.closeTag('package');
|
||||
}
|
||||
|
||||
onDetail(node) {
|
||||
const fileCoverage = node.getFileCoverage();
|
||||
const metrics = node.getCoverageSummary();
|
||||
const branchByLine = fileCoverage.getBranchCoverageByLine();
|
||||
|
||||
this.xml.openTag('class', {
|
||||
name: escape(asClassName(node)),
|
||||
filename: path.relative(this.projectRoot, fileCoverage.path),
|
||||
'line-rate': metrics.lines.pct / 100.0,
|
||||
'branch-rate': metrics.branches.pct / 100.0
|
||||
});
|
||||
|
||||
this.xml.openTag('methods');
|
||||
const fnMap = fileCoverage.fnMap;
|
||||
Object.entries(fnMap).forEach(([k, { name, decl }]) => {
|
||||
const hits = fileCoverage.f[k];
|
||||
this.xml.openTag('method', {
|
||||
name: escape(name),
|
||||
hits,
|
||||
signature: '()V' //fake out a no-args void return
|
||||
});
|
||||
this.xml.openTag('lines');
|
||||
//Add the function definition line and hits so that jenkins cobertura plugin records method hits
|
||||
this.xml.inlineTag('line', {
|
||||
number: decl.start.line,
|
||||
hits
|
||||
});
|
||||
this.xml.closeTag('lines');
|
||||
this.xml.closeTag('method');
|
||||
});
|
||||
this.xml.closeTag('methods');
|
||||
|
||||
this.xml.openTag('lines');
|
||||
const lines = fileCoverage.getLineCoverage();
|
||||
Object.entries(lines).forEach(([k, hits]) => {
|
||||
const attrs = {
|
||||
number: k,
|
||||
hits,
|
||||
branch: 'false'
|
||||
};
|
||||
const branchDetail = branchByLine[k];
|
||||
|
||||
if (branchDetail) {
|
||||
attrs.branch = true;
|
||||
attrs['condition-coverage'] =
|
||||
branchDetail.coverage +
|
||||
'% (' +
|
||||
branchDetail.covered +
|
||||
'/' +
|
||||
branchDetail.total +
|
||||
')';
|
||||
}
|
||||
this.xml.inlineTag('line', attrs);
|
||||
});
|
||||
|
||||
this.xml.closeTag('lines');
|
||||
this.xml.closeTag('class');
|
||||
}
|
||||
}
|
||||
|
||||
function asJavaPackage(node) {
|
||||
return node
|
||||
.getRelativeName()
|
||||
.replace(/\//g, '.')
|
||||
.replace(/\\/g, '.')
|
||||
.replace(/\.$/, '');
|
||||
}
|
||||
|
||||
function asClassName(node) {
|
||||
return node.getRelativeName().replace(/.*[\\/]/, '');
|
||||
}
|
||||
|
||||
module.exports = CoberturaReport;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": [["@babel/preset-env", { "modules": "commonjs" }], "@babel/preset-react"]
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var l=t[r]={i:r,l:!1,exports:{}};return e[r].call(l.exports,l,l.exports,n),l.l=!0,l.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var l in e)n.d(r,l,function(t){return e[t]}.bind(null,l));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=2)}([function(e,t,n){"use strict";e.exports=n(3)},function(e,t,n){"use strict";
|
||||
/*
|
||||
object-assign
|
||||
(c) Sindre Sorhus
|
||||
@license MIT
|
||||
*/var r=Object.getOwnPropertySymbols,l=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable;function a(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,t){for(var n,o,u=a(e),c=1;c<arguments.length;c++){for(var s in n=Object(arguments[c]))l.call(n,s)&&(u[s]=n[s]);if(r){o=r(n);for(var f=0;f<o.length;f++)i.call(n,o[f])&&(u[o[f]]=n[o[f]])}}return u}},function(e,t,n){"use strict";function r(){return(r=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)({}).hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(null,arguments)}function l(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var r,l,i,a,o=[],u=!0,c=!1;try{if(i=(n=n.call(e)).next,0===t){if(Object(n)!==n)return;u=!1}else for(;!(u=(r=i.call(n)).done)&&(o.push(r.value),o.length!==t);u=!0);}catch(e){c=!0,l=e}finally{try{if(!u&&null!=n.return&&(a=n.return(),Object(a)!==a))return}finally{if(c)throw l}}return o}}(e,t)||function(e,t){if(e){if("string"==typeof e)return i(e,t);var n={}.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?i(e,t):void 0}}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}for(var a=n(0),o=n(4),u=n(8),c=n(9),s=n(10),f=n(11),d=n(12),p=n(13),m=n(14),h=n(15),v=h.setLocation,y=h.decodeLocation,g=a.useState,b=a.useMemo,w=a.useEffect,E=window.data,k={},x=0;x<window.metricsToShow.length;x++)k[window.metricsToShow[x]]=!0;var S=!0;function T(){var e=y(),t=l(g(e&&e.activeSort||{sortKey:"file",order:"desc"}),2),n=t[0],i=t[1],h=l(g(e&&e.isFlat||!1),2),x=h[0],T=h[1],C=l(g(e&&e.activeFilters||{low:!0,medium:!0,high:!0}),2),_=C[0],P=C[1],N=l(g(e&&e.expandedLines||[]),2),O=N[0],F=N[1],z=l(g(e&&e.fileFilter||""),2),I=z[0],M=z[1],R=b((function(){return f(E,k,n,x,_,I)}),[n,x,_,I]),L=E.metrics;return w((function(){v(S,n,x,_,I,O),S=!1}),[n,x,_,I,O]),w((function(){window.onpopstate=function(){var e=y();e&&o.unstable_batchedUpdates((function(){P(e.activeFilters),i(e.activeSort),T(e.isFlat),F(e.expandedLines),M(e.fileFilter)}))}}),[]),a.createElement("div",{className:"layout"},a.createElement("div",{className:"layout__section"},a.createElement(s,{metrics:L,metricsToShow:k})),a.createElement("div",{className:"layout__section"},a.createElement("div",{className:"toolbar"},a.createElement("div",{className:"toolbar__item"},a.createElement(d,{setIsFlat:T,isFlat:x})),a.createElement("div",{className:"toolbar__item"},a.createElement(p,{activeFilters:_,setFilters:P})))),a.createElement("div",{className:"layout__section"},a.createElement("h1",null,a.createElement(m,{fileFilter:I,setFileFilter:M}))),a.createElement("div",{className:"layout__section layout__section--fill"},a.createElement("table",{className:"coverage-summary"},a.createElement(u,{onSort:function(e){i(e)},activeSort:n,metricsToShow:k}),a.createElement("tbody",null,R.map((function(e){return a.createElement(c,r({},e,{key:e.file,metricsToShow:k,expandedLines:O,setExpandedLines:F,fileFilter:I,setFileFilter:M}))}))))),a.createElement("div",{className:"layout__section center small quiet"},"Code coverage generated by"," ",a.createElement("a",{href:"https://istanbul.js.org/",target:"_blank",rel:"noopener noreferrer"},"istanbul")," ","at ",window.generatedDatetime))}o.render(a.createElement(T,null),document.getElementById("app"))},function(e,t,n){"use strict";
|
||||
/** @license React v16.14.0
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var r=n(1),l="function"==typeof Symbol&&Symbol.for,i=l?Symbol.for("react.element"):60103,a=l?Symbol.for("react.portal"):60106,o=l?Symbol.for("react.fragment"):60107,u=l?Symbol.for("react.strict_mode"):60108,c=l?Symbol.for("react.profiler"):60114,s=l?Symbol.for("react.provider"):60109,f=l?Symbol.for("react.context"):60110,d=l?Symbol.for("react.forward_ref"):60112,p=l?Symbol.for("react.suspense"):60113,m=l?Symbol.for("react.memo"):60115,h=l?Symbol.for("react.lazy"):60116,v="function"==typeof Symbol&&Symbol.iterator;function y(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n<arguments.length;n++)t+="&args[]="+encodeURIComponent(arguments[n]);return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}var g={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},b={};function w(e,t,n){this.props=e,this.context=t,this.refs=b,this.updater=n||g}function E(){}function k(e,t,n){this.props=e,this.context=t,this.refs=b,this.updater=n||g}w.prototype.isReactComponent={},w.prototype.setState=function(e,t){if("object"!=typeof e&&"function"!=typeof e&&null!=e)throw Error(y(85));this.updater.enqueueSetState(this,e,t,"setState")},w.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")},E.prototype=w.prototype;var x=k.prototype=new E;x.constructor=k,r(x,w.prototype),x.isPureReactComponent=!0;var S={current:null},T=Object.prototype.hasOwnProperty,C={key:!0,ref:!0,__self:!0,__source:!0};function _(e,t,n){var r,l={},a=null,o=null;if(null!=t)for(r in void 0!==t.ref&&(o=t.ref),void 0!==t.key&&(a=""+t.key),t)T.call(t,r)&&!C.hasOwnProperty(r)&&(l[r]=t[r]);var u=arguments.length-2;if(1===u)l.children=n;else if(1<u){for(var c=Array(u),s=0;s<u;s++)c[s]=arguments[s+2];l.children=c}if(e&&e.defaultProps)for(r in u=e.defaultProps)void 0===l[r]&&(l[r]=u[r]);return{$$typeof:i,type:e,key:a,ref:o,props:l,_owner:S.current}}function P(e){return"object"==typeof e&&null!==e&&e.$$typeof===i}var N=/\/+/g,O=[];function F(e,t,n,r){if(O.length){var l=O.pop();return l.result=e,l.keyPrefix=t,l.func=n,l.context=r,l.count=0,l}return{result:e,keyPrefix:t,func:n,context:r,count:0}}function z(e){e.result=null,e.keyPrefix=null,e.func=null,e.context=null,e.count=0,10>O.length&&O.push(e)}function I(e,t,n){return null==e?0:function e(t,n,r,l){var o=typeof t;"undefined"!==o&&"boolean"!==o||(t=null);var u=!1;if(null===t)u=!0;else switch(o){case"string":case"number":u=!0;break;case"object":switch(t.$$typeof){case i:case a:u=!0}}if(u)return r(l,t,""===n?"."+M(t,0):n),1;if(u=0,n=""===n?".":n+":",Array.isArray(t))for(var c=0;c<t.length;c++){var s=n+M(o=t[c],c);u+=e(o,s,r,l)}else if(null===t||"object"!=typeof t?s=null:s="function"==typeof(s=v&&t[v]||t["@@iterator"])?s:null,"function"==typeof s)for(t=s.call(t),c=0;!(o=t.next()).done;)u+=e(o=o.value,s=n+M(o,c++),r,l);else if("object"===o)throw r=""+t,Error(y(31,"[object Object]"===r?"object with keys {"+Object.keys(t).join(", ")+"}":r,""));return u}(e,"",t,n)}function M(e,t){return"object"==typeof e&&null!==e&&null!=e.key?function(e){var t={"=":"=0",":":"=2"};return"$"+(""+e).replace(/[=:]/g,(function(e){return t[e]}))}(e.key):t.toString(36)}function R(e,t){e.func.call(e.context,t,e.count++)}function L(e,t,n){var r=e.result,l=e.keyPrefix;e=e.func.call(e.context,t,e.count++),Array.isArray(e)?D(e,r,n,(function(e){return e})):null!=e&&(P(e)&&(e=function(e,t){return{$$typeof:i,type:e.type,key:t,ref:e.ref,props:e.props,_owner:e._owner}}(e,l+(!e.key||t&&t.key===e.key?"":(""+e.key).replace(N,"$&/")+"/")+n)),r.push(e))}function D(e,t,n,r,l){var i="";null!=n&&(i=(""+n).replace(N,"$&/")+"/"),I(e,L,t=F(t,i,r,l)),z(t)}var j={current:null};function A(){var e=j.current;if(null===e)throw Error(y(321));return e}var U={ReactCurrentDispatcher:j,ReactCurrentBatchConfig:{suspense:null},ReactCurrentOwner:S,IsSomeRendererActing:{current:!1},assign:r};t.Children={map:function(e,t,n){if(null==e)return e;var r=[];return D(e,r,null,t,n),r},forEach:function(e,t,n){if(null==e)return e;I(e,R,t=F(null,null,t,n)),z(t)},count:function(e){return I(e,(function(){return null}),null)},toArray:function(e){var t=[];return D(e,t,null,(function(e){return e})),t},only:function(e){if(!P(e))throw Error(y(143));return e}},t.Component=w,t.Fragment=o,t.Profiler=c,t.PureComponent=k,t.StrictMode=u,t.Suspense=p,t.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=U,t.cloneElement=function(e,t,n){if(null==e)throw Error(y(267,e));var l=r({},e.props),a=e.key,o=e.ref,u=e._owner;if(null!=t){if(void 0!==t.ref&&(o=t.ref,u=S.current),void 0!==t.key&&(a=""+t.key),e.type&&e.type.defaultProps)var c=e.type.defaultProps;for(s in t)T.call(t,s)&&!C.hasOwnProperty(s)&&(l[s]=void 0===t[s]&&void 0!==c?c[s]:t[s])}var s=arguments.length-2;if(1===s)l.children=n;else if(1<s){c=Array(s);for(var f=0;f<s;f++)c[f]=arguments[f+2];l.children=c}return{$$typeof:i,typLine truncated
|
||||
/** @license React v16.14.0
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var r=n(0),l=n(1),i=n(6);function a(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n<arguments.length;n++)t+="&args[]="+encodeURIComponent(arguments[n]);return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}if(!r)throw Error(a(227));function o(e,t,n,r,l,i,a,o,u){var c=Array.prototype.slice.call(arguments,3);try{t.apply(n,c)}catch(e){this.onError(e)}}var u=!1,c=null,s=!1,f=null,d={onError:function(e){u=!0,c=e}};function p(e,t,n,r,l,i,a,s,f){u=!1,c=null,o.apply(d,arguments)}var m=null,h=null,v=null;function y(e,t,n){var r=e.type||"unknown-event";e.currentTarget=v(n),function(e,t,n,r,l,i,o,d,m){if(p.apply(this,arguments),u){if(!u)throw Error(a(198));var h=c;u=!1,c=null,s||(s=!0,f=h)}}(r,t,void 0,e),e.currentTarget=null}var g=null,b={};function w(){if(g)for(var e in b){var t=b[e],n=g.indexOf(e);if(!(-1<n))throw Error(a(96,e));if(!k[n]){if(!t.extractEvents)throw Error(a(97,e));for(var r in k[n]=t,n=t.eventTypes){var l=void 0,i=n[r],o=t,u=r;if(x.hasOwnProperty(u))throw Error(a(99,u));x[u]=i;var c=i.phasedRegistrationNames;if(c){for(l in c)c.hasOwnProperty(l)&&E(c[l],o,u);l=!0}else i.registrationName?(E(i.registrationName,o,u),l=!0):l=!1;if(!l)throw Error(a(98,r,e))}}}}function E(e,t,n){if(S[e])throw Error(a(100,e));S[e]=t,T[e]=t.eventTypes[n].dependencies}var k=[],x={},S={},T={};function C(e){var t,n=!1;for(t in e)if(e.hasOwnProperty(t)){var r=e[t];if(!b.hasOwnProperty(t)||b[t]!==r){if(b[t])throw Error(a(102,t));b[t]=r,n=!0}}n&&w()}var _=!("undefined"==typeof window||void 0===window.document||void 0===window.document.createElement),P=null,N=null,O=null;function F(e){if(e=h(e)){if("function"!=typeof P)throw Error(a(280));var t=e.stateNode;t&&(t=m(t),P(e.stateNode,e.type,t))}}function z(e){N?O?O.push(e):O=[e]:N=e}function I(){if(N){var e=N,t=O;if(O=N=null,F(e),t)for(e=0;e<t.length;e++)F(t[e])}}function M(e,t){return e(t)}function R(e,t,n,r,l){return e(t,n,r,l)}function L(){}var D=M,j=!1,A=!1;function U(){null===N&&null===O||(L(),I())}function V(e,t,n){if(A)return e(t,n);A=!0;try{return D(e,t,n)}finally{A=!1,U()}}var W=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,Q=Object.prototype.hasOwnProperty,$={},B={};function H(e,t,n,r,l,i){this.acceptsBooleans=2===t||3===t||4===t,this.attributeName=r,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=i}var K={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach((function(e){K[e]=new H(e,0,!1,e,null,!1)})),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach((function(e){var t=e[0];K[t]=new H(t,1,!1,e[1],null,!1)})),["contentEditable","draggable","spellCheck","value"].forEach((function(e){K[e]=new H(e,2,!1,e.toLowerCase(),null,!1)})),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach((function(e){K[e]=new H(e,2,!1,e,null,!1)})),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach((function(e){K[e]=new H(e,3,!1,e.toLowerCase(),null,!1)})),["checked","multiple","muted","selected"].forEach((function(e){K[e]=new H(e,3,!0,e,null,!1)})),["capture","download"].forEach((function(e){K[e]=new H(e,4,!1,e,null,!1)})),["cols","rows","size","span"].forEach((function(e){K[e]=new H(e,6,!1,e,null,!1)})),["rowSpan","start"].forEach((function(e){K[e]=new H(e,5,!1,e.toLowerCase(),null,!1)}));var q=/[\-:]([a-z])/g;function Y(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-rangeLine truncated
|
||||
/** @license React v0.19.1
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var r,l,i,a,o;if("undefined"==typeof window||"function"!=typeof MessageChannel){var u=null,c=null,s=function(){if(null!==u)try{var e=t.unstable_now();u(!0,e),u=null}catch(e){throw setTimeout(s,0),e}},f=Date.now();t.unstable_now=function(){return Date.now()-f},r=function(e){null!==u?setTimeout(r,0,e):(u=e,setTimeout(s,0))},l=function(e,t){c=setTimeout(e,t)},i=function(){clearTimeout(c)},a=function(){return!1},o=t.unstable_forceFrameRate=function(){}}else{var d=window.performance,p=window.Date,m=window.setTimeout,h=window.clearTimeout;if("undefined"!=typeof console){var v=window.cancelAnimationFrame;"function"!=typeof window.requestAnimationFrame&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills"),"function"!=typeof v&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills")}if("object"==typeof d&&"function"==typeof d.now)t.unstable_now=function(){return d.now()};else{var y=p.now();t.unstable_now=function(){return p.now()-y}}var g=!1,b=null,w=-1,E=5,k=0;a=function(){return t.unstable_now()>=k},o=function(){},t.unstable_forceFrameRate=function(e){0>e||125<e?console.error("forceFrameRate takes a positive int between 0 and 125, forcing framerates higher than 125 fps is not unsupported"):E=0<e?Math.floor(1e3/e):5};var x=new MessageChannel,S=x.port2;x.port1.onmessage=function(){if(null!==b){var e=t.unstable_now();k=e+E;try{b(!0,e)?S.postMessage(null):(g=!1,b=null)}catch(e){throw S.postMessage(null),e}}else g=!1},r=function(e){b=e,g||(g=!0,S.postMessage(null))},l=function(e,n){w=m((function(){e(t.unstable_now())}),n)},i=function(){h(w),w=-1}}function T(e,t){var n=e.length;e.push(t);e:for(;;){var r=n-1>>>1,l=e[r];if(!(void 0!==l&&0<P(l,t)))break e;e[r]=t,e[n]=l,n=r}}function C(e){return void 0===(e=e[0])?null:e}function _(e){var t=e[0];if(void 0!==t){var n=e.pop();if(n!==t){e[0]=n;e:for(var r=0,l=e.length;r<l;){var i=2*(r+1)-1,a=e[i],o=i+1,u=e[o];if(void 0!==a&&0>P(a,n))void 0!==u&&0>P(u,a)?(e[r]=u,e[o]=n,r=o):(e[r]=a,e[i]=n,r=i);else{if(!(void 0!==u&&0>P(u,n)))break e;e[r]=u,e[o]=n,r=o}}}return t}return null}function P(e,t){var n=e.sortIndex-t.sortIndex;return 0!==n?n:e.id-t.id}var N=[],O=[],F=1,z=null,I=3,M=!1,R=!1,L=!1;function D(e){for(var t=C(O);null!==t;){if(null===t.callback)_(O);else{if(!(t.startTime<=e))break;_(O),t.sortIndex=t.expirationTime,T(N,t)}t=C(O)}}function j(e){if(L=!1,D(e),!R)if(null!==C(N))R=!0,r(A);else{var t=C(O);null!==t&&l(j,t.startTime-e)}}function A(e,n){R=!1,L&&(L=!1,i()),M=!0;var r=I;try{for(D(n),z=C(N);null!==z&&(!(z.expirationTime>n)||e&&!a());){var o=z.callback;if(null!==o){z.callback=null,I=z.priorityLevel;var u=o(z.expirationTime<=n);n=t.unstable_now(),"function"==typeof u?z.callback=u:z===C(N)&&_(N),D(n)}else _(N);z=C(N)}if(null!==z)var c=!0;else{var s=C(O);null!==s&&l(j,s.startTime-n),c=!1}return c}finally{z=null,I=r,M=!1}}function U(e){switch(e){case 1:return-1;case 2:return 250;case 5:return 1073741823;case 4:return 1e4;default:return 5e3}}var V=o;t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_continueExecution=function(){R||M||(R=!0,r(A))},t.unstable_getCurrentPriorityLevel=function(){return I},t.unstable_getFirstCallbackNode=function(){return C(N)},t.unstable_next=function(e){switch(I){case 1:case 2:case 3:var t=3;break;default:t=I}var n=I;I=t;try{return e()}finally{I=n}},t.unstable_pauseExecution=function(){},t.unstable_requestPaint=V,t.unstable_runWithPriority=function(e,t){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var n=I;I=e;try{return t()}finally{I=n}},t.unstable_scheduleCallback=function(e,n,a){var o=t.unstable_now();if("object"==typeof a&&null!==a){var u=a.delay;u="number"==typeof u&&0<u?o+u:o,a="number"==typeof a.timeout?a.timeout:U(e)}else a=U(e),u=o;return e={id:F++,callback:n,priorityLevel:e,startTime:u,expirationTime:a=u+a,sortIndex:-1},u>o?(e.sortIndex=u,T(O,e),null===C(N)&&e===C(O)&&(L?i():L=!0,l(j,u-o))):(e.sortIndex=a,T(N,e),R||M||(R=!0,r(A))),e},t.unstable_shouldYield=function(){var e=t.unstable_now();D(e);var n=C(N);return n!==z&&null!==z&&null!==n&&null!==n.callback&&n.startTime<=e&&n.expirationTime<z.expirationTime||a()},t.unstable_wrapCallback=function(e){var t=I;return function(){var n=I;I=t;try{return e.apply(this,arguments)}finally{I=n}}}},function(e,t,n){"use strict";var r=n(0);function l(e,t){var n={sortKey:e,order:"desc"},r="";return t&&t.sortKey===e&&(r="sorted","desc"===t.order?(r+="-desc",n.order="asc"):"file"!==e&&(n={sortKey:"file",order:"desc"})),{newSort:n,sortClass:r}}function i(e){var t=e.name,n=e.onSort,i=l(e.sortKey,e.activeSort),a=i.newSort,o=i.sortClass;return r.createElement("th",{className:"sortable headercell "+o,onClick:function()
Line truncated
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 138 B |
+298
@@ -0,0 +1,298 @@
|
||||
/* Base */
|
||||
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #333;
|
||||
background-color: #fcfcfc;
|
||||
font: 14px/14px -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI',
|
||||
Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0;
|
||||
border: none;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
*,
|
||||
*:after,
|
||||
*:before {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0074d9;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.quiet {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Colors */
|
||||
|
||||
.low {
|
||||
background: #fce1e5;
|
||||
}
|
||||
|
||||
.low--dark {
|
||||
background: #c21f39;
|
||||
}
|
||||
|
||||
.medium {
|
||||
background: #fff4c2;
|
||||
}
|
||||
|
||||
.medium--dark {
|
||||
background: #f9cd0b;
|
||||
}
|
||||
|
||||
.high {
|
||||
background: rgb(230, 245, 208);
|
||||
}
|
||||
|
||||
.high--dark {
|
||||
background: rgb(77, 146, 33);
|
||||
}
|
||||
|
||||
/* App */
|
||||
|
||||
.app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
|
||||
.layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.layout__section {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.layout__section--fill {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.layout__section + .layout__section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 640px) {
|
||||
.col3 {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
.hide-mobile {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Toolbar */
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.toolbar__item {
|
||||
margin-right: 40px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Toggle */
|
||||
|
||||
.toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toggle__label {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.toggle__options {
|
||||
display: inline-block;
|
||||
border: 1px solid #0074d9;
|
||||
border-radius: 4px;
|
||||
color: #0074d9;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.toggle__option {
|
||||
padding: 4px 8px;
|
||||
background: #fcfcfc;
|
||||
}
|
||||
|
||||
.toggle__option + .toggle__option {
|
||||
border-left: 1px solid #0074d9;
|
||||
}
|
||||
|
||||
.toggle__option.is-toggled {
|
||||
color: #fff;
|
||||
background: #0074d9;
|
||||
border-left-color: #fcfcfc;
|
||||
}
|
||||
|
||||
/* Expand */
|
||||
|
||||
.expandbutton {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
margin-right: 0.25em;
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Fraction */
|
||||
|
||||
.fraction {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Coverage */
|
||||
|
||||
.coverage-summary {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.coverage-summary tbody tr {
|
||||
border-bottom: 1px solid #fff;
|
||||
}
|
||||
|
||||
.coverage-summary td,
|
||||
.coverage-summary th {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.coverage-summary th {
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.coverage-summary th.abs,
|
||||
.coverage-summary td.pct,
|
||||
.coverage-summary td.abs {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.coverage-summary th.file {
|
||||
min-width: 300px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.coverage-summary td.file {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.coverage-summary td.pct {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.coverage-summary td.abs {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.coverage-summary td.empty {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.coverage-summary .headercell {
|
||||
border-top: 1px solid #eee;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.coverage-summary .headercell:nth-child(5n - 3),
|
||||
.coverage-summary td:nth-child(5n - 3) {
|
||||
border-left: 2px solid #fcfcfc;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
.filetab {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
/* Sorter */
|
||||
|
||||
.sorter {
|
||||
display: inline-block;
|
||||
width: 7px;
|
||||
height: 10px;
|
||||
margin-left: 0.5em;
|
||||
background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
|
||||
}
|
||||
|
||||
.sorted .sorter {
|
||||
background-position: 0 -20px;
|
||||
}
|
||||
|
||||
.sorted-desc .sorter {
|
||||
background-position: 0 -10px;
|
||||
}
|
||||
|
||||
.sortable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Bar */
|
||||
|
||||
.bar {
|
||||
width: 50px;
|
||||
height: 5px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.bar__data {
|
||||
height: 100%;
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
'use strict';
|
||||
/*
|
||||
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 { ReportBase } = require('istanbul-lib-report');
|
||||
const HtmlReport = require('../html');
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
class HtmlSpaReport extends ReportBase {
|
||||
constructor(opts = {}) {
|
||||
super({
|
||||
// force the summarizer to nested for html-spa
|
||||
summarizer: 'nested'
|
||||
});
|
||||
|
||||
this.verbose = opts.verbose || false;
|
||||
this.linkMapper = opts.linkMapper || standardLinkMapper;
|
||||
this.subdir = opts.subdir || '';
|
||||
this.date = Date();
|
||||
this.skipEmpty = opts.skipEmpty;
|
||||
this.htmlReport = new HtmlReport(opts);
|
||||
this.htmlReport.getBreadcrumbHtml = function() {
|
||||
return '<a href="javascript:history.back()">Back</a>';
|
||||
};
|
||||
|
||||
this.metricsToShow = opts.metricsToShow || [
|
||||
'lines',
|
||||
'branches',
|
||||
'functions'
|
||||
];
|
||||
}
|
||||
|
||||
getWriter(context) {
|
||||
if (!this.subdir) {
|
||||
return context.writer;
|
||||
}
|
||||
return context.writer.writerForDir(this.subdir);
|
||||
}
|
||||
|
||||
onStart(root, context) {
|
||||
this.htmlReport.onStart(root, context);
|
||||
|
||||
const writer = this.getWriter(context);
|
||||
const srcDir = path.resolve(__dirname, './assets');
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onDetail(node, context) {
|
||||
this.htmlReport.onDetail(node, context);
|
||||
}
|
||||
|
||||
getMetric(metric, type, context) {
|
||||
const isEmpty = metric.total === 0;
|
||||
return {
|
||||
total: metric.total,
|
||||
covered: metric.covered,
|
||||
skipped: metric.skipped,
|
||||
missed: metric.total - metric.covered,
|
||||
pct: isEmpty ? 0 : metric.pct,
|
||||
classForPercent: isEmpty
|
||||
? 'empty'
|
||||
: context.classForPercent(type, metric.pct)
|
||||
};
|
||||
}
|
||||
|
||||
toDataStructure(node, context) {
|
||||
const coverageSummary = node.getCoverageSummary();
|
||||
const metrics = {
|
||||
statements: this.getMetric(
|
||||
coverageSummary.statements,
|
||||
'statements',
|
||||
context
|
||||
),
|
||||
branches: this.getMetric(
|
||||
coverageSummary.branches,
|
||||
'branches',
|
||||
context
|
||||
),
|
||||
functions: this.getMetric(
|
||||
coverageSummary.functions,
|
||||
'functions',
|
||||
context
|
||||
),
|
||||
lines: this.getMetric(coverageSummary.lines, 'lines', context)
|
||||
};
|
||||
|
||||
return {
|
||||
file: node.getRelativeName(),
|
||||
isEmpty: coverageSummary.isEmpty(),
|
||||
metrics,
|
||||
children:
|
||||
node.isSummary() &&
|
||||
node
|
||||
.getChildren()
|
||||
.map(child => this.toDataStructure(child, context))
|
||||
};
|
||||
}
|
||||
|
||||
onEnd(rootNode, context) {
|
||||
const data = this.toDataStructure(rootNode, context);
|
||||
|
||||
const cw = this.getWriter(context).writeFile(
|
||||
this.linkMapper.getPath(rootNode)
|
||||
);
|
||||
|
||||
cw.write(
|
||||
`<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="stylesheet" href="spa.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="app"></div>
|
||||
<script>
|
||||
window.data = ${JSON.stringify(data)};
|
||||
window.generatedDatetime = ${JSON.stringify(
|
||||
String(Date())
|
||||
)};
|
||||
window.metricsToShow = ${JSON.stringify(
|
||||
this.metricsToShow
|
||||
)};
|
||||
</script>
|
||||
<script src="bundle.js"></script>
|
||||
</body>
|
||||
</html>`
|
||||
);
|
||||
cw.close();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HtmlSpaReport;
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
const React = require('react');
|
||||
|
||||
module.exports = function FileBreadcrumbs({ fileFilter = '', setFileFilter }) {
|
||||
const parts = fileFilter.split('/');
|
||||
const breadcrumbs = [
|
||||
{
|
||||
path: '',
|
||||
name: 'all files'
|
||||
},
|
||||
...parts.map((part, i) => ({
|
||||
path: parts.slice(0, i + 1).join('/'),
|
||||
name: part
|
||||
}))
|
||||
];
|
||||
|
||||
return breadcrumbs.map(({ path, name }) =>
|
||||
path === fileFilter ? (
|
||||
name
|
||||
) : (
|
||||
<>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
onClick={() => setFileFilter(path)}
|
||||
>
|
||||
{name}
|
||||
</a>
|
||||
/
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
const React = require('react');
|
||||
|
||||
function ToggleOption({ children, filter, activeFilters, setFilters }) {
|
||||
return (
|
||||
<button
|
||||
className={
|
||||
'toggle__option ' + (activeFilters[filter] ? 'is-toggled' : '')
|
||||
}
|
||||
onClick={() =>
|
||||
setFilters({
|
||||
...activeFilters,
|
||||
[filter]: !activeFilters[filter]
|
||||
})
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = function FilterToggle({ activeFilters, setFilters }) {
|
||||
return (
|
||||
<div className="toggle">
|
||||
<div className="toggle__label">Filter:</div>
|
||||
<div className="toggle__options">
|
||||
<ToggleOption
|
||||
filter="low"
|
||||
activeFilters={activeFilters}
|
||||
setFilters={setFilters}
|
||||
>
|
||||
Low
|
||||
</ToggleOption>
|
||||
<ToggleOption
|
||||
filter="medium"
|
||||
activeFilters={activeFilters}
|
||||
setFilters={setFilters}
|
||||
>
|
||||
Medium
|
||||
</ToggleOption>
|
||||
<ToggleOption
|
||||
filter="high"
|
||||
activeFilters={activeFilters}
|
||||
setFilters={setFilters}
|
||||
>
|
||||
High
|
||||
</ToggleOption>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
const React = require('react');
|
||||
|
||||
module.exports = function FlattenButton({ setIsFlat, isFlat }) {
|
||||
return (
|
||||
<div className="toggle">
|
||||
<div className="toggle__label">Files:</div>
|
||||
<div className="toggle__options">
|
||||
<button
|
||||
onClick={() => setIsFlat(!isFlat)}
|
||||
className={
|
||||
'toggle__option ' + (!isFlat ? 'is-toggled' : '')
|
||||
}
|
||||
>
|
||||
Tree
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsFlat(!isFlat)}
|
||||
className={'toggle__option ' + (isFlat ? 'is-toggled' : '')}
|
||||
>
|
||||
Flat
|
||||
</button>
|
||||
</div>{' '}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
function addPath(node, parentPath) {
|
||||
if (!parentPath) {
|
||||
return node;
|
||||
}
|
||||
return { ...node, file: parentPath + '/' + node.file };
|
||||
}
|
||||
|
||||
function flatten(nodes, parentPath) {
|
||||
let children = [];
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const child = nodes[i];
|
||||
if (child.children) {
|
||||
children = [
|
||||
...children,
|
||||
...flatten(
|
||||
child.children,
|
||||
(parentPath ? parentPath + '/' : '') + child.file
|
||||
)
|
||||
];
|
||||
} else {
|
||||
children.push(addPath(child, parentPath));
|
||||
}
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
function filterByFile(nodes, fileFilter, parentPath) {
|
||||
let children = [];
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const child = nodes[i];
|
||||
const childFullPath = (parentPath ? parentPath + '/' : '') + child.file;
|
||||
|
||||
const isChildUnderFilter =
|
||||
fileFilter === childFullPath ||
|
||||
fileFilter.indexOf(childFullPath + '/') === 0;
|
||||
const isChildAboveFilter =
|
||||
childFullPath.indexOf(fileFilter + '/') === 0;
|
||||
|
||||
if (isChildUnderFilter) {
|
||||
// flatten and continue looking underneath
|
||||
children = [
|
||||
...children,
|
||||
...filterByFile(child.children, fileFilter, childFullPath)
|
||||
];
|
||||
} else if (isChildAboveFilter) {
|
||||
// remove the parent path and add everything underneath
|
||||
const charsToRemoveFromFile =
|
||||
fileFilter.length - (parentPath ? parentPath.length : 0);
|
||||
let childFilename = child.file.slice(charsToRemoveFromFile);
|
||||
if (childFilename[0] === '/') {
|
||||
childFilename = childFilename.slice(1);
|
||||
}
|
||||
children.push({
|
||||
...child,
|
||||
file: childFilename
|
||||
});
|
||||
}
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
function sort(childData, activeSort) {
|
||||
const top = activeSort.order === 'asc' ? 1 : -1;
|
||||
const bottom = activeSort.order === 'asc' ? -1 : 1;
|
||||
childData.sort((a, b) => {
|
||||
let valueA;
|
||||
let valueB;
|
||||
if (activeSort.sortKey === 'file') {
|
||||
valueA = a.file;
|
||||
valueB = b.file;
|
||||
} else {
|
||||
const [metricType, valueType] = activeSort.sortKey.split('.');
|
||||
valueA = a.metrics[metricType][valueType];
|
||||
valueB = b.metrics[metricType][valueType];
|
||||
}
|
||||
|
||||
if (valueA === valueB) {
|
||||
return 0;
|
||||
}
|
||||
return valueA < valueB ? top : bottom;
|
||||
});
|
||||
|
||||
for (let i = 0; i < childData.length; i++) {
|
||||
const child = childData[i];
|
||||
if (child.children) {
|
||||
childData[i] = {
|
||||
...child,
|
||||
children: sort(child.children, activeSort)
|
||||
};
|
||||
}
|
||||
}
|
||||
return childData;
|
||||
}
|
||||
|
||||
function filter(nodes, metricsMap, activeFilters) {
|
||||
const children = [];
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let child = nodes[i];
|
||||
if (child.children) {
|
||||
const newSubChildren = filter(
|
||||
child.children,
|
||||
metricsMap,
|
||||
activeFilters
|
||||
);
|
||||
if (newSubChildren.length) {
|
||||
child = { ...child, children: newSubChildren };
|
||||
children.push(child);
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
(metricsMap.statements &&
|
||||
activeFilters[child.metrics.statements.classForPercent]) ||
|
||||
(metricsMap.branches &&
|
||||
activeFilters[child.metrics.branches.classForPercent]) ||
|
||||
(metricsMap.functions &&
|
||||
activeFilters[child.metrics.functions.classForPercent]) ||
|
||||
(metricsMap.lines &&
|
||||
activeFilters[child.metrics.lines.classForPercent])
|
||||
) {
|
||||
children.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
module.exports = function getChildData(
|
||||
sourceData,
|
||||
metricsToShow,
|
||||
activeSort,
|
||||
isFlat,
|
||||
activeFilters,
|
||||
fileFilter
|
||||
) {
|
||||
let childData = sourceData.children;
|
||||
|
||||
if (isFlat) {
|
||||
childData = flatten(childData.slice(0));
|
||||
}
|
||||
|
||||
if (fileFilter) {
|
||||
childData = filterByFile(childData, fileFilter);
|
||||
}
|
||||
|
||||
if (activeFilters.low) {
|
||||
activeFilters = { ...activeFilters, empty: true };
|
||||
}
|
||||
childData = filter(childData, metricsToShow, activeFilters);
|
||||
|
||||
if (activeSort) {
|
||||
childData = sort(childData, activeSort);
|
||||
}
|
||||
return childData;
|
||||
};
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
// The index file for the spa running on the summary page
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
const SummaryTableHeader = require('./summaryTableHeader');
|
||||
const SummaryTableLine = require('./summaryTableLine');
|
||||
const SummaryHeader = require('./summaryHeader');
|
||||
const getChildData = require('./getChildData');
|
||||
const FlattenToggle = require('./flattenToggle');
|
||||
const FilterToggle = require('./filterToggle');
|
||||
const FileBreadcrumbs = require('./fileBreadcrumbs');
|
||||
const { setLocation, decodeLocation } = require('./routing');
|
||||
|
||||
const { useState, useMemo, useEffect } = React;
|
||||
|
||||
const sourceData = window.data;
|
||||
const metricsToShow = {};
|
||||
for (let i = 0; i < window.metricsToShow.length; i++) {
|
||||
metricsToShow[window.metricsToShow[i]] = true;
|
||||
}
|
||||
|
||||
let firstMount = true;
|
||||
|
||||
function App() {
|
||||
const routingDefaults = decodeLocation();
|
||||
|
||||
const [activeSort, setSort] = useState(
|
||||
(routingDefaults && routingDefaults.activeSort) || {
|
||||
sortKey: 'file',
|
||||
order: 'desc'
|
||||
}
|
||||
);
|
||||
const [isFlat, setIsFlat] = useState(
|
||||
(routingDefaults && routingDefaults.isFlat) || false
|
||||
);
|
||||
const [activeFilters, setFilters] = useState(
|
||||
(routingDefaults && routingDefaults.activeFilters) || {
|
||||
low: true,
|
||||
medium: true,
|
||||
high: true
|
||||
}
|
||||
);
|
||||
const [expandedLines, setExpandedLines] = useState(
|
||||
(routingDefaults && routingDefaults.expandedLines) || []
|
||||
);
|
||||
const [fileFilter, setFileFilter] = useState(
|
||||
(routingDefaults && routingDefaults.fileFilter) || ''
|
||||
);
|
||||
const childData = useMemo(
|
||||
() =>
|
||||
getChildData(
|
||||
sourceData,
|
||||
metricsToShow,
|
||||
activeSort,
|
||||
isFlat,
|
||||
activeFilters,
|
||||
fileFilter
|
||||
),
|
||||
[activeSort, isFlat, activeFilters, fileFilter]
|
||||
);
|
||||
const overallMetrics = sourceData.metrics;
|
||||
|
||||
useEffect(() => {
|
||||
setLocation(
|
||||
firstMount,
|
||||
activeSort,
|
||||
isFlat,
|
||||
activeFilters,
|
||||
fileFilter,
|
||||
expandedLines
|
||||
);
|
||||
firstMount = false;
|
||||
}, [activeSort, isFlat, activeFilters, fileFilter, expandedLines]);
|
||||
|
||||
useEffect(() => {
|
||||
window.onpopstate = () => {
|
||||
const routingState = decodeLocation();
|
||||
if (routingState) {
|
||||
// make sure all the state is set before rendering to avoid url updates
|
||||
// alternative is to merge all the states into one so it can be set in one go
|
||||
// https://github.com/facebook/react/issues/14259
|
||||
ReactDOM.unstable_batchedUpdates(() => {
|
||||
setFilters(routingState.activeFilters);
|
||||
setSort(routingState.activeSort);
|
||||
setIsFlat(routingState.isFlat);
|
||||
setExpandedLines(routingState.expandedLines);
|
||||
setFileFilter(routingState.fileFilter);
|
||||
});
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="layout">
|
||||
<div className="layout__section">
|
||||
<SummaryHeader
|
||||
metrics={overallMetrics}
|
||||
metricsToShow={metricsToShow}
|
||||
/>
|
||||
</div>
|
||||
<div className="layout__section">
|
||||
<div className="toolbar">
|
||||
<div className="toolbar__item">
|
||||
<FlattenToggle setIsFlat={setIsFlat} isFlat={isFlat} />
|
||||
</div>
|
||||
<div className="toolbar__item">
|
||||
<FilterToggle
|
||||
activeFilters={activeFilters}
|
||||
setFilters={setFilters}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="layout__section">
|
||||
<h1>
|
||||
<FileBreadcrumbs
|
||||
fileFilter={fileFilter}
|
||||
setFileFilter={setFileFilter}
|
||||
/>
|
||||
</h1>
|
||||
</div>
|
||||
<div className="layout__section layout__section--fill">
|
||||
<table className="coverage-summary">
|
||||
<SummaryTableHeader
|
||||
onSort={newSort => {
|
||||
setSort(newSort);
|
||||
}}
|
||||
activeSort={activeSort}
|
||||
metricsToShow={metricsToShow}
|
||||
/>
|
||||
<tbody>
|
||||
{childData.map(child => (
|
||||
<SummaryTableLine
|
||||
{...child}
|
||||
key={child.file}
|
||||
metricsToShow={metricsToShow}
|
||||
expandedLines={expandedLines}
|
||||
setExpandedLines={setExpandedLines}
|
||||
fileFilter={fileFilter}
|
||||
setFileFilter={setFileFilter}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="layout__section center small quiet">
|
||||
Code coverage generated by{' '}
|
||||
<a
|
||||
href="https://istanbul.js.org/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
istanbul
|
||||
</a>{' '}
|
||||
at {window.generatedDatetime}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('app'));
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
exports.setLocation = function setLocation(
|
||||
isReplace,
|
||||
activeSort,
|
||||
isFlat,
|
||||
activeFilters,
|
||||
fileFilter,
|
||||
expandedLines
|
||||
) {
|
||||
const params = [
|
||||
activeSort.sortKey,
|
||||
activeSort.order,
|
||||
isFlat,
|
||||
activeFilters.low,
|
||||
activeFilters.medium,
|
||||
activeFilters.high,
|
||||
encodeURIComponent(fileFilter),
|
||||
expandedLines.map(encodeURIComponent).join(',')
|
||||
];
|
||||
const newUrl = `#${params.join('/')}`;
|
||||
|
||||
if (newUrl === location.hash) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.history[isReplace ? 'replaceState' : 'pushState'](null, '', newUrl);
|
||||
};
|
||||
|
||||
exports.decodeLocation = function decodeLocation() {
|
||||
const items = location.hash.substr(1).split('/');
|
||||
if (items.length !== 8) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return {
|
||||
activeSort: {
|
||||
sortKey: items[0],
|
||||
order: items[1]
|
||||
},
|
||||
isFlat: JSON.parse(items[2]),
|
||||
activeFilters: {
|
||||
low: JSON.parse(items[3]),
|
||||
medium: JSON.parse(items[4]),
|
||||
high: JSON.parse(items[5])
|
||||
},
|
||||
fileFilter: decodeURIComponent(items[6]),
|
||||
expandedLines: items[7].split(',').map(decodeURIComponent)
|
||||
};
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
const React = require('react');
|
||||
|
||||
function Ignores({ metrics, metricsToShow }) {
|
||||
const metricKeys = Object.keys(metricsToShow);
|
||||
const result = [];
|
||||
|
||||
for (let i = 0; i < metricKeys.length; i++) {
|
||||
const metricKey = metricKeys[i];
|
||||
if (metricsToShow[metricKey]) {
|
||||
const skipped = metrics[metricKey].skipped;
|
||||
if (skipped > 0) {
|
||||
result.push(
|
||||
`${skipped} ${metricKey}${
|
||||
skipped === 1 ? '' : metricKey === 'branch' ? 'es' : 's'
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="toolbar__item">
|
||||
<span className="strong">{result.join(', ')}</span>
|
||||
<span className="quiet">Ignored</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StatusMetric({ data, name }) {
|
||||
return (
|
||||
<div className="toolbar__item">
|
||||
<span className="strong">{data.pct}%</span>{' '}
|
||||
<span className="quiet">{name}</span>{' '}
|
||||
<span className={'fraction ' + data.classForPercent}>
|
||||
{data.covered}/{data.total}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = function SummaryHeader({ metrics, metricsToShow }) {
|
||||
return (
|
||||
<div className="toolbar">
|
||||
{metricsToShow.statements && (
|
||||
<StatusMetric data={metrics.statements} name="Statements" />
|
||||
)}
|
||||
{metricsToShow.branches && (
|
||||
<StatusMetric data={metrics.branches} name="Branches" />
|
||||
)}
|
||||
{metricsToShow.functions && (
|
||||
<StatusMetric data={metrics.functions} name="Functions" />
|
||||
)}
|
||||
{metricsToShow.lines && (
|
||||
<StatusMetric data={metrics.lines} name="Lines" />
|
||||
)}
|
||||
<Ignores metrics={metrics} metricsToShow={metricsToShow} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
const React = require('react');
|
||||
|
||||
function getSortDetails(sortKey, activeSort) {
|
||||
let newSort = { sortKey, order: 'desc' };
|
||||
let sortClass = '';
|
||||
if (activeSort && activeSort.sortKey === sortKey) {
|
||||
sortClass = 'sorted';
|
||||
if (activeSort.order === 'desc') {
|
||||
sortClass += '-desc';
|
||||
newSort.order = 'asc';
|
||||
} else {
|
||||
if (sortKey !== 'file') {
|
||||
newSort = { sortKey: 'file', order: 'desc' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
newSort,
|
||||
sortClass
|
||||
};
|
||||
}
|
||||
|
||||
function SummaryTableHeaderCell({ name, onSort, sortKey, activeSort }) {
|
||||
const { newSort, sortClass } = getSortDetails(sortKey, activeSort);
|
||||
return (
|
||||
<th
|
||||
className={'sortable headercell ' + sortClass}
|
||||
onClick={() => onSort(newSort)}
|
||||
>
|
||||
{name}
|
||||
<span className="sorter" />
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
function FileHeaderCell({ onSort, activeSort }) {
|
||||
const { newSort, sortClass } = getSortDetails('file', activeSort);
|
||||
|
||||
return (
|
||||
<th
|
||||
className={'sortable file ' + sortClass}
|
||||
onClick={() => onSort(newSort)}
|
||||
>
|
||||
File
|
||||
<span className="sorter" />
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
function SubHeadings({ sortKeyPrefix, onSort, activeSort }) {
|
||||
return (
|
||||
<>
|
||||
<SummaryTableHeaderCell
|
||||
name="%"
|
||||
onSort={onSort}
|
||||
sortKey={sortKeyPrefix + '.pct'}
|
||||
activeSort={activeSort}
|
||||
/>
|
||||
<th className="headercell"></th>
|
||||
<SummaryTableHeaderCell
|
||||
name="Covered"
|
||||
onSort={onSort}
|
||||
sortKey={sortKeyPrefix + '.covered'}
|
||||
activeSort={activeSort}
|
||||
/>
|
||||
<SummaryTableHeaderCell
|
||||
name="Missed"
|
||||
onSort={onSort}
|
||||
sortKey={sortKeyPrefix + '.missed'}
|
||||
activeSort={activeSort}
|
||||
/>
|
||||
<SummaryTableHeaderCell
|
||||
name="Total"
|
||||
onSort={onSort}
|
||||
sortKey={sortKeyPrefix + '.total'}
|
||||
activeSort={activeSort}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = function SummaryTableHeader({
|
||||
onSort,
|
||||
activeSort,
|
||||
metricsToShow
|
||||
}) {
|
||||
return (
|
||||
<thead>
|
||||
<tr className="topheading">
|
||||
<th></th>
|
||||
{metricsToShow.statements && <th colSpan={5}>Statements</th>}
|
||||
{metricsToShow.branches && <th colSpan={5}>Branches</th>}
|
||||
{metricsToShow.functions && <th colSpan={5}>Functions</th>}
|
||||
{metricsToShow.lines && <th colSpan={5}>Lines</th>}
|
||||
</tr>
|
||||
<tr className="subheading">
|
||||
<FileHeaderCell onSort={onSort} activeSort={activeSort} />
|
||||
{metricsToShow.statements && (
|
||||
<SubHeadings
|
||||
sortKeyPrefix="statements"
|
||||
onSort={onSort}
|
||||
activeSort={activeSort}
|
||||
/>
|
||||
)}
|
||||
{metricsToShow.branches && (
|
||||
<SubHeadings
|
||||
sortKeyPrefix="branches"
|
||||
onSort={onSort}
|
||||
activeSort={activeSort}
|
||||
/>
|
||||
)}
|
||||
{metricsToShow.functions && (
|
||||
<SubHeadings
|
||||
sortKeyPrefix="functions"
|
||||
onSort={onSort}
|
||||
activeSort={activeSort}
|
||||
/>
|
||||
)}
|
||||
{metricsToShow.lines && (
|
||||
<SubHeadings
|
||||
sortKeyPrefix="lines"
|
||||
onSort={onSort}
|
||||
activeSort={activeSort}
|
||||
/>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
);
|
||||
};
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
const React = require('react');
|
||||
|
||||
function MetricCells({ metrics }) {
|
||||
const { classForPercent, pct, covered, missed, total } = metrics;
|
||||
|
||||
return (
|
||||
<>
|
||||
<td className={'pct ' + classForPercent}>{Math.round(pct)}% </td>
|
||||
<td className={classForPercent}>
|
||||
<div className="bar">
|
||||
<div
|
||||
className={`bar__data ${classForPercent} ${classForPercent}--dark`}
|
||||
style={{ width: pct + '%' }}
|
||||
></div>
|
||||
</div>
|
||||
</td>
|
||||
<td className={'abs ' + classForPercent}>{covered}</td>
|
||||
<td className={'abs ' + classForPercent}>{missed}</td>
|
||||
<td className={'abs ' + classForPercent}>{total}</td>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FileCell({
|
||||
file,
|
||||
prefix,
|
||||
expandedLines,
|
||||
setExpandedLines,
|
||||
hasChildren,
|
||||
setFileFilter
|
||||
}) {
|
||||
if (hasChildren) {
|
||||
const expandedIndex = expandedLines.indexOf(prefix + file);
|
||||
const isExpanded = expandedIndex >= 0;
|
||||
const newExpandedLines = isExpanded
|
||||
? [
|
||||
...expandedLines.slice(0, expandedIndex),
|
||||
...expandedLines.slice(expandedIndex + 1)
|
||||
]
|
||||
: [...expandedLines, prefix + file];
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setExpandedLines(newExpandedLines)}
|
||||
className="expandbutton"
|
||||
>
|
||||
{isExpanded ? String.fromCharCode(0x2013) : '+'}
|
||||
</button>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
onClick={() => setFileFilter(prefix + file)}
|
||||
>
|
||||
{file}
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return <a href={`./${prefix}${file}.html`}>{file}</a>;
|
||||
}
|
||||
}
|
||||
|
||||
function getWorstMetricClassForPercent(metricsToShow, metrics) {
|
||||
let classForPercent = 'none';
|
||||
for (const metricToShow in metricsToShow) {
|
||||
if (metricsToShow[metricToShow]) {
|
||||
const metricClassForPercent = metrics[metricToShow].classForPercent;
|
||||
|
||||
// ignore none metrics so they don't change whats shown
|
||||
if (metricClassForPercent === 'none') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if the metric low or lower than whats currently being used, replace it
|
||||
if (
|
||||
metricClassForPercent == 'low' ||
|
||||
(metricClassForPercent === 'medium' &&
|
||||
classForPercent !== 'low') ||
|
||||
(metricClassForPercent === 'high' &&
|
||||
classForPercent !== 'low' &&
|
||||
classForPercent !== 'medium')
|
||||
) {
|
||||
classForPercent = metricClassForPercent;
|
||||
}
|
||||
}
|
||||
}
|
||||
return classForPercent;
|
||||
}
|
||||
|
||||
module.exports = function SummaryTableLine({
|
||||
prefix,
|
||||
metrics,
|
||||
file,
|
||||
children,
|
||||
tabSize,
|
||||
metricsToShow,
|
||||
expandedLines,
|
||||
setExpandedLines,
|
||||
fileFilter,
|
||||
setFileFilter
|
||||
}) {
|
||||
tabSize = tabSize || 0;
|
||||
if (children && tabSize > 0) {
|
||||
tabSize--;
|
||||
}
|
||||
prefix = (fileFilter ? fileFilter + '/' : '') + (prefix || '');
|
||||
|
||||
return (
|
||||
<>
|
||||
<tr>
|
||||
<td
|
||||
className={
|
||||
'file ' +
|
||||
getWorstMetricClassForPercent(metricsToShow, metrics)
|
||||
}
|
||||
>
|
||||
{/* eslint-disable-line prefer-spread */ Array.apply(null, {
|
||||
length: tabSize
|
||||
}).map((nothing, index) => (
|
||||
<span className="filetab" key={index} />
|
||||
))}
|
||||
<FileCell
|
||||
file={file}
|
||||
prefix={prefix}
|
||||
expandedLines={expandedLines}
|
||||
setExpandedLines={setExpandedLines}
|
||||
hasChildren={Boolean(children)}
|
||||
setFileFilter={setFileFilter}
|
||||
/>
|
||||
</td>
|
||||
{metricsToShow.statements && (
|
||||
<MetricCells metrics={metrics.statements} />
|
||||
)}
|
||||
{metricsToShow.branches && (
|
||||
<MetricCells metrics={metrics.branches} />
|
||||
)}
|
||||
{metricsToShow.functions && (
|
||||
<MetricCells metrics={metrics.functions} />
|
||||
)}
|
||||
{metricsToShow.lines && <MetricCells metrics={metrics.lines} />}
|
||||
</tr>
|
||||
{children &&
|
||||
expandedLines.indexOf(prefix + file) >= 0 &&
|
||||
children.map(child => (
|
||||
<SummaryTableLine
|
||||
{...child}
|
||||
tabSize={tabSize + 2}
|
||||
key={child.file}
|
||||
prefix={prefix + file + '/'}
|
||||
metricsToShow={metricsToShow}
|
||||
expandedLines={expandedLines}
|
||||
setExpandedLines={setExpandedLines}
|
||||
setFileFilter={setFileFilter}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: path.resolve(__dirname, 'src/index.js'),
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'assets'),
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
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, '&')
|
||||
.replace(RE_LT, '<')
|
||||
.replace(RE_GT, '>')
|
||||
.replace(RE_lt, '<')
|
||||
.replace(RE_gt, '>');
|
||||
}
|
||||
|
||||
function annotateLines(fileCoverage, structuredText) {
|
||||
const lineStats = fileCoverage.getLineCoverage();
|
||||
if (!lineStats) {
|
||||
return;
|
||||
}
|
||||
Object.entries(lineStats).forEach(([lineNumber, count]) => {
|
||||
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.entries(statementStats).forEach(([stName, count]) => {
|
||||
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.entries(fnStats).forEach(([fName, count]) => {
|
||||
const meta = fnMeta[fName];
|
||||
const type = count > 0 ? 'yes' : 'no';
|
||||
// Some versions of the instrumenter in the wild populate 'func'
|
||||
// but not 'decl':
|
||||
const decl = meta.decl || meta.loc;
|
||||
const startCol = decl.start.column;
|
||||
let endCol = decl.end.column + 1;
|
||||
const startLine = decl.start.line;
|
||||
const endLine = 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.entries(branchStats).forEach(([branchName, branchArray]) => {
|
||||
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)) {
|
||||
// Need to recover the metaArray placeholder item to count an implicit else
|
||||
if (
|
||||
// Check if the branch is a conditional if branch.
|
||||
branchMeta[branchName].type === 'if' &&
|
||||
// Check if the branch has an implicit else.
|
||||
branchArray.length === 2 &&
|
||||
// Check if the implicit else branch is unaccounted for.
|
||||
metaArray.length === 1 &&
|
||||
// Check if the implicit else branch is uncovered.
|
||||
branchArray[1] === 0
|
||||
) {
|
||||
metaArray[1] = {
|
||||
start: {},
|
||||
end: {}
|
||||
};
|
||||
}
|
||||
|
||||
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 the branch is an implicit else from an if statement,
|
||||
// then the coverage report won't show a statistic.
|
||||
// Therefore, the previous branch will be used to report that
|
||||
// there is no coverage on that implicit branch.
|
||||
if (
|
||||
count === 0 &&
|
||||
startLine === undefined &&
|
||||
branchMeta[branchName].type === 'if'
|
||||
) {
|
||||
const prevMeta = metaArray[i - 1];
|
||||
startCol = prevMeta.start.column;
|
||||
endCol = prevMeta.end.column + 1;
|
||||
startLine = prevMeta.start.line;
|
||||
endLine = prevMeta.end.line;
|
||||
}
|
||||
|
||||
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 nonexistent
|
||||
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()) || ' '
|
||||
);
|
||||
|
||||
lineCoverageArray = structured.map(item => ({
|
||||
covered: item.covered,
|
||||
hits: item.hits > 0 ? item.hits + 'x' : ' '
|
||||
}));
|
||||
|
||||
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
@@ -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;
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
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) > `
|
||||
|
||||
// Selector 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) {
|
||||
if (
|
||||
document.getElementById('fileSearch') === document.activeElement &&
|
||||
document.activeElement != null
|
||||
) {
|
||||
// if we're currently focused on the search input, we don't want to navigate
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 445 B |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 138 B |
+209
@@ -0,0 +1,209 @@
|
||||
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];
|
||||
}
|
||||
|
||||
function onFilterInput() {
|
||||
const searchValue = document.getElementById('fileSearch').value;
|
||||
const rows = document.getElementsByTagName('tbody')[0].children;
|
||||
|
||||
// Try to create a RegExp from the searchValue. If it fails (invalid regex),
|
||||
// it will be treated as a plain text search
|
||||
let searchRegex;
|
||||
try {
|
||||
searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive
|
||||
} catch (error) {
|
||||
searchRegex = null;
|
||||
}
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const row = rows[i];
|
||||
let isMatch = false;
|
||||
|
||||
if (searchRegex) {
|
||||
// If a valid regex was created, use it for matching
|
||||
isMatch = searchRegex.test(row.textContent);
|
||||
} else {
|
||||
// Otherwise, fall back to the original plain text search
|
||||
isMatch = row.textContent
|
||||
.toLowerCase()
|
||||
.includes(searchValue.toLowerCase());
|
||||
}
|
||||
|
||||
row.style.display = isMatch ? '' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// loads the search box
|
||||
function addSearchBox() {
|
||||
var template = document.getElementById('filterTemplate');
|
||||
var templateClone = template.content.cloneNode(true);
|
||||
templateClone.getElementById('fileSearch').oninput = onFilterInput;
|
||||
template.parentElement.appendChild(templateClone);
|
||||
}
|
||||
|
||||
// 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();
|
||||
addSearchBox();
|
||||
addSortIndicators();
|
||||
enableUI();
|
||||
};
|
||||
})();
|
||||
|
||||
window.addEventListener('load', addSorting);
|
||||
+1
@@ -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}
|
||||
+1
@@ -0,0 +1 @@
|
||||
window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V<U;++V){var ae=Z[V];if(ae.ignoreCase){ac=true}else{if(/[a-z]/i.test(ae.source.replace(/\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi,""))){S=true;ac=false;break}}}var Y={b:8,t:9,n:10,v:11,f:12,r:13};function ab(ah){var ag=ah.charCodeAt(0);if(ag!==92){return ag}var af=ah.charAt(1);ag=Y[af];if(ag){return ag}else{if("0"<=af&&af<="7"){return parseInt(ah.substring(1),8)}else{if(af==="u"||af==="x"){return parseInt(ah.substring(2),16)}else{return ah.charCodeAt(1)}}}}function T(af){if(af<32){return(af<16?"\\x0":"\\x")+af.toString(16)}var ag=String.fromCharCode(af);if(ag==="\\"||ag==="-"||ag==="["||ag==="]"){ag="\\"+ag}return ag}function X(am){var aq=am.substring(1,am.length-1).match(new RegExp("\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]","g"));var ak=[];var af=[];var ao=aq[0]==="^";for(var ar=ao?1:0,aj=aq.length;ar<aj;++ar){var ah=aq[ar];if(/\\[bdsw]/i.test(ah)){ak.push(ah)}else{var ag=ab(ah);var al;if(ar+2<aj&&"-"===aq[ar+1]){al=ab(aq[ar+2]);ar+=2}else{al=ag}af.push([ag,al]);if(!(al<65||ag>122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;ar<af.length;++ar){var at=af[ar];if(at[0]<=ap[1]+1){ap[1]=Math.max(ap[1],at[1])}else{ai.push(ap=at)}}var an=["["];if(ao){an.push("^")}an.push.apply(an,ak);for(var ar=0;ar<ai.length;++ar){var at=ai[ar];an.push(T(at[0]));if(at[1]>at[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak<ah;++ak){var ag=aj[ak];if(ag==="("){++am}else{if("\\"===ag.charAt(0)){var af=+ag.substring(1);if(af&&af<=am){an[af]=-1}}}}for(var ak=1;ak<an.length;++ak){if(-1===an[ak]){an[ak]=++ad}}for(var ak=0,am=0;ak<ah;++ak){var ag=aj[ak];if(ag==="("){++am;if(an[am]===undefined){aj[ak]="(?:"}}else{if("\\"===ag.charAt(0)){var af=+ag.substring(1);if(af&&af<=am){aj[ak]="\\"+an[am]}}}}for(var ak=0,am=0;ak<ah;++ak){if("^"===aj[ak]&&"^"!==aj[ak+1]){aj[ak]=""}}if(al.ignoreCase&&S){for(var ak=0;ak<ah;++ak){var ag=aj[ak];var ai=ag.charAt(0);Line truncated
|
||||
+421
@@ -0,0 +1,421 @@
|
||||
'use strict';
|
||||
/*
|
||||
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 html = require('html-escaper');
|
||||
const { ReportBase } = require('istanbul-lib-report');
|
||||
const annotator = require('./annotator');
|
||||
|
||||
function htmlHead(details) {
|
||||
return `
|
||||
<head>
|
||||
<title>Code coverage report for ${html.escape(details.entity)}</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="${html.escape(details.prettify.css)}" />
|
||||
<link rel="stylesheet" href="${html.escape(details.base.css)}" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="${html.escape(
|
||||
details.favicon
|
||||
)}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(${html.escape(details.sorter.image)});
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
`;
|
||||
}
|
||||
|
||||
function headerTemplate(details) {
|
||||
function metricsTemplate({ pct, covered, total }, kind) {
|
||||
return `
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">${pct}% </span>
|
||||
<span class="quiet">${kind}</span>
|
||||
<span class='fraction'>${covered}/${total}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function skipTemplate(metrics) {
|
||||
const statements = metrics.statements.skipped;
|
||||
const branches = metrics.branches.skipped;
|
||||
const functions = metrics.functions.skipped;
|
||||
|
||||
const countLabel = (c, label, plural) =>
|
||||
c === 0 ? [] : `${c} ${label}${c === 1 ? '' : plural}`;
|
||||
const skips = [].concat(
|
||||
countLabel(statements, 'statement', 's'),
|
||||
countLabel(functions, 'function', 's'),
|
||||
countLabel(branches, 'branch', 'es')
|
||||
);
|
||||
|
||||
if (skips.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `
|
||||
<div class='fl pad1y'>
|
||||
<span class="strong">${skips.join(', ')}</span>
|
||||
<span class="quiet">Ignored</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
${htmlHead(details)}
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1>${details.pathHtml}</h1>
|
||||
<div class='clearfix'>
|
||||
${metricsTemplate(details.metrics.statements, 'Statements')}
|
||||
${metricsTemplate(details.metrics.branches, 'Branches')}
|
||||
${metricsTemplate(details.metrics.functions, 'Functions')}
|
||||
${metricsTemplate(details.metrics.lines, 'Lines')}
|
||||
${skipTemplate(details.metrics)}
|
||||
</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>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line ${details.reportClass}'></div>
|
||||
`;
|
||||
}
|
||||
|
||||
function footerTemplate(details) {
|
||||
return `
|
||||
<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" rel="noopener noreferrer">istanbul</a>
|
||||
at ${html.escape(details.datetime)}
|
||||
</div>
|
||||
<script src="${html.escape(details.prettify.js)}"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="${html.escape(details.sorter.js)}"></script>
|
||||
<script src="${html.escape(details.blockNavigation.js)}"></script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
function detailTemplate(data) {
|
||||
const lineNumbers = new Array(data.maxLines).fill().map((_, i) => i + 1);
|
||||
const lineLink = num =>
|
||||
`<a name='L${num}'></a><a href='#L${num}'>${num}</a>`;
|
||||
const lineCount = line =>
|
||||
`<span class="cline-any cline-${line.covered}">${line.hits}</span>`;
|
||||
|
||||
/* This is rendered in a `<pre>`, need control of all whitespace. */
|
||||
return [
|
||||
'<tr>',
|
||||
`<td class="line-count quiet">${lineNumbers
|
||||
.map(lineLink)
|
||||
.join('\n')}</td>`,
|
||||
`<td class="line-coverage quiet">${data.lineCoverage
|
||||
.map(lineCount)
|
||||
.join('\n')}</td>`,
|
||||
`<td class="text"><pre class="prettyprint lang-js">${data.annotatedCode.join(
|
||||
'\n'
|
||||
)}</pre></td>`,
|
||||
'</tr>'
|
||||
].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');
|
||||
|
||||
function summaryLineTemplate(details) {
|
||||
const { reportClasses, metrics, file, output } = details;
|
||||
const percentGraph = pct => {
|
||||
if (!isFinite(pct)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const cls = ['cover-fill'];
|
||||
if (pct === 100) {
|
||||
cls.push('cover-full');
|
||||
}
|
||||
|
||||
pct = Math.floor(pct);
|
||||
return [
|
||||
`<div class="${cls.join(' ')}" style="width: ${pct}%"></div>`,
|
||||
`<div class="cover-empty" style="width: ${100 - pct}%"></div>`
|
||||
].join('');
|
||||
};
|
||||
const summaryType = (type, showGraph = false) => {
|
||||
const info = metrics[type];
|
||||
const reportClass = reportClasses[type];
|
||||
const result = [
|
||||
`<td data-value="${info.pct}" class="pct ${reportClass}">${info.pct}%</td>`,
|
||||
`<td data-value="${info.total}" class="abs ${reportClass}">${info.covered}/${info.total}</td>`
|
||||
];
|
||||
if (showGraph) {
|
||||
result.unshift(
|
||||
`<td data-value="${info.pct}" class="pic ${reportClass}">`,
|
||||
`<div class="chart">${percentGraph(info.pct)}</div>`,
|
||||
`</td>`
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return []
|
||||
.concat(
|
||||
'<tr>',
|
||||
`<td class="file ${
|
||||
reportClasses.statements
|
||||
}" data-value="${html.escape(file)}"><a href="${html.escape(
|
||||
output
|
||||
)}">${html.escape(file)}</a></td>`,
|
||||
summaryType('statements', true),
|
||||
summaryType('branches'),
|
||||
summaryType('functions'),
|
||||
summaryType('lines'),
|
||||
'</tr>\n'
|
||||
)
|
||||
.join('\n\t');
|
||||
}
|
||||
|
||||
const summaryTableFooter = ['</tbody>', '</table>', '</div>'].join('\n');
|
||||
const emptyClasses = {
|
||||
statements: 'empty',
|
||||
lines: 'empty',
|
||||
functions: 'empty',
|
||||
branches: 'empty'
|
||||
};
|
||||
|
||||
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.posix.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 extends ReportBase {
|
||||
constructor(opts) {
|
||||
super();
|
||||
|
||||
this.verbose = opts.verbose;
|
||||
this.linkMapper = opts.linkMapper || standardLinkMapper;
|
||||
this.subdir = opts.subdir || '';
|
||||
this.date = new Date().toISOString();
|
||||
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')
|
||||
};
|
||||
templateData.favicon = linkMapper.assetPath(node, 'favicon.png');
|
||||
}
|
||||
|
||||
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(node.getFileCoverage(), context)));
|
||||
cw.write('</table></pre>\n');
|
||||
cw.write(footerTemplate(templateData));
|
||||
cw.close();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HtmlReport;
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
'use strict';
|
||||
/*
|
||||
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;
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
Copyright 2012-2015, Yahoo Inc.
|
||||
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
|
||||
*/
|
||||
'use strict';
|
||||
const { ReportBase } = require('istanbul-lib-report');
|
||||
|
||||
class JsonSummaryReport extends ReportBase {
|
||||
constructor(opts) {
|
||||
super();
|
||||
|
||||
this.file = opts.file || 'coverage-summary.json';
|
||||
this.contentWriter = null;
|
||||
this.first = true;
|
||||
}
|
||||
|
||||
onStart(root, context) {
|
||||
this.contentWriter = context.writer.writeFile(this.file);
|
||||
this.contentWriter.write('{');
|
||||
}
|
||||
|
||||
writeSummary(filePath, sc) {
|
||||
const cw = this.contentWriter;
|
||||
if (this.first) {
|
||||
this.first = false;
|
||||
} else {
|
||||
cw.write(',');
|
||||
}
|
||||
cw.write(JSON.stringify(filePath));
|
||||
cw.write(': ');
|
||||
cw.write(JSON.stringify(sc));
|
||||
cw.println('');
|
||||
}
|
||||
|
||||
onSummary(node) {
|
||||
if (!node.isRoot()) {
|
||||
return;
|
||||
}
|
||||
this.writeSummary('total', node.getCoverageSummary());
|
||||
}
|
||||
|
||||
onDetail(node) {
|
||||
this.writeSummary(
|
||||
node.getFileCoverage().path,
|
||||
node.getCoverageSummary()
|
||||
);
|
||||
}
|
||||
|
||||
onEnd() {
|
||||
const cw = this.contentWriter;
|
||||
cw.println('}');
|
||||
cw.close();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JsonSummaryReport;
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Copyright 2012-2015, Yahoo Inc.
|
||||
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
|
||||
*/
|
||||
'use strict';
|
||||
const { ReportBase } = require('istanbul-lib-report');
|
||||
|
||||
class JsonReport extends ReportBase {
|
||||
constructor(opts) {
|
||||
super();
|
||||
|
||||
this.file = opts.file || 'coverage-final.json';
|
||||
this.first = true;
|
||||
}
|
||||
|
||||
onStart(root, context) {
|
||||
this.contentWriter = context.writer.writeFile(this.file);
|
||||
this.contentWriter.write('{');
|
||||
}
|
||||
|
||||
onDetail(node) {
|
||||
const fc = node.getFileCoverage();
|
||||
const key = fc.path;
|
||||
const cw = this.contentWriter;
|
||||
|
||||
if (this.first) {
|
||||
this.first = false;
|
||||
} else {
|
||||
cw.write(',');
|
||||
}
|
||||
cw.write(JSON.stringify(key));
|
||||
cw.write(': ');
|
||||
cw.write(JSON.stringify(fc));
|
||||
cw.println('');
|
||||
}
|
||||
|
||||
onEnd() {
|
||||
const cw = this.contentWriter;
|
||||
cw.println('}');
|
||||
cw.close();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JsonReport;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
/*
|
||||
Copyright 2012-2015, Yahoo Inc.
|
||||
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
|
||||
*/
|
||||
const { ReportBase } = require('istanbul-lib-report');
|
||||
const LcovOnlyReport = require('../lcovonly');
|
||||
const HtmlReport = require('../html');
|
||||
|
||||
class LcovReport extends ReportBase {
|
||||
constructor(opts) {
|
||||
super();
|
||||
this.lcov = new LcovOnlyReport({ file: 'lcov.info', ...opts });
|
||||
this.html = new HtmlReport({ subdir: 'lcov-report' });
|
||||
}
|
||||
}
|
||||
|
||||
['Start', 'End', 'Summary', 'SummaryEnd', 'Detail'].forEach(what => {
|
||||
const meth = 'on' + what;
|
||||
LcovReport.prototype[meth] = function(...args) {
|
||||
const lcov = this.lcov;
|
||||
const html = this.html;
|
||||
|
||||
if (lcov[meth]) {
|
||||
lcov[meth](...args);
|
||||
}
|
||||
if (html[meth]) {
|
||||
html[meth](...args);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.exports = LcovReport;
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
Copyright 2012-2015, Yahoo Inc.
|
||||
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
|
||||
*/
|
||||
'use strict';
|
||||
const { ReportBase } = require('istanbul-lib-report');
|
||||
|
||||
class LcovOnlyReport extends ReportBase {
|
||||
constructor(opts) {
|
||||
super();
|
||||
opts = opts || {};
|
||||
this.file = opts.file || 'lcov.info';
|
||||
this.projectRoot = opts.projectRoot || process.cwd();
|
||||
this.contentWriter = null;
|
||||
}
|
||||
|
||||
onStart(root, context) {
|
||||
this.contentWriter = context.writer.writeFile(this.file);
|
||||
}
|
||||
|
||||
onDetail(node) {
|
||||
const fc = node.getFileCoverage();
|
||||
const writer = this.contentWriter;
|
||||
const functions = fc.f;
|
||||
const functionMap = fc.fnMap;
|
||||
const lines = fc.getLineCoverage();
|
||||
const branches = fc.b;
|
||||
const branchMap = fc.branchMap;
|
||||
const summary = node.getCoverageSummary();
|
||||
const path = require('path');
|
||||
|
||||
writer.println('TN:');
|
||||
const fileName = path.relative(this.projectRoot, fc.path);
|
||||
writer.println('SF:' + fileName);
|
||||
|
||||
Object.values(functionMap).forEach(meta => {
|
||||
// Some versions of the instrumenter in the wild populate 'loc'
|
||||
// but not 'decl':
|
||||
const decl = meta.decl || meta.loc;
|
||||
writer.println('FN:' + [decl.start.line, meta.name].join(','));
|
||||
});
|
||||
writer.println('FNF:' + summary.functions.total);
|
||||
writer.println('FNH:' + summary.functions.covered);
|
||||
|
||||
Object.entries(functionMap).forEach(([key, meta]) => {
|
||||
const stats = functions[key];
|
||||
writer.println('FNDA:' + [stats, meta.name].join(','));
|
||||
});
|
||||
|
||||
Object.entries(lines).forEach(entry => {
|
||||
writer.println('DA:' + entry.join(','));
|
||||
});
|
||||
writer.println('LF:' + summary.lines.total);
|
||||
writer.println('LH:' + summary.lines.covered);
|
||||
|
||||
Object.entries(branches).forEach(([key, branchArray]) => {
|
||||
const meta = branchMap[key];
|
||||
if (meta) {
|
||||
const { line } = meta.loc.start;
|
||||
branchArray.forEach((b, i) => {
|
||||
writer.println('BRDA:' + [line, key, i, b].join(','));
|
||||
});
|
||||
} else {
|
||||
console.warn('Missing coverage entries in', fileName, key);
|
||||
}
|
||||
});
|
||||
writer.println('BRF:' + summary.branches.total);
|
||||
writer.println('BRH:' + summary.branches.covered);
|
||||
writer.println('end_of_record');
|
||||
}
|
||||
|
||||
onEnd() {
|
||||
this.contentWriter.close();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LcovOnlyReport;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
/*
|
||||
Copyright 2012-2015, Yahoo Inc.
|
||||
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
|
||||
*/
|
||||
const { ReportBase } = require('istanbul-lib-report');
|
||||
|
||||
class NoneReport extends ReportBase {}
|
||||
|
||||
module.exports = NoneReport;
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Copyright 2012-2015, Yahoo Inc.
|
||||
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
|
||||
*/
|
||||
'use strict';
|
||||
const { ReportBase } = require('istanbul-lib-report');
|
||||
|
||||
class TeamcityReport extends ReportBase {
|
||||
constructor(opts) {
|
||||
super();
|
||||
|
||||
opts = opts || {};
|
||||
this.file = opts.file || null;
|
||||
this.blockName = opts.blockName || 'Code Coverage Summary';
|
||||
}
|
||||
|
||||
onStart(node, context) {
|
||||
const metrics = node.getCoverageSummary();
|
||||
const cw = context.writer.writeFile(this.file);
|
||||
|
||||
cw.println('');
|
||||
cw.println("##teamcity[blockOpened name='" + this.blockName + "']");
|
||||
|
||||
//Statements Covered
|
||||
cw.println(
|
||||
lineForKey(metrics.statements.covered, 'CodeCoverageAbsBCovered')
|
||||
);
|
||||
cw.println(
|
||||
lineForKey(metrics.statements.total, 'CodeCoverageAbsBTotal')
|
||||
);
|
||||
|
||||
//Branches Covered
|
||||
cw.println(
|
||||
lineForKey(metrics.branches.covered, 'CodeCoverageAbsRCovered')
|
||||
);
|
||||
cw.println(lineForKey(metrics.branches.total, 'CodeCoverageAbsRTotal'));
|
||||
|
||||
//Functions Covered
|
||||
cw.println(
|
||||
lineForKey(metrics.functions.covered, 'CodeCoverageAbsMCovered')
|
||||
);
|
||||
cw.println(
|
||||
lineForKey(metrics.functions.total, 'CodeCoverageAbsMTotal')
|
||||
);
|
||||
|
||||
//Lines Covered
|
||||
cw.println(
|
||||
lineForKey(metrics.lines.covered, 'CodeCoverageAbsLCovered')
|
||||
);
|
||||
cw.println(lineForKey(metrics.lines.total, 'CodeCoverageAbsLTotal'));
|
||||
|
||||
cw.println("##teamcity[blockClosed name='" + this.blockName + "']");
|
||||
cw.close();
|
||||
}
|
||||
}
|
||||
|
||||
function lineForKey(value, teamcityVar) {
|
||||
return (
|
||||
"##teamcity[buildStatisticValue key='" +
|
||||
teamcityVar +
|
||||
"' value='" +
|
||||
value +
|
||||
"']"
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = TeamcityReport;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
/*
|
||||
Copyright 2012-2015, Yahoo Inc.
|
||||
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
|
||||
*/
|
||||
const LcovOnly = require('../lcovonly');
|
||||
|
||||
class TextLcov extends LcovOnly {
|
||||
constructor(opts) {
|
||||
super({
|
||||
...opts,
|
||||
file: '-'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TextLcov;
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2012-2015, Yahoo Inc.
|
||||
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
|
||||
*/
|
||||
'use strict';
|
||||
const { ReportBase } = require('istanbul-lib-report');
|
||||
|
||||
class TextSummaryReport extends ReportBase {
|
||||
constructor(opts) {
|
||||
super();
|
||||
|
||||
opts = opts || {};
|
||||
this.file = opts.file || null;
|
||||
}
|
||||
|
||||
onStart(node, context) {
|
||||
const summary = node.getCoverageSummary();
|
||||
const cw = context.writer.writeFile(this.file);
|
||||
const printLine = function(key) {
|
||||
const str = lineForKey(summary, key);
|
||||
const clazz = context.classForPercent(key, summary[key].pct);
|
||||
cw.println(cw.colorize(str, clazz));
|
||||
};
|
||||
|
||||
cw.println('');
|
||||
cw.println(
|
||||
'=============================== Coverage summary ==============================='
|
||||
);
|
||||
printLine('statements');
|
||||
printLine('branches');
|
||||
printLine('functions');
|
||||
printLine('lines');
|
||||
cw.println(
|
||||
'================================================================================'
|
||||
);
|
||||
cw.close();
|
||||
}
|
||||
}
|
||||
|
||||
function lineForKey(summary, key) {
|
||||
const metrics = summary[key];
|
||||
|
||||
key = key.substring(0, 1).toUpperCase() + key.substring(1);
|
||||
if (key.length < 12) {
|
||||
key += ' '.substring(0, 12 - key.length);
|
||||
}
|
||||
const result = [
|
||||
key,
|
||||
':',
|
||||
metrics.pct + '%',
|
||||
'(',
|
||||
metrics.covered + '/' + metrics.total,
|
||||
')'
|
||||
].join(' ');
|
||||
const skipped = metrics.skipped;
|
||||
if (skipped > 0) {
|
||||
return result + ', ' + skipped + ' ignored';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = TextSummaryReport;
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
Copyright 2012-2015, Yahoo Inc.
|
||||
Copyrights licensed under the New BSD License. See the accompanying LICENSE
|
||||
file for terms.
|
||||
*/
|
||||
'use strict';
|
||||
const { ReportBase } = require('istanbul-lib-report');
|
||||
|
||||
const NAME_COL = 4;
|
||||
const PCT_COLS = 7;
|
||||
const MISSING_COL = 17;
|
||||
const TAB_SIZE = 1;
|
||||
const DELIM = ' | ';
|
||||
|
||||
function padding(num, ch) {
|
||||
let str = '';
|
||||
let i;
|
||||
ch = ch || ' ';
|
||||
for (i = 0; i < num; i += 1) {
|
||||
str += ch;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function fill(str, width, right, tabs) {
|
||||
tabs = tabs || 0;
|
||||
str = String(str);
|
||||
|
||||
const leadingSpaces = tabs * TAB_SIZE;
|
||||
const remaining = width - leadingSpaces;
|
||||
const leader = padding(leadingSpaces);
|
||||
let fmtStr = '';
|
||||
|
||||
if (remaining > 0) {
|
||||
const strlen = str.length;
|
||||
let fillStr;
|
||||
|
||||
if (remaining >= strlen) {
|
||||
fillStr = padding(remaining - strlen);
|
||||
} else {
|
||||
fillStr = '...';
|
||||
const length = remaining - fillStr.length;
|
||||
|
||||
str = str.substring(strlen - length);
|
||||
right = true;
|
||||
}
|
||||
fmtStr = right ? fillStr + str : str + fillStr;
|
||||
}
|
||||
|
||||
return leader + fmtStr;
|
||||
}
|
||||
|
||||
function formatName(name, maxCols, level) {
|
||||
return fill(name, maxCols, false, level);
|
||||
}
|
||||
|
||||
function formatPct(pct, width) {
|
||||
return fill(pct, width || PCT_COLS, true, 0);
|
||||
}
|
||||
|
||||
function nodeMissing(node) {
|
||||
if (node.isSummary()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const metrics = node.getCoverageSummary();
|
||||
const isEmpty = metrics.isEmpty();
|
||||
const lines = isEmpty ? 0 : metrics.lines.pct;
|
||||
|
||||
let coveredLines;
|
||||
|
||||
const fileCoverage = node.getFileCoverage();
|
||||
if (lines === 100) {
|
||||
const branches = fileCoverage.getBranchCoverageByLine();
|
||||
coveredLines = Object.entries(branches).map(([key, { coverage }]) => [
|
||||
key,
|
||||
coverage === 100
|
||||
]);
|
||||
} else {
|
||||
coveredLines = Object.entries(fileCoverage.getLineCoverage());
|
||||
}
|
||||
|
||||
let newRange = true;
|
||||
const ranges = coveredLines
|
||||
.reduce((acum, [line, hit]) => {
|
||||
if (hit) newRange = true;
|
||||
else {
|
||||
line = parseInt(line);
|
||||
if (newRange) {
|
||||
acum.push([line]);
|
||||
newRange = false;
|
||||
} else acum[acum.length - 1][1] = line;
|
||||
}
|
||||
|
||||
return acum;
|
||||
}, [])
|
||||
.map(range => {
|
||||
const { length } = range;
|
||||
|
||||
if (length === 1) return range[0];
|
||||
|
||||
return `${range[0]}-${range[1]}`;
|
||||
});
|
||||
|
||||
return [].concat(...ranges).join(',');
|
||||
}
|
||||
|
||||
function nodeName(node) {
|
||||
return node.getRelativeName() || 'All files';
|
||||
}
|
||||
|
||||
function depthFor(node) {
|
||||
let ret = 0;
|
||||
node = node.getParent();
|
||||
while (node) {
|
||||
ret += 1;
|
||||
node = node.getParent();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function nullDepthFor() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
function findWidth(node, context, nodeExtractor, depthFor = nullDepthFor) {
|
||||
let last = 0;
|
||||
function compareWidth(node) {
|
||||
last = Math.max(
|
||||
last,
|
||||
TAB_SIZE * depthFor(node) + nodeExtractor(node).length
|
||||
);
|
||||
}
|
||||
const visitor = {
|
||||
onSummary: compareWidth,
|
||||
onDetail: compareWidth
|
||||
};
|
||||
node.visit(context.getVisitor(visitor));
|
||||
return last;
|
||||
}
|
||||
|
||||
function makeLine(nameWidth, missingWidth) {
|
||||
const name = padding(nameWidth, '-');
|
||||
const pct = padding(PCT_COLS, '-');
|
||||
const elements = [];
|
||||
|
||||
elements.push(name);
|
||||
elements.push(pct);
|
||||
elements.push(padding(PCT_COLS + 1, '-'));
|
||||
elements.push(pct);
|
||||
elements.push(pct);
|
||||
elements.push(padding(missingWidth, '-'));
|
||||
return elements.join(DELIM.replace(/ /g, '-')) + '-';
|
||||
}
|
||||
|
||||
function tableHeader(maxNameCols, missingWidth) {
|
||||
const elements = [];
|
||||
elements.push(formatName('File', maxNameCols, 0));
|
||||
elements.push(formatPct('% Stmts'));
|
||||
elements.push(formatPct('% Branch', PCT_COLS + 1));
|
||||
elements.push(formatPct('% Funcs'));
|
||||
elements.push(formatPct('% Lines'));
|
||||
elements.push(formatName('Uncovered Line #s', missingWidth));
|
||||
return elements.join(DELIM) + ' ';
|
||||
}
|
||||
|
||||
function isFull(metrics) {
|
||||
return (
|
||||
metrics.statements.pct === 100 &&
|
||||
metrics.branches.pct === 100 &&
|
||||
metrics.functions.pct === 100 &&
|
||||
metrics.lines.pct === 100
|
||||
);
|
||||
}
|
||||
|
||||
function tableRow(
|
||||
node,
|
||||
context,
|
||||
colorizer,
|
||||
maxNameCols,
|
||||
level,
|
||||
skipEmpty,
|
||||
skipFull,
|
||||
missingWidth
|
||||
) {
|
||||
const name = nodeName(node);
|
||||
const metrics = node.getCoverageSummary();
|
||||
const isEmpty = metrics.isEmpty();
|
||||
if (skipEmpty && isEmpty) {
|
||||
return '';
|
||||
}
|
||||
if (skipFull && isFull(metrics)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const mm = {
|
||||
statements: isEmpty ? 0 : metrics.statements.pct,
|
||||
branches: isEmpty ? 0 : metrics.branches.pct,
|
||||
functions: isEmpty ? 0 : metrics.functions.pct,
|
||||
lines: isEmpty ? 0 : metrics.lines.pct
|
||||
};
|
||||
const colorize = isEmpty
|
||||
? function(str) {
|
||||
return str;
|
||||
}
|
||||
: function(str, key) {
|
||||
return colorizer(str, context.classForPercent(key, mm[key]));
|
||||
};
|
||||
const elements = [];
|
||||
|
||||
elements.push(colorize(formatName(name, maxNameCols, level), 'statements'));
|
||||
elements.push(colorize(formatPct(mm.statements), 'statements'));
|
||||
elements.push(colorize(formatPct(mm.branches, PCT_COLS + 1), 'branches'));
|
||||
elements.push(colorize(formatPct(mm.functions), 'functions'));
|
||||
elements.push(colorize(formatPct(mm.lines), 'lines'));
|
||||
elements.push(
|
||||
colorizer(
|
||||
formatName(nodeMissing(node), missingWidth),
|
||||
mm.lines === 100 ? 'medium' : 'low'
|
||||
)
|
||||
);
|
||||
|
||||
return elements.join(DELIM) + ' ';
|
||||
}
|
||||
|
||||
class TextReport extends ReportBase {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
|
||||
opts = opts || {};
|
||||
const { maxCols } = opts;
|
||||
|
||||
this.file = opts.file || null;
|
||||
this.maxCols = maxCols != null ? maxCols : process.stdout.columns || 80;
|
||||
this.cw = null;
|
||||
this.skipEmpty = opts.skipEmpty;
|
||||
this.skipFull = opts.skipFull;
|
||||
}
|
||||
|
||||
onStart(root, context) {
|
||||
this.cw = context.writer.writeFile(this.file);
|
||||
this.nameWidth = Math.max(
|
||||
NAME_COL,
|
||||
findWidth(root, context, nodeName, depthFor)
|
||||
);
|
||||
this.missingWidth = Math.max(
|
||||
MISSING_COL,
|
||||
findWidth(root, context, nodeMissing)
|
||||
);
|
||||
|
||||
if (this.maxCols > 0) {
|
||||
const pct_cols = DELIM.length + 4 * (PCT_COLS + DELIM.length) + 2;
|
||||
|
||||
const maxRemaining = this.maxCols - (pct_cols + MISSING_COL);
|
||||
if (this.nameWidth > maxRemaining) {
|
||||
this.nameWidth = maxRemaining;
|
||||
this.missingWidth = MISSING_COL;
|
||||
} else if (this.nameWidth < maxRemaining) {
|
||||
const maxRemaining = this.maxCols - (this.nameWidth + pct_cols);
|
||||
if (this.missingWidth > maxRemaining) {
|
||||
this.missingWidth = maxRemaining;
|
||||
}
|
||||
}
|
||||
}
|
||||
const line = makeLine(this.nameWidth, this.missingWidth);
|
||||
this.cw.println(line);
|
||||
this.cw.println(tableHeader(this.nameWidth, this.missingWidth));
|
||||
this.cw.println(line);
|
||||
}
|
||||
|
||||
onSummary(node, context) {
|
||||
const nodeDepth = depthFor(node);
|
||||
const row = tableRow(
|
||||
node,
|
||||
context,
|
||||
this.cw.colorize.bind(this.cw),
|
||||
this.nameWidth,
|
||||
nodeDepth,
|
||||
this.skipEmpty,
|
||||
this.skipFull,
|
||||
this.missingWidth
|
||||
);
|
||||
if (row) {
|
||||
this.cw.println(row);
|
||||
}
|
||||
}
|
||||
|
||||
onDetail(node, context) {
|
||||
return this.onSummary(node, context);
|
||||
}
|
||||
|
||||
onEnd() {
|
||||
this.cw.println(makeLine(this.nameWidth, this.missingWidth));
|
||||
this.cw.close();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TextReport;
|
||||
Reference in new issue
Block a user