Skip to content

Commit

Permalink
fix: properly render uncovered lines column (#950)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
mshanemc and AllanOricil authored Sep 19, 2023
1 parent 339d6e5 commit cb48d08
Show file tree
Hide file tree
Showing 5 changed files with 359 additions and 190 deletions.
33 changes: 19 additions & 14 deletions src/coverageUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,32 @@ function mapTestResults(testResults: Failures[] | Successes[]): ApexTestResultDa
});
}

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

coverage.forEach((cov: CodeCoverage & { lineNotCovered: string }) => {
const numLocationsNum = parseInt(cov.numLocations, 10);
const numLocationsNotCovered = parseInt(cov.numLocationsNotCovered, 10);
const color = numLocationsNotCovered > 0 ? chalk.red : chalk.green;

const coverageDecimal = parseFloat(((numLocationsNum - numLocationsNotCovered) / numLocationsNum).toFixed(2));
const pctCovered = numLocationsNum > 0 ? coverageDecimal * 100 : 100;
cov.numLocations = color(`${pctCovered}%`);

cov.lineNotCovered = cov.locationsNotCovered
return coverage.map((cov) => ({
...cov,
numLocations: stylePercentage(calculateCoveragePercent(cov)),
lineNotCovered: cov.locationsNotCovered
? ensureArray(cov.locationsNotCovered)
.map((location) => location.line)
.join(',')
: '';
});
return coverage;
: '',
}));
}

const stylePercentage = (pct: number): string => {
const color = pct < 75 ? chalk.red : pct >= 90 ? chalk.green : chalk.yellow;
return color(`${pct}%`);
};

const calculateCoveragePercent = (cov: CodeCoverage): number => {
const numLocationsNum = parseInt(cov.numLocations, 10);
const numLocationsNotCovered = parseInt(cov.numLocationsNotCovered, 10);
const coverageDecimal = parseFloat(((numLocationsNum - numLocationsNotCovered) / numLocationsNum).toFixed(2));
return numLocationsNum > 0 ? coverageDecimal * 100 : 100;
};

function generateCoveredLines(cov: CodeCoverage): [number[], number[]] {
const numCovered = parseInt(cov.numLocations, 10);
const numUncovered = parseInt(cov.numLocationsNotCovered, 10);
Expand Down
42 changes: 42 additions & 0 deletions src/formatters/codeCoverageTable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2023, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { ensureArray } from '@salesforce/kit';
import { Ux } from '@salesforce/sf-plugins-core';
import { CodeCoverage } from '@salesforce/source-deploy-retrieve';
import chalk = require('chalk');
import { prepCoverageForDisplay } from '../coverageUtils';

/**
* prints a table of formatted code coverage results if there are any
* This is a no-op if tests didn't run or there is no coverage
*
* @param coverageFromMdapiResult
* @param ux
*/
export const maybePrintCodeCoverageTable = (coverageFromMdapiResult: CodeCoverage | CodeCoverage[], ux: Ux): void => {
const codeCoverage = ensureArray(coverageFromMdapiResult);

if (codeCoverage.length) {
const coverage = prepCoverageForDisplay(codeCoverage);

ux.log('');
ux.styledHeader(chalk.blue('Apex Code Coverage'));

ux.table(
coverage.map((entry) => ({
name: entry.name,
numLocations: entry.numLocations,
lineNotCovered: entry.lineNotCovered,
})),
{
name: { header: 'Name' },
numLocations: { header: '% Covered' },
lineNotCovered: { header: 'Uncovered Lines' },
}
);
}
};
23 changes: 2 additions & 21 deletions src/formatters/deployResultFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ import {
Successes,
} from '@salesforce/source-deploy-retrieve';
import { Ux } from '@salesforce/sf-plugins-core';
import { prepCoverageForDisplay } from '../coverageUtils';
import { ResultFormatter, ResultFormatterOptions } from './resultFormatter';
import { MdDeployResult } from './mdapi/mdDeployResultFormatter';
import { maybePrintCodeCoverageTable } from './codeCoverageTable';

Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/plugin-source', 'deploy');
Expand Down Expand Up @@ -304,26 +304,7 @@ export class DeployResultFormatter extends ResultFormatter {
}
);
}
const codeCoverage = ensureArray(this.result?.response?.details?.runTestResult?.codeCoverage);

if (codeCoverage.length) {
const coverage = prepCoverageForDisplay(codeCoverage);

this.ux.log('');
this.ux.styledHeader(chalk.blue('Apex Code Coverage'));
this.ux.table(
coverage.map((cov) => ({
name: cov.name,
numLocations: cov.numLocations,
lineNotCovered: cov.locationsNotCovered,
})),
{
name: { header: 'Name' },
numLocations: { header: '% Covered' },
lineNotCovered: { header: 'Uncovered Lines' },
}
);
}
maybePrintCodeCoverageTable(this.result.response.details?.runTestResult?.codeCoverage, this.ux);
}

protected verboseTestTime(): void {
Expand Down
25 changes: 2 additions & 23 deletions src/formatters/mdapi/mdDeployResultFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
import { ensureArray } from '@salesforce/kit';
import { Ux } from '@salesforce/sf-plugins-core';
import { CoverageResultsFileInfo, ResultFormatter, ResultFormatterOptions } from '../resultFormatter';
import { prepCoverageForDisplay } from '../../coverageUtils';
import { maybePrintCodeCoverageTable } from '../codeCoverageTable';

Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/plugin-source', 'md.deploy');
Expand Down Expand Up @@ -212,28 +212,7 @@ export class MdDeployResultFormatter extends ResultFormatter {
}
);
}
const codeCoverage = ensureArray(this.result?.response?.details?.runTestResult?.codeCoverage);

if (codeCoverage.length) {
const coverage = prepCoverageForDisplay(codeCoverage);

this.ux.log('');
this.ux.styledHeader(chalk.blue('Apex Code Coverage'));

// TODO: unsure about locationsNotCovered vs lineNotCovered
this.ux.table(
coverage.map((entry) => ({
name: entry.name,
numLocations: entry.numLocations,
lineNotCovered: entry.locationsNotCovered,
})),
{
name: { header: 'Name' },
numLocations: { header: '% Covered' },
lineNotCovered: { header: 'Uncovered Lines' },
}
);
}
maybePrintCodeCoverageTable(this.result.response.details?.runTestResult?.codeCoverage, this.ux);
}

protected verboseTestTime(): void {
Expand Down
Loading

0 comments on commit cb48d08

Please sign in to comment.