aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.css14
-rw-r--r--src/App.js55
-rw-r--r--src/Blocks.js36
-rw-r--r--src/CoverageListing.js93
-rw-r--r--src/Report.js59
5 files changed, 217 insertions, 40 deletions
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 @@
51.App li[well-covered="false"] { 51.App li[well-covered="false"] {
52 color: red; 52 color: red;
53} 53}
54
55.App .listing {
56 overflow-x: auto;
57 padding: 0.5rem 0;
58 background-color: #FAFAFA;
59}
60
61.App .listing li[well-covered] {
62 font-weight: bold;
63}
64
65.App pre {
66 margin: 0.1rem;
67}
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 @@
18 */ 18 */
19 19
20import React, { Component } from 'react'; 20import React, { Component } from 'react';
21import {Parser} from 'xml2js'; 21import { Parser } from 'xml2js';
22import JSZip from 'jszip';
22 23
23import {Counters, SessionInfo, PackagesCoverage} from './Blocks.js'; 24import { Report } from './Report.js';
24import './App.css'; 25import './App.css';
25 26
26class App extends Component { 27class App extends Component {
@@ -28,24 +29,31 @@ class App extends Component {
28 super(props); 29 super(props);
29 this.state = { 30 this.state = {
30 report: null, 31 report: null,
32 sourceSet: null,
31 hasError: false 33 hasError: false
32 }; 34 };
33 } 35 }
34 36
35 componentDidCatch(error, info) { 37 componentDidCatch(error, info) {
36 this.setState({ hasError: true }); 38 this.setState({ hasError: true });
37 console.err(error, info); 39 console.error(error, info);
38 } 40 }
39 41
40 _useReportFile(file) { 42 _useReportFile(file) {
41 const fileReader = new FileReader(); 43 const fileReader = new FileReader();
42 fileReader.onloadend = (readEvent) => this._useReport(readEvent.target.result); 44 fileReader.onloadend = readEvent => this._useReport(readEvent.target.result);
43 fileReader.readAsText(file); 45 fileReader.readAsText(file);
44 } 46 }
45 47
46 _useReport(xmlString) { 48 _useReport(xmlString) {
49 this.setState({ hasError: false });
47 const xmlParser = new Parser(); 50 const xmlParser = new Parser();
48 xmlParser.parseString(xmlString, (err, result) => this.setState({ report: result.report })); 51 xmlParser.parseString(xmlString, (_, result) => this.setState({ report: result.report }));
52 }
53
54 _useSourceArchive(file) {
55 this.setState({ hasError: false });
56 JSZip.loadAsync(file).then(zip => this.setState({ sourceSet: zip.files }));
49 } 57 }
50 58
51 _renderError() { 59 _renderError() {
@@ -58,7 +66,7 @@ class App extends Component {
58 } 66 }
59 67
60 _renderReport() { 68 _renderReport() {
61 return this.state.hasError ? this._renderError(): (<Report report={this.state.report} />); 69 return this.state.hasError ? this._renderError(): (<Report report={this.state.report} sourceSet={this.state.sourceSet} />);
62 } 70 }
63 71
64 render() { 72 render() {
@@ -73,7 +81,7 @@ class App extends Component {
73 81
74 <div> 82 <div>
75 <label htmlFor="sourceJar">Source JAR: </label> 83 <label htmlFor="sourceJar">Source JAR: </label>
76 <input id="sourceJar" type="file" accept=".jar" onChange={event => null} /> 84 <input id="sourceJar" type="file" accept=".jar" onChange={event => this._useSourceArchive(event.target.files[0])} />
77 </div> 85 </div>
78 86
79 <hr /> 87 <hr />
@@ -83,37 +91,4 @@ class App extends Component {
83 } 91 }
84} 92}
85 93
86class Report extends Component {
87 _renderNone() {
88 return (<span>Please provide a JaCoCo XML report file to visualise.</span>);
89 }
90
91 _renderReport() {
92 return (
93 <div>
94 <h2>Viewing report: "{this.props.report.$.name}"</h2>
95
96 <section>
97 <h3>Session info</h3>
98 <SessionInfo data={this.props.report.sessioninfo} />
99 </section>
100
101 <section>
102 <h3>Global coverage</h3>
103 <Counters data={this.props.report.counter} />
104 </section>
105
106 <section>
107 <h3>Details</h3>
108 <PackagesCoverage packages={this.props.report.package} />
109 </section>
110 </div>
111 );
112 }
113
114 render() {
115 return this.props.report ? this._renderReport() : this._renderNone();
116 }
117}
118
119export default App; 94export 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 @@
18 */ 18 */
19 19
20import React, { Component } from 'react'; 20import React, { Component } from 'react';
21import { CoverageListing } from './CoverageListing.js';
21 22
22function renderRows(renderRowFunc, rows, inline) { 23function renderRows(renderRowFunc, rows, inline) {
23 const renderedRows = rows ? rows.map(renderRowFunc) : (<li>None.</li>); 24 const renderedRows = rows ? rows.map(renderRowFunc) : (<li>None.</li>);
@@ -97,3 +98,38 @@ class MethodsCoverage extends Component {
97 return renderRows(this._renderRow, this.props.methods, false); 98 return renderRows(this._renderRow, this.props.methods, false);
98 } 99 }
99} 100}
101
102export class PackagesSourceCoverage extends Component {
103 _renderRow(row) {
104 return (
105 <li key={row.$.name}>
106 <span>{row.$.name}</span>
107 <SourcesCoverage package={row.$.name}
108 packageSourceFiles={row.sourcefile}
109 sourceSet={this.props.sourceSet} />
110 </li>
111 )
112 }
113
114 render() {
115 return renderRows(row => this._renderRow(row), this.props.packages, false);
116 }
117}
118
119class SourcesCoverage extends Component {
120 _renderRow(row) {
121 const fileName = this.props.package + '/' + row.$.name;
122 return (
123 <li key={fileName}>
124 <span>{fileName}</span>
125 <CoverageListing fileName={fileName}
126 sourceSet={this.props.sourceSet}
127 coverage={row.line} />
128 </li>
129 )
130 }
131
132 render() {
133 return renderRows(row => this._renderRow(row), this.props.packageSourceFiles, false);
134 }
135}
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 @@
1/*
2 * JaCoCo Report Viewer, a web-based coverage report viewer
3 * Copyright (C) 2018 Pacien TRAN-GIRARD
4 * Adam NAILI
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20import React, { Component } from 'react';
21
22export class CoverageListing extends Component {
23 constructor(props) {
24 super(props);
25 this.state = {
26 listingContent: null,
27 coverageMap: null
28 };
29 }
30
31 componentDidMount() {
32 this._updateState();
33 }
34
35 componentDidUpdate(prevProps) {
36 if (this.props.fileName === prevProps.fileName &&
37 this.props.sourceSet === prevProps.sourceSet &&
38 this.props.coverage === prevProps.coverage) return;
39
40 this._updateState();
41 }
42
43 _updateState() {
44 if (!this.props.sourceSet || !(this.props.fileName in this.props.sourceSet)) {
45 this.setState({ listingContent: null });
46 return;
47 }
48
49 this._updateListingContent();
50 this._updateCoverageMap();
51 }
52
53 _updateCoverageMap() {
54 const coverageMap = this.props.coverage.reduce((map, line) => (map[parseInt(line.$.nr)] = line.$, map), {});
55 this.setState({ coverageMap: coverageMap });
56 }
57
58 _updateListingContent() {