Skip to content

Commit c22ca24

Browse files
authored
Created coverage details for pull request comment (#3)
* Created coverage details for pull request comment * Bug fixes, rebuilt index.js * Comment bug fixes * Bug fix * Fixed comment style * Removed test * Reverted change * Fix default test script * Default test script fix
1 parent fbfacbf commit c22ca24

10 files changed

+251
-41
lines changed

.github/workflows/test.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,3 @@ jobs:
1111
uses: ./ # Uses an action in the root directory
1212
with:
1313
github_token: ${{ secrets.GITHUB_TOKEN }}
14-
test_script: npm run test:coverage

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2020 Artiom Tretjakovas
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { parseCoverageDetails } from '../src/parseCoverageDetails';
2+
3+
const coverageOutput1 = `
4+
-------------------------|---------|----------|---------|---------|-------------------
5+
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
6+
-------------------------|---------|----------|---------|---------|-------------------
7+
All files | 100 | 87.5 | 100 | 100 |
8+
parseCoverageSummary.ts | 100 | 87.5 | 100 | 100 | 27
9+
-------------------------|---------|----------|---------|---------|-------------------
10+
11+
=============================== Coverage summary ===============================
12+
Statements : 100% ( 16/16 )
13+
Branches : 1% ( 7/8 )
14+
Functions : 0.001% ( 2/2 )
15+
Lines : 81.15% ( 15/15 )
16+
================================================================================
17+
`;
18+
19+
describe('parseCoverageDetails', () => {
20+
it('', () => {
21+
expect(parseCoverageDetails(coverageOutput1)).toStrictEqual({
22+
'parseCoverageSummary.ts': {
23+
statements: 100,
24+
branches: 87.5,
25+
functions: 100,
26+
lines: 100,
27+
},
28+
});
29+
});
30+
});

__tests__/parseCoverageSummary.test.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,43 @@
11
import { parseCoverageSummary } from '../src/parseCoverageSummary';
22

3+
const coverageOutput1 = `
4+
-------------------------|---------|----------|---------|---------|-------------------
5+
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
6+
-------------------------|---------|----------|---------|---------|-------------------
7+
All files | 100 | 87.5 | 100 | 100 |
8+
parseCoverageSummary.ts | 100 | 87.5 | 100 | 100 | 27
9+
-------------------------|---------|----------|---------|---------|-------------------
10+
11+
=============================== Coverage summary ===============================
12+
Statements : 100% ( 16/16 )
13+
Branches : 1% ( 7/8 )
14+
Functions : 0.001% ( 2/2 )
15+
Lines : 81.15% ( 15/15 )
16+
================================================================================
17+
`;
18+
319
describe('parseCoverageSummary', () => {
420
it('stats', () => {
5-
expect(
6-
parseCoverageSummary(`
7-
=============================== Coverage summary ===============================
8-
Statements : 100% ( 1/1 )
9-
Branches : 1% ( 1/1 )
10-
Functions : 0.001% ( 1/1 )
11-
Lines : 81.15% ( 1/1 )
12-
================================================================================
13-
`)
14-
).toStrictEqual({
21+
expect(parseCoverageSummary(coverageOutput1)).toStrictEqual({
1522
statements: {
1623
percentage: 100,
17-
covered: 1,
18-
total: 1,
24+
covered: 16,
25+
total: 16,
1926
},
2027
branches: {
2128
percentage: 1,
22-
covered: 1,
23-
total: 1,
29+
covered: 7,
30+
total: 8,
2431
},
2532
functions: {
2633
percentage: 0.001,
27-
covered: 1,
28-
total: 1,
34+
covered: 2,
35+
total: 2,
2936
},
3037
lines: {
3138
percentage: 81.15,
32-
covered: 1,
33-
total: 1,
39+
covered: 15,
40+
total: 15,
3441
},
3542
});
3643
});

action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ inputs:
1111
test_script:
1212
required: false
1313
description: 'a custom npm script to test'
14-
default: npx jest --silent --coverage --json
14+
default: npx jest --silent --coverage --coverageReporters="text" --coverageReporters="text-summary"
1515
runs:
1616
using: 'docker'
1717
image: 'Dockerfile'

dist/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/getCommentBody.ts

Lines changed: 116 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { MESSAGE_HEADING } from './fetchPreviousComment';
22
import { ParsedCoverageSummary } from './parseCoverageSummary';
33
import table from 'markdown-table';
4+
import {
5+
FileCoverageDetail,
6+
ParsedCoverageDetails,
7+
} from './parseCoverageDetails';
48

59
const map: Record<string, string> = {
610
statements: 'Statements',
@@ -27,13 +31,12 @@ const getFormattedPercentage = (
2731
return `${headPercentage}% (${symbol} ${delta})`;
2832
};
2933

30-
export const getCommentBody = (
34+
const getSummaryTable = (
3135
headSummary: ParsedCoverageSummary,
3236
baseSummary: ParsedCoverageSummary
33-
): string => {
34-
return [
35-
MESSAGE_HEADING,
36-
table([
37+
) =>
38+
table(
39+
[
3740
['Category', 'Percentage', 'Covered / Total'],
3841
...(Object.keys(headSummary) as Array<
3942
keyof ParsedCoverageSummary
@@ -45,6 +48,112 @@ export const getCommentBody = (
4548
),
4649
`${headSummary[value].covered}/${headSummary[value].total}`,
4750
]),
48-
]),
49-
].join('\n');
51+
],
52+
{ align: ['l', 'l', 'c'] }
53+
);
54+
55+
const getDetailsTable = (content: string[][]) =>
56+
table([
57+
['Filename', 'Statements', 'Branches', 'Functions', 'Lines'],
58+
...content,
59+
]);
60+
61+
const getNewFilesSpoiler = (
62+
headDetails: ParsedCoverageDetails,
63+
baseDetails: ParsedCoverageDetails
64+
) => {
65+
const tableContent = Object.keys(headDetails)
66+
.filter((filename) => baseDetails[filename] === undefined)
67+
.map((filename) => [
68+
filename,
69+
`${headDetails[filename].statements}%`,
70+
`${headDetails[filename].branches}%`,
71+
`${headDetails[filename].functions}%`,
72+
`${headDetails[filename].lines}%`,
73+
]);
74+
75+
if (tableContent.length > 0) {
76+
return `
77+
<details>
78+
<summary>Show new covered files</summary>
79+
80+
### Coverage of new files
81+
82+
${getDetailsTable(tableContent)}
83+
84+
</details>
85+
`;
86+
}
87+
88+
return undefined;
89+
};
90+
91+
const ltCoverage = (first: FileCoverageDetail, second: FileCoverageDetail) =>
92+
first.statements < second.statements ||
93+
first.branches < second.branches ||
94+
first.functions < second.functions ||
95+
first.lines < second.lines;
96+
97+
const getFilesWithDecreasedCoverage = (
98+
headDetails: ParsedCoverageDetails,
99+
baseDetails: ParsedCoverageDetails
100+
) => {
101+
const tableContent = Object.keys(headDetails)
102+
.filter(
103+
(filename) =>
104+
headDetails[filename] &&
105+
baseDetails[filename] &&
106+
ltCoverage(headDetails[filename], baseDetails[filename])
107+
)
108+
.map((filename) => [
109+
filename,
110+
getFormattedPercentage(
111+
headDetails[filename].statements,
112+
baseDetails[filename].statements
113+
),
114+
getFormattedPercentage(
115+
headDetails[filename].branches,
116+
baseDetails[filename].branches
117+
),
118+
getFormattedPercentage(
119+
headDetails[filename].functions,
120+
baseDetails[filename].functions
121+
),
122+
getFormattedPercentage(
123+
headDetails[filename].lines,
124+
baseDetails[filename].lines
125+
),
126+
]);
127+
128+
if (tableContent.length > 0) {
129+
return `
130+
<details>
131+
<summary>Show files with reduced coverage</summary>
132+
133+
### Reduced coverage
134+
135+
${getDetailsTable(tableContent)}
136+
137+
</details>
138+
`;
139+
}
140+
141+
return undefined;
142+
};
143+
144+
export const getCommentBody = (
145+
headSummary: ParsedCoverageSummary,
146+
baseSummary: ParsedCoverageSummary,
147+
headDetails: ParsedCoverageDetails,
148+
baseDetails: ParsedCoverageDetails
149+
): string => {
150+
return [
151+
MESSAGE_HEADING,
152+
'### Total coverage',
153+
getSummaryTable(headSummary, baseSummary),
154+
getFilesWithDecreasedCoverage(headDetails, baseDetails),
155+
getNewFilesSpoiler(headDetails, baseDetails),
156+
]
157+
.filter(Boolean)
158+
.join('\n');
50159
};

src/index.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { argv } from 'process';
66
import { parseCoverageSummary } from './parseCoverageSummary';
77
import { fetchPreviousComment } from './fetchPreviousComment';
88
import { getCommentBody } from './getCommentBody';
9+
import { parseCoverageDetails } from './parseCoverageDetails';
910

1011
async function getCoverage(testCommand: string, branch?: string) {
1112
if (branch) {
@@ -58,31 +59,38 @@ async function run() {
5859
const headSummary = parseCoverageSummary(headOutput);
5960
const baseSummary = parseCoverageSummary(baseOutput);
6061

62+
const headDetails = parseCoverageDetails(headOutput);
63+
const baseDetails = parseCoverageDetails(baseOutput);
64+
6165
const previousComment = await fetchPreviousComment(
6266
octokit,
6367
repo,
6468
pull_request
6569
);
6670

67-
const body = getCommentBody(headSummary, baseSummary);
71+
const body = getCommentBody(
72+
headSummary,
73+
baseSummary,
74+
headDetails,
75+
baseDetails
76+
);
6877

69-
if (previousComment) {
70-
try {
78+
try {
79+
if (previousComment) {
7180
await octokit.issues.deleteComment({
7281
...repo,
7382
comment_id: (previousComment as any).id,
7483
});
75-
76-
await octokit.issues.createComment({
77-
...repo,
78-
issue_number: pull_request.number,
79-
body,
80-
});
81-
} catch (error) {
82-
console.error(
83-
"Error deleting and/or creating comment. This can happen for PR's originating from a fork without write permissions."
84-
);
8584
}
85+
await octokit.issues.createComment({
86+
...repo,
87+
issue_number: pull_request.number,
88+
body,
89+
});
90+
} catch (error) {
91+
console.error(
92+
"Error deleting and/or creating comment. This can happen for PR's originating from a fork without write permissions."
93+
);
8694
}
8795
} catch (error) {
8896
setFailed(error.message);

src/parseCoverageDetails.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const coverageDetailsRegexp = /^([^0-9|-]+)(?:\|\s*([0-9]+\.?[0-9]*)\s*)(?:\|\s*([0-9]+\.?[0-9]*)\s*)(?:\|\s*([0-9]+\.?[0-9]*)\s*)(?:\|\s*([0-9]+\.?[0-9]*)\s*)/gm;
2+
3+
export type FileCoverageDetail = {
4+
statements: number;
5+
branches: number;
6+
functions: number;
7+
lines: number;
8+
};
9+
10+
export type ParsedCoverageDetails = Record<string, FileCoverageDetail>;
11+
12+
export const parseCoverageDetails = (source: string): ParsedCoverageDetails => {
13+
source = source.slice(source.indexOf('-'), source.lastIndexOf('-'));
14+
15+
const output: ParsedCoverageDetails = {};
16+
17+
source.replace(
18+
coverageDetailsRegexp,
19+
(_junk, filename, statements, branches, functions, lines) => {
20+
filename = filename.trim();
21+
if (filename !== 'All files') {
22+
output[filename] = {
23+
statements: parseFloat(statements),
24+
branches: parseFloat(branches),
25+
functions: parseFloat(functions),
26+
lines: parseFloat(lines),
27+
};
28+
}
29+
return '';
30+
}
31+
);
32+
33+
return output;
34+
};

src/parseCoverageSummary.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export type ParsedCoverageSummary = {
1414
};
1515

1616
export const parseCoverageSummary = (source: string): ParsedCoverageSummary => {
17+
source = source.slice(source.indexOf('='), source.lastIndexOf('='));
18+
1719
const map: ParsedCoverageSummary = {} as ParsedCoverageSummary;
1820

1921
source.replace(statsRegexp, (key, percentage, covered, total) => {

0 commit comments

Comments
 (0)