From 9bc43545d438e71a18399ba5d1a5bde57e0ba21f Mon Sep 17 00:00:00 2001 From: pacien Date: Thu, 11 Oct 2018 00:02:59 +0200 Subject: Implement source coverage inspection --- package.json | 2 ++ src/App.css | 14 ++++++++ src/App.js | 55 ++++++++--------------------- src/Blocks.js | 36 +++++++++++++++++++ src/CoverageListing.js | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/Report.js | 59 ++++++++++++++++++++++++++++++++ yarn.lock | 59 +++++++++++++++++++++++++++++++- 7 files changed, 277 insertions(+), 41 deletions(-) create mode 100644 src/CoverageListing.js create mode 100644 src/Report.js diff --git a/package.json b/package.json index 1cb608c..05c7fd6 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "jszip": "^3.1.5", "react": "^16.5.2", "react-dom": "^16.5.2", "react-scripts": "2.0.4", @@ -24,6 +25,7 @@ "not op_mini all" ], "devDependencies": { + "@types/jszip": "^3.1.4", "@types/xml2js": "^0.4.3" } } diff --git a/src/App.css b/src/App.css index d0870a2..fc9d5ec 100644 --- a/src/App.css +++ b/src/App.css @@ -51,3 +51,17 @@ .App li[well-covered="false"] { color: red; } + +.App .listing { + overflow-x: auto; + padding: 0.5rem 0; + background-color: #FAFAFA; +} + +.App .listing li[well-covered] { + font-weight: bold; +} + +.App pre { + margin: 0.1rem; +} diff --git a/src/App.js b/src/App.js index 347d243..fb77d93 100644 --- a/src/App.js +++ b/src/App.js @@ -18,9 +18,10 @@ */ import React, { Component } from 'react'; -import {Parser} from 'xml2js'; +import { Parser } from 'xml2js'; +import JSZip from 'jszip'; -import {Counters, SessionInfo, PackagesCoverage} from './Blocks.js'; +import { Report } from './Report.js'; import './App.css'; class App extends Component { @@ -28,24 +29,31 @@ class App extends Component { super(props); this.state = { report: null, + sourceSet: null, hasError: false }; } componentDidCatch(error, info) { this.setState({ hasError: true }); - console.err(error, info); + console.error(error, info); } _useReportFile(file) { const fileReader = new FileReader(); - fileReader.onloadend = (readEvent) => this._useReport(readEvent.target.result); + fileReader.onloadend = readEvent => this._useReport(readEvent.target.result); fileReader.readAsText(file); } _useReport(xmlString) { + this.setState({ hasError: false }); const xmlParser = new Parser(); - xmlParser.parseString(xmlString, (err, result) => this.setState({ report: result.report })); + xmlParser.parseString(xmlString, (_, result) => this.setState({ report: result.report })); + } + + _useSourceArchive(file) { + this.setState({ hasError: false }); + JSZip.loadAsync(file).then(zip => this.setState({ sourceSet: zip.files })); } _renderError() { @@ -58,7 +66,7 @@ class App extends Component { } _renderReport() { - return this.state.hasError ? this._renderError(): (); + return this.state.hasError ? this._renderError(): (); } render() { @@ -73,7 +81,7 @@ class App extends Component {
- null} /> + this._useSourceArchive(event.target.files[0])} />

@@ -83,37 +91,4 @@ class App extends Component { } } -class Report extends Component { - _renderNone() { - return (Please provide a JaCoCo XML report file to visualise.); - } - - _renderReport() { - return ( -
-

Viewing report: "{this.props.report.$.name}"

- -
-

Session info

- -
- -
-

Global coverage

- -
- -
-

Details

- -
-
- ); - } - - render() { - return this.props.report ? this._renderReport() : this._renderNone(); - } -} - export default App; diff --git a/src/Blocks.js b/src/Blocks.js index d9020fa..f830538 100644 --- a/src/Blocks.js +++ b/src/Blocks.js @@ -18,6 +18,7 @@ */ import React, { Component } from 'react'; +import { CoverageListing } from './CoverageListing.js'; function renderRows(renderRowFunc, rows, inline) { const renderedRows = rows ? rows.map(renderRowFunc) : (
  • None.
  • ); @@ -97,3 +98,38 @@ class MethodsCoverage extends Component { return renderRows(this._renderRow, this.props.methods, false); } } + +export class PackagesSourceCoverage extends Component { + _renderRow(row) { + return ( +
  • + {row.$.name} + +
  • + ) + } + + render() { + return renderRows(row => this._renderRow(row), this.props.packages, false); + } +} + +class SourcesCoverage extends Component { + _renderRow(row) { + const fileName = this.props.package + '/' + row.$.name; + return ( +
  • + {fileName} + +
  • + ) + } + + render() { + return renderRows(row => this._renderRow(row), this.props.packageSourceFiles, false); + } +} diff --git a/src/CoverageListing.js b/src/CoverageListing.js new file mode 100644 index 0000000..8b7830c --- /dev/null +++ b/src/CoverageListing.js @@ -0,0 +1,93 @@ +/* + * JaCoCo Report Viewer, a web-based coverage report viewer + * Copyright (C) 2018 Pacien TRAN-GIRARD + * Adam NAILI + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import React, { Component } from 'react'; + +export class CoverageListing extends Component { + constructor(props) { + super(props); + this.state = { + listingContent: null, + coverageMap: null + }; + } + + componentDidMount() { + this._updateState(); + } + + componentDidUpdate(prevProps) { + if (this.props.fileName === prevProps.fileName && + this.props.sourceSet === prevProps.sourceSet && + this.props.coverage === prevProps.coverage) return; + + this._updateState(); + } + + _updateState() { + if (!this.props.sourceSet || !(this.props.fileName in this.props.sourceSet)) { + this.setState({ listingContent: null }); + return; + } + + this._updateListingContent(); + this._updateCoverageMap(); + } + + _updateCoverageMap() { + const coverageMap = this.props.coverage.reduce((map, line) => (map[parseInt(line.$.nr)] = line.$, map), {}); + this.setState({ coverageMap: coverageMap }); + } + + _updateListingContent() { + this.props.sourceSet[this.props.fileName] + .async('text') + .then(content => this.setState({ listingContent: content })); + } + + _renderNone() { + return (
    No source file provided.
    ); + } + + _nonEmptyString(str) { + return str ? str : ' '; // workaround for empty
     collapsing
    +  }
    +
    +  _renderLine(lineContent, lineNumber) {
    +    if (!(lineNumber in this.state.coverageMap))
    +      return (
  • {lineContent}
  • ); + + const coverage = this.state.coverageMap[lineNumber]; + const wellCovered = parseInt(coverage.mi) === 0 && parseInt(coverage.mb) === 0; + return (
  • {lineContent}
  • ); + } + + _renderListing() { + const lines = this.state.listingContent + .split('\n') + .map(this._nonEmptyString) + .map((line, index) => this._renderLine(line, index + 1)); + + return (
      {lines}
    ); + } + + render() { + return this.state.listingContent ? this._renderListing() : this._renderNone(); + } +} diff --git a/src/Report.js b/src/Report.js new file mode 100644 index 0000000..e79b832 --- /dev/null +++ b/src/Report.js @@ -0,0 +1,59 @@ +/* + * JaCoCo Report Viewer, a web-based coverage report viewer + * Copyright (C) 2018 Pacien TRAN-GIRARD + * Adam NAILI + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import React, { Component } from 'react'; +import { Counters, SessionInfo, PackagesCoverage, PackagesSourceCoverage } from './Blocks.js'; + +export class Report extends Component { + _renderNone() { + return (Please provide a JaCoCo XML report file to visualise.); + } + + _renderReport() { + return ( +
    +

    Viewing report: "{this.props.report.$.name}"

    + +
    +

    Session info

    + +
    + +
    +

    Global coverage

    + +
    + +
    +

    Coverage tree

    + +
    + +
    +

    Source coverage

    + +
    +
    + ); + } + + render() { + return this.props.report ? this._renderReport() : this._renderNone(); + } +} diff --git a/yarn.lock b/yarn.lock index e3eb3af..438f668 100644 --- a/yarn.lock +++ b/yarn.lock @@ -854,6 +854,13 @@ resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" integrity sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA== +"@types/jszip@^3.1.4": + version "3.1.4" + resolved "https://registry.yarnpkg.com/@types/jszip/-/jszip-3.1.4.tgz#9b81e3901a6988e9459ac27abf483e6b892251af" + integrity sha512-UaVbz4buRlBEolZYrxqkrGDOypugYlbqGNrUFB4qBaexrLypTH0jyvaF5jolNy5D+5C4kKV1WJ3Yx9cn/JH8oA== + dependencies: + "@types/node" "*" + "@types/node@*": version "10.11.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.11.6.tgz#ce5690df6cd917a9178439a1013e39a7e565c46e" @@ -2422,6 +2429,11 @@ core-js@2.5.7, core-js@^2.4.0, core-js@^2.5.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" integrity sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw== +core-js@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.3.0.tgz#fab83fbb0b2d8dc85fa636c4b9d34c75420c6d65" + integrity sha1-+rg/uwstjchfpjbEudNMdUIMbWU= + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -3177,6 +3189,11 @@ es-to-primitive@^1.1.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es6-promise@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.0.2.tgz#010d5858423a5f118979665f46486a95c6ee2bb6" + integrity sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y= + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -4472,6 +4489,11 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= + import-cwd@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" @@ -5545,6 +5567,17 @@ jsx-ast-utils@^2.0.1: dependencies: array-includes "^3.0.3" +jszip@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.1.5.tgz#e3c2a6c6d706ac6e603314036d43cd40beefdf37" + integrity sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ== + dependencies: + core-js "~2.3.0" + es6-promise "~3.0.2" + lie "~3.1.0" + pako "~1.0.2" + readable-stream "~2.0.6" + killable@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" @@ -5636,6 +5669,13 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +lie@~3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" + integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4= + dependencies: + immediate "~3.0.5" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -6574,7 +6614,7 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ== -pako@~1.0.5: +pako@~1.0.2, pako@~1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" integrity sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg== @@ -7471,6 +7511,11 @@ private@^0.1.6, private@^0.1.8: resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== +process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= + process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" @@ -7850,6 +7895,18 @@ readable-stream@1.0: isarray "0.0.1" string_decoder "~0.10.x" +readable-stream@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" + integrity sha1-j5A0HmilPMySh4jaz80Rs265t44= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + string_decoder "~0.10.x" + util-deprecate "~1.0.1" + readdirp@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" -- cgit v1.2.3