Skip to content

Commit cb48d08

Browse files
fix: properly render uncovered lines column (#950)
* feat: improve code coverage percentage outputs and add yellow color * fix: use red for values lower than 75% * fix: not covered lines aren't json * refactor: share table printing function (dry) * test: coverage line formatting --------- Co-authored-by: Allan Oricil <allanoricilcos@outlook.com>
1 parent 339d6e5 commit cb48d08

File tree

5 files changed

+359
-190
lines changed

5 files changed

+359
-190
lines changed

src/coverageUtils.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,27 +39,32 @@ function mapTestResults(testResults: Failures[] | Successes[]): ApexTestResultDa
3939
});
4040
}
4141

42-
export function prepCoverageForDisplay(codeCoverage: CodeCoverage[]): CodeCoverage[] {
42+
export function prepCoverageForDisplay(codeCoverage: CodeCoverage[]): Array<CodeCoverage & { lineNotCovered: string }> {
4343
const coverage = codeCoverage.sort((a, b) => (a.name.toUpperCase() > b.name.toUpperCase() ? 1 : -1));
4444

45-
coverage.forEach((cov: CodeCoverage & { lineNotCovered: string }) => {
46-
const numLocationsNum = parseInt(cov.numLocations, 10);
47-
const numLocationsNotCovered = parseInt(cov.numLocationsNotCovered, 10);
48-
const color = numLocationsNotCovered > 0 ? chalk.red : chalk.green;
49-
50-
const coverageDecimal = parseFloat(((numLocationsNum - numLocationsNotCovered) / numLocationsNum).toFixed(2));
51-
const pctCovered = numLocationsNum > 0 ? coverageDecimal * 100 : 100;
52-
cov.numLocations = color(`${pctCovered}%`);
53-
54-
cov.lineNotCovered = cov.locationsNotCovered
45+
return coverage.map((cov) => ({
46+
...cov,
47+
numLocations: stylePercentage(calculateCoveragePercent(cov)),
48+
lineNotCovered: cov.locationsNotCovered
5549
? ensureArray(cov.locationsNotCovered)
5650
.map((location) => location.line)
5751
.join(',')
58-
: '';
59-
});
60-
return coverage;
52+
: '',
53+
}));
6154
}
6255

56+
const stylePercentage = (pct: number): string => {
57+
const color = pct < 75 ? chalk.red : pct >= 90 ? chalk.green : chalk.yellow;
58+
return color(`${pct}%`);
59+
};
60+
61+
const calculateCoveragePercent = (cov: CodeCoverage): number => {
62+
const numLocationsNum = parseInt(cov.numLocations, 10);
63+
const numLocationsNotCovered = parseInt(cov.numLocationsNotCovered, 10);
64+
const coverageDecimal = parseFloat(((numLocationsNum - numLocationsNotCovered) / numLocationsNum).toFixed(2));
65+
return numLocationsNum > 0 ? coverageDecimal * 100 : 100;
66+
};
67+
6368
function generateCoveredLines(cov: CodeCoverage): [number[], number[]] {
6469
const numCovered = parseInt(cov.numLocations, 10);
6570
const numUncovered = parseInt(cov.numLocationsNotCovered, 10);

src/formatters/codeCoverageTable.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (c) 2023, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import { ensureArray } from '@salesforce/kit';
8+
import { Ux } from '@salesforce/sf-plugins-core';
9+
import { CodeCoverage } from '@salesforce/source-deploy-retrieve';
10+
import chalk = require('chalk');
11+
import { prepCoverageForDisplay } from '../coverageUtils';
12+
13+
/**
14+
* prints a table of formatted code coverage results if there are any
15+
* This is a no-op if tests didn't run or there is no coverage
16+
*
17+
* @param coverageFromMdapiResult
18+
* @param ux
19+
*/
20+
export const maybePrintCodeCoverageTable = (coverageFromMdapiResult: CodeCoverage | CodeCoverage[], ux: Ux): void => {
21+
const codeCoverage = ensureArray(coverageFromMdapiResult);
22+
23+
if (codeCoverage.length) {
24+
const coverage = prepCoverageForDisplay(codeCoverage);
25+
26+
ux.log('');
27+
ux.styledHeader(chalk.blue('Apex Code Coverage'));
28+
29+
ux.table(
30+
coverage.map((entry) => ({
31+
name: entry.name,
32+
numLocations: entry.numLocations,
33+
lineNotCovered: entry.lineNotCovered,
34+
})),
35+
{
36+
name: { header: 'Name' },
37+
numLocations: { header: '% Covered' },
38+
lineNotCovered: { header: 'Uncovered Lines' },
39+
}
40+
);
41+
}
42+
};

src/formatters/deployResultFormatter.ts

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ import {
2222
Successes,
2323
} from '@salesforce/source-deploy-retrieve';
2424
import { Ux } from '@salesforce/sf-plugins-core';
25-
import { prepCoverageForDisplay } from '../coverageUtils';
2625
import { ResultFormatter, ResultFormatterOptions } from './resultFormatter';
2726
import { MdDeployResult } from './mdapi/mdDeployResultFormatter';
27+
import { maybePrintCodeCoverageTable } from './codeCoverageTable';
2828

2929
Messages.importMessagesDirectory(__dirname);
3030
const messages = Messages.loadMessages('@salesforce/plugin-source', 'deploy');
@@ -304,26 +304,7 @@ export class DeployResultFormatter extends ResultFormatter {
304304
}
305305
);
306306
}
307-
const codeCoverage = ensureArray(this.result?.response?.details?.runTestResult?.codeCoverage);
308-
309-
if (codeCoverage.length) {
310-
const coverage = prepCoverageForDisplay(codeCoverage);
311-
312-
this.ux.log('');
313-
this.ux.styledHeader(chalk.blue('Apex Code Coverage'));
314-
this.ux.table(
315-
coverage.map((cov) => ({
316-
name: cov.name,
317-
numLocations: cov.numLocations,
318-
lineNotCovered: cov.locationsNotCovered,
319-
})),
320-
{
321-
name: { header: 'Name' },
322-
numLocations: { header: '% Covered' },
323-
lineNotCovered: { header: 'Uncovered Lines' },
324-
}
325-
);
326-
}
307+
maybePrintCodeCoverageTable(this.result.response.details?.runTestResult?.codeCoverage, this.ux);
327308
}
328309

329310
protected verboseTestTime(): void {

src/formatters/mdapi/mdDeployResultFormatter.ts

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
import { ensureArray } from '@salesforce/kit';
2020
import { Ux } from '@salesforce/sf-plugins-core';
2121
import { CoverageResultsFileInfo, ResultFormatter, ResultFormatterOptions } from '../resultFormatter';
22-
import { prepCoverageForDisplay } from '../../coverageUtils';
22+
import { maybePrintCodeCoverageTable } from '../codeCoverageTable';
2323

2424
Messages.importMessagesDirectory(__dirname);
2525
const messages = Messages.loadMessages('@salesforce/plugin-source', 'md.deploy');
@@ -212,28 +212,7 @@ export class MdDeployResultFormatter extends ResultFormatter {
212212
}
213213
);
214214
}
215-
const codeCoverage = ensureArray(this.result?.response?.details?.runTestResult?.codeCoverage);
216-
217-
if (codeCoverage.length) {
218-
const coverage = prepCoverageForDisplay(codeCoverage);
219-
220-
this.ux.log('');
221-
this.ux.styledHeader(chalk.blue('Apex Code Coverage'));
222-
223-
// TODO: unsure about locationsNotCovered vs lineNotCovered
224-
this.ux.table(
225-
coverage.map((entry) => ({
226-
name: entry.name,
227-
numLocations: entry.numLocations,
228-
lineNotCovered: entry.locationsNotCovered,
229-
})),
230-
{
231-
name: { header: 'Name' },
232-
numLocations: { header: '% Covered' },
233-
lineNotCovered: { header: 'Uncovered Lines' },
234-
}
235-
);
236-
}
215+
maybePrintCodeCoverageTable(this.result.response.details?.runTestResult?.codeCoverage, this.ux);
237216
}
238217

239218
protected verboseTestTime(): void {

0 commit comments

Comments
 (0)