-
Notifications
You must be signed in to change notification settings - Fork 21
/
generateFileCoverageHtml.ts
120 lines (103 loc) · 3.74 KB
/
generateFileCoverageHtml.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import * as path from 'path'
import { generateBlobFileUrl } from './generateFileUrl'
import { LineRange, getUncoveredLinesFromStatements } from './getUncoveredLinesFromStatements'
import { JsonFinal } from '../types/JsonFinal'
import { JsonSummary } from '../types/JsonSummary'
import { oneLine } from 'common-tags'
import { FileCoverageMode } from '../inputs/FileCoverageMode'
type FileCoverageInputs = {
jsonSummary: JsonSummary;
jsonFinal: JsonFinal;
fileCoverageMode: FileCoverageMode;
pullChanges: string[];
}
const workspacePath = process.cwd();
const generateFileCoverageHtml = ({ jsonSummary, jsonFinal, fileCoverageMode, pullChanges }: FileCoverageInputs) => {
const filePaths = Object.keys(jsonSummary).filter((key) => key !== 'total');
const formatFileLine = (filePath: string) => {
const coverageSummary = jsonSummary[filePath];
const lineCoverage = jsonFinal[filePath];
// LineCoverage might be empty if coverage-final.json was not provided.
const uncoveredLines = lineCoverage ? getUncoveredLinesFromStatements(jsonFinal[filePath]) : [];
const relativeFilePath = path.relative(workspacePath, filePath);
const url = generateBlobFileUrl(relativeFilePath);
return `
<tr>
<td align="left"><a href="${url}">${relativeFilePath}</a></td>
<td align="right">${coverageSummary.statements.pct}%</td>
<td align="right">${coverageSummary.branches.pct}%</td>
<td align="right">${coverageSummary.functions.pct}%</td>
<td align="right">${coverageSummary.lines.pct}%</td>
<td align="left">${createRangeURLs(uncoveredLines, url)}</td>
</tr>`
}
let reportData: string = ''
const [changedFiles, unchangedFiles] = splitFilesByChangeStatus(filePaths, pullChanges);
if(fileCoverageMode === FileCoverageMode.Changes && changedFiles.length === 0) {
return `No changed files found.`
}
if (changedFiles.length > 0) {
reportData += `
${formatGroupLine('Changed Files')}
${changedFiles.map(formatFileLine).join('')}
`
};
if(fileCoverageMode === FileCoverageMode.All && unchangedFiles.length > 0) {
reportData += `
${formatGroupLine('Unchanged Files')}
${unchangedFiles.map(formatFileLine).join('')}
`
}
return oneLine`
<table>
<thead>
<tr>
<th align="left">File</th>
<th align="right">Stmts</th>
<th align="right">% Branch</th>
<th align="right">% Funcs</th>
<th align="right">% Lines</th>
<th align="left">Uncovered Lines</th>
</tr>
</thead>
<tbody>
${reportData}
</tbody>
</table>
`
}
function formatGroupLine (caption: string): string {
return `
<tr>
<td align="left" colspan="6"><b>${caption}</b></td>
</tr>
`
}
function createRangeURLs(uncoveredLines: LineRange[], url: string): string {
return uncoveredLines.map((range) => {
let linkText = `${range.start}`;
let urlHash = `#L${range.start}`;
if (range.start !== range.end) {
linkText += `-${range.end}`;
urlHash += `-L${range.end}`;
}
return `<a href="${url}${urlHash}" class="text-red">${linkText}</a>`;
})
.join(', ');
}
function splitFilesByChangeStatus(filePaths: string[], pullChanges: string[]): [string[], string[]] {
return filePaths.reduce(([changedFiles, unchangedFiles], filePath) => {
// Pull Changes has filePaths relative to the git repository, whereas the jsonSummary has filePaths relative to the workspace.
// So we have to convert the filePaths to be relative to the workspace.
const comparePath = path.relative(workspacePath, filePath);
if (pullChanges.includes(comparePath)) {
changedFiles.push(filePath);
} else {
unchangedFiles.push(filePath);
}
return [changedFiles, unchangedFiles];
}, [[], []] as [string[], string[]]);
}
export {
generateFileCoverageHtml
};