UNPKG

istanbul-coverage-display

Version:

React component which can be added to instrumentation build to display coverage report of testing.

614 lines (543 loc) 18.4 kB
import libCoverage from 'istanbul-lib-coverage'; import React, { Component } from 'react'; import PropTypes from 'prop-types'; function getCoverage() { if (window.__coverage__ && libCoverage) { var map = libCoverage.createCoverageMap({}); map.merge(window.__coverage__); return Object.keys(map.data).map(function (key) { return { key: key, data: map.data[key].toSummary().data }; }); } return null; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } } function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } function getInitialSubData() { return { total: 0, covered: 0 }; } function getInitialData() { return { branches: getInitialSubData(), functions: getInitialSubData(), lines: getInitialSubData(), statements: getInitialSubData() }; } function computeTotals(fileCoverages) { var data = getInitialData(); fileCoverages.forEach(function (c) { data.branches.total += c.data.branches.total; data.branches.covered += c.data.branches.covered; data.functions.total += c.data.functions.total; data.functions.covered += c.data.functions.covered; data.lines.total += c.data.lines.total; data.lines.covered += c.data.lines.covered; data.statements.total += c.data.statements.total; data.statements.covered += c.data.statements.covered; }); data.branches.pct = data.branches.total ? Math.round(data.branches.covered * 100 / data.branches.total) : 100; data.functions.pct = data.functions.total ? Math.round(data.functions.covered * 100 / data.functions.total) : 100; data.lines.pct = data.lines.total ? Math.round(data.lines.covered * 100 / data.lines.total) : 100; data.statements.pct = data.statements.total ? Math.round(data.statements.covered * 100 / data.statements.total) : 100; return data; } var Summary = /*#__PURE__*/ function (_Component) { _inherits(Summary, _Component); function Summary() { var _this; _classCallCheck(this, Summary); _this = _possibleConstructorReturn(this, _getPrototypeOf(Summary).call(this)); _this.state = { data: {} }; return _this; } _createClass(Summary, [{ key: "handleRefresh", value: function handleRefresh() { this.refresh(); } }, { key: "handleClose", value: function handleClose() { this.setState({ data: {}, showGuide: false }); } }, { key: "handleShow", value: function handleShow() { this.refresh(); } }, { key: "refresh", value: function refresh() { var fileCoverages = getCoverage(); if (fileCoverages) { var data = computeTotals(fileCoverages); this.setState({ data: data }); } else { this.setState({ showGuide: true }); } } }, { key: "render", value: function render() { var _this$props = this.props, magic = _this$props.magic, onNavigate = _this$props.onNavigate; var _this$state = this.state, data = _this$state.data, showGuide = _this$state.showGuide; return React.createElement("div", { className: "icd-summary" }, data.branches ? React.createElement("div", { className: "icd-summary__popover" }, React.createElement("table", { className: "icd-summary__table" }, React.createElement("thead", null, React.createElement("tr", null, React.createElement("th", { width: "200" }), React.createElement("th", { width: "100" }))), React.createElement("tbody", null, React.createElement("tr", null, React.createElement("td", null, "Branch coverage:"), React.createElement("td", { align: "right" }, data.branches.pct, "% (", data.branches.total, ")")), React.createElement("tr", null, React.createElement("td", null, "Function coverage:"), React.createElement("td", { align: "right" }, data.functions.pct, "% (", data.functions.total, ")")), React.createElement("tr", null, React.createElement("td", null, "Line coverage:"), React.createElement("td", { align: "right" }, data.lines.pct, "% (", data.lines.total, ")")), React.createElement("tr", null, React.createElement("td", null, "Statement coverage:"), React.createElement("td", { align: "right" }, data.statements.pct, "% (", data.statements.total, ")")))), React.createElement("div", { className: "icd-summary__nav" }, onNavigate ? React.createElement("button", { className: "icd-summary__button", onClick: onNavigate }, "Details") : null, React.createElement("button", { className: "icd-summary__button", onClick: this.handleRefresh.bind(this) }, "Refresh"), React.createElement("button", { className: "icd-summary__button", onClick: this.handleClose.bind(this) }, "Close"))) : showGuide ? React.createElement("div", { className: "icd-summary__popover" }, React.createElement("p", null, "Please install babel-plugin-istanbul as devDependency. Add the plugin to .babelrc file or webpack config for instrumenting your code."), React.createElement("div", { style: { textAlign: 'right' } }, React.createElement("button", { className: "icd-summary__button", onClick: this.handleClose.bind(this) }, "Close"))) : null, React.createElement("button", { className: "icd-summary__show", style: { opacity: magic ? 0 : 1 }, onClick: this.handleShow.bind(this) }, "Show coverage")); } }]); return Summary; }(Component); Summary.propTypes = { magic: PropTypes.bool, onNavigate: PropTypes.func }; function Arrow(_ref) { var className = _ref.className, down = _ref.down; var deg = down ? 180 : 0; return React.createElement("svg", { className: className, viewBox: "0 0 32 32", fill: "currentColor", style: { transform: "rotate(".concat(deg, "deg)"), transition: 'transform 200ms ease-in-out' } }, React.createElement("path", { d: "M18.221 7.206l9.585 9.585a2.265 2.265 0 0 1 0 3.195l-.8.801a2.266 2.266 0 0 1-3.194 0l-7.315-7.315-7.315 7.315a2.266 2.266 0 0 1-3.194 0l-.8-.801a2.265 2.265 0 0 1 0-3.195l9.587-9.585a2.24 2.24 0 0 1 1.723-.647 2.247 2.247 0 0 1 1.723.647z", fill: "#515151" })); } Arrow.propTypes = { down: PropTypes.bool }; var Dropdown = /*#__PURE__*/ function (_Component) { _inherits(Dropdown, _Component); function Dropdown() { _classCallCheck(this, Dropdown); return _possibleConstructorReturn(this, _getPrototypeOf(Dropdown).apply(this, arguments)); } _createClass(Dropdown, [{ key: "render", value: function render() { var _this$props = this.props, text = _this$props.text, expanded = _this$props.expanded, onToggle = _this$props.onToggle; return React.createElement("span", { className: "dropdown", onClick: onToggle }, React.createElement(Arrow, { className: "dropdown__arrow", down: expanded }), React.createElement("span", { className: "dropdown__text" }, text)); } }]); return Dropdown; }(Component); Dropdown.propTypes = { text: PropTypes.string, expanded: PropTypes.bool, onToggle: PropTypes.func }; var TreeTable = /*#__PURE__*/ function (_Component) { _inherits(TreeTable, _Component); function TreeTable(props) { var _this; _classCallCheck(this, TreeTable); _this = _possibleConstructorReturn(this, _getPrototypeOf(TreeTable).call(this)); var items = []; var root = props.data.find(function (d) { return d.id === 1; }); if (root) { items.push({ node: root, expanded: true }); var children = props.data.filter(function (d) { return d.parentId === 1; }); children.forEach(function (c) { items.push({ node: c, expanded: false }); }); } _this.state = { items: items }; return _this; } _createClass(TreeTable, [{ key: "getChildrenCount", value: function getChildrenCount(item) { var _this2 = this; if (item.node.leaf) return 0; if (!item.expanded) return 0; var items = this.state.items; var children = items.filter(function (i) { return i.node.parentId === item.node.id; }); var count = children.length; children.forEach(function (c) { count += _this2.getChildrenCount(c); }); return count; } }, { key: "handleToggle", value: function handleToggle(id) { var items = this.state.items; var data = this.props.data; items = items.slice(); var index = items.findIndex(function (i) { return i.node.id === id; }); var item = items[index]; if (item.expanded) { var count = this.getChildrenCount(item); items.splice(index + 1, count); items[index] = _objectSpread({}, items[index], { expanded: false }); this.setState({ items: items }); } else { var _items; var children = data.filter(function (d) { return d.parentId === id; }).map(function (d) { return { node: d, expanded: false }; }); (_items = items).splice.apply(_items, [index + 1, 0].concat(_toConsumableArray(children))); items[index] = _objectSpread({}, items[index], { expanded: true }); this.setState({ items: items }); } } }, { key: "render", value: function render() { var _this3 = this; var items = this.state.items; return React.createElement("table", { className: "icd-table" }, React.createElement("thead", null, React.createElement("tr", null, React.createElement("th", { width: "300" }, "Folder / File"), React.createElement("th", { width: "300" }, "Path"), React.createElement("th", { width: "100" }, "Branches"), React.createElement("th", { width: "100" }, "Functions"), React.createElement("th", { width: "100" }, "Lines"), React.createElement("th", { width: "100" }, "Statements"))), React.createElement("tbody", null, items.map(function (item) { return React.createElement("tr", { key: item.node.id }, React.createElement("td", { style: { paddingLeft: 16 * (item.node.level + 1) } }, item.node.leaf ? item.node.name : React.createElement(Dropdown, { text: item.node.name, expanded: item.expanded, onToggle: _this3.handleToggle.bind(_this3, item.node.id) })), React.createElement("td", { style: { paddingLeft: 16 } }, item.node.path), React.createElement("td", null, item.node.data.branches.pct, "% (", item.node.data.branches.total, ")"), React.createElement("td", null, item.node.data.functions.pct, "% (", item.node.data.functions.total, ")"), React.createElement("td", null, item.node.data.lines.pct, "% (", item.node.data.lines.total, ")"), React.createElement("td", null, item.node.data.statements.pct, "% (", item.node.data.statements.total, ")")); }))); } }]); return TreeTable; }(Component); TreeTable.propTypes = { data: PropTypes.array }; var CoverageDetail = /*#__PURE__*/ function (_Component) { _inherits(CoverageDetail, _Component); function CoverageDetail() { var _this; _classCallCheck(this, CoverageDetail); _this = _possibleConstructorReturn(this, _getPrototypeOf(CoverageDetail).call(this)); _this.state = { treeNodes: [] }; return _this; } _createClass(CoverageDetail, [{ key: "componentDidMount", value: function componentDidMount() { var fileCoverages = getCoverage(); if (!fileCoverages) return; var minSlashes = 100; fileCoverages.forEach(function (fc) { var slashes = fc.key.split('/').length; if (slashes < minSlashes) minSlashes = slashes; }); fileCoverages.forEach(function (fileCoverage) { var paths = fileCoverage.key.split('/').slice(minSlashes - 1); fileCoverage.path = paths.join('/'); fileCoverage.parentPath = paths.length > 1 ? paths.slice(0, paths.length - 1).join('/') : rootPath; fileCoverage.level = paths.length; fileCoverage.name = paths[fileCoverage.level - 1]; fileCoverage.paths = paths; fileCoverage.leaf = true; }); var id = 1; var rootPath = '[Total]'; var rootNode = { id: id++, path: rootPath, name: 'Total', level: 0 }; var treeData = _defineProperty({}, rootNode.path, rootNode); fileCoverages.forEach(function (fc) { fc.id = id++; if (fc.level === 1) { fc.parentId = 1; return; } var childNode = fc; for (var i = fc.level - 2; i >= 0; i--) { var paths = fc.paths.slice(0, i + 1); var path = paths.join('/'); var node = treeData[path]; if (!node) { node = { id: id++, path: path, parentPath: paths.length > 1 ? paths.slice(0, paths.length - 1).join('/') : rootPath, name: paths[paths.length - 1], parentId: 1, level: i + 1 }; treeData[path] = node; } childNode.parentId = node.id; childNode = node; } }); var treeNodes = Object.values(treeData); treeNodes = treeNodes.concat(fileCoverages); treeNodes.sort(function (a, b) { return a.level < b.level ? 1 : a.level > b.level ? -1 : 0; }); treeNodes.forEach(function (treeNode) { if (!treeNode.leaf) { var children = treeNodes.filter(function (n) { return n.parentId === treeNode.id; }); treeNode.data = computeTotals(children); } var _treeNode$data = treeNode.data, branches = _treeNode$data.branches, functions = _treeNode$data.functions, lines = _treeNode$data.lines, statements = _treeNode$data.statements; treeNode.branchPerc = "".concat(branches.pct, "% (").concat(branches.total, ")"); treeNode.functionPerc = "".concat(functions.pct, "% (").concat(functions.total, ")"); treeNode.linePerc = "".concat(lines.pct, "% (").concat(lines.total, ")"); treeNode.stmtPerc = "".concat(statements.pct, "% (").concat(statements.total, ")"); }); this.setState({ treeNodes: treeNodes }); } }, { key: "render", value: function render() { var treeNodes = this.state.treeNodes; return React.createElement("div", null, treeNodes.length ? React.createElement(TreeTable, { data: treeNodes }) : null); } }]); return CoverageDetail; }(Component); export { getCoverage, Summary as CoverageSummary, CoverageDetail };