Skip to content

Commit

Permalink
add threshold-uncovered option
Browse files Browse the repository at this point in the history
allows threshold to be set by number of uncovered lines instead of percentage
of coverage.

In a codebase which continuously grows, if you fix the number of lines uncovered
then you (loosely) enforce that all new code should be covered.

closes rpl#168
  • Loading branch information
guyfedwards committed Nov 2, 2018
1 parent ea09e05 commit 5c853d6
Show file tree
Hide file tree
Showing 20 changed files with 5,979 additions and 29 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"scripts": {
"ava": "nyc ava --verbose",
"build": "rimraf dist && babel -d dist src --only src/lib --source-maps",
"dev": "babel -d dist src --only src/lib --source-maps --watch",
"update-flow-typed": "rimraf flow-typed && flow-typed install -s -i dev",
"flow-coverage": "bin/flow-coverage-report.js",
"flow-check": "flow check",
Expand Down Expand Up @@ -185,7 +186,7 @@
"\\.jsx$": "babel-jest"
},
"transformIgnorePatterns": [
"node_modules/(?!(svgo)/)"
"node_modules/(?!(svgo|badge-up)/)"
],
"testEnvironment": "node",
"testMatch": [
Expand Down
1 change: 1 addition & 0 deletions src/__tests__/fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const FLOW_COVERAGE_SUMMARY_DATA = {
covered_count: 5,
uncovered_count: 5,
threshold: 40,
uncoveredThreshold: 3,
percent: 50,
globIncludePatterns: [firstGlob, secondGlob],
files: allFiles.reduce((acc, filename) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<FlowCoverageMeterBar /> 1`] = `
exports[`<FlowCoverageMeterBar /> percent <= threshold 1`] = `
<div
className="row red"
style={
Expand All @@ -11,3 +11,15 @@ exports[`<FlowCoverageMeterBar /> 1`] = `
}
/>
`;

exports[`<FlowCoverageMeterBar /> uncoveredCount <= thresholdUncovered 1`] = `
<div
className="row green"
style={
Object {
"height": 12,
"padding": 0,
}
}
/>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,45 @@ exports[`<FlowCoverageSummaryTable /> 2`] = `
</tbody>
</table>
`;

exports[`<FlowCoverageSummaryTable /> 3`] = `
<table
className="ui small celled table"
>
<thead>
<tr>
<th>
Percent
</th>
<th>
Total
</th>
<th>
Covered
</th>
<th>
Uncovered
</th>
</tr>
</thead>
<tbody>
<tr
className="positive"
>
<td>
50
%
</td>
<td>
10
</td>
<td>
5
</td>
<td>
5
</td>
</tr>
</tbody>
</table>
`;
28 changes: 20 additions & 8 deletions src/__tests__/react-components/test-coverage-meter-bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,26 @@ import {BASE_DIR} from './common';

const REACT_COMPONENT = `${BASE_DIR}/coverage-meter-bar`;

test('<FlowCoverageMeterBar />', () => {
const FlowCoverageMeterBar = require(REACT_COMPONENT).default;
const props = {
percent: 20,
threshold: 80
};
const tree = renderer.create(<FlowCoverageMeterBar {...props}/>).toJSON();
expect(tree).toMatchSnapshot();
describe('<FlowCoverageMeterBar />', () => {
test('percent <= threshold', () => {
const FlowCoverageMeterBar = require(REACT_COMPONENT).default;
const props = {
percent: 20,
threshold: 80
};
const tree = renderer.create(<FlowCoverageMeterBar {...props}/>).toJSON();
expect(tree).toMatchSnapshot();
});

test('uncoveredCount <= thresholdUncovered', () => {
const FlowCoverageMeterBar = require(REACT_COMPONENT).default;
const props = {
uncoveredCount: 20,
thresholdUncovered: 30
};
const tree = renderer.create(<FlowCoverageMeterBar {...props}/>).toJSON();
expect(tree).toMatchSnapshot();
});
});

test.skip('<FlowCoverageMeterBar /> with missing props');
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,19 @@ test('<FlowCoverageSummaryTable />', () => {
const positiveSummaryTree = renderer.create(<FlowCoverageSummaryTable {...positiveSummaryProps}/>).toJSON();
expect(positiveSummaryTree).toMatchSnapshot();

// Expect positive with higher threshold.
// Expect negative with higher threshold.
const negativeSummaryProps = {
coverageSummaryData: {...FLOW_COVERAGE_SUMMARY_DATA, threshold: 90}
};
const negativeSummaryTree = renderer.create(<FlowCoverageSummaryTable {...negativeSummaryProps}/>).toJSON();
expect(negativeSummaryTree).toMatchSnapshot();

// Expect negative with uncovered_count >= thresholdUncovered
const negativeSummaryUncoveredProps = {
coverageSummaryData: {...FLOW_COVERAGE_SUMMARY_DATA, uncoveredThreshold: 4}
};
const negativeSummaryUncoveredTree = renderer.create(<FlowCoverageSummaryTable {...negativeSummaryUncoveredProps}/>).toJSON();
expect(negativeSummaryUncoveredTree).toMatchSnapshot();
});

test.skip('<FlowCoverageSummaryTable /> with missing props');
2 changes: 2 additions & 0 deletions src/__tests__/test-flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ const testCollectFlowCoverage = async ({
globIncludePatterns,
globExcludePatterns,
threshold: 80,
thresholdUncovered: 3,
concurrentFiles: 5,
strictCoverage,
excludeNonFlow
Expand Down Expand Up @@ -462,6 +463,7 @@ const testCollectFlowCoverage = async ({
concurrentFiles: 5,
percent: 50,
threshold: 80,
thresholdUncovered: 3,
/* eslint-disable camelcase */
covered_count: excludeNonFlow ? 4 : 5,
uncovered_count: excludeNonFlow ? 4 : 5,
Expand Down
10 changes: 10 additions & 0 deletions src/__tests__/test-index.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,14 @@ it('generateFlowCoverageReport', async () => {
expect(exception3 && exception3.message).toMatch(
/empty globIncludePatterns option/
);

let exception4;
try {
await generateFlowCoverageReport({...options, threshold: undefined, thresholdUncovered: undefined});
} catch (err) {
exception4 = err;
}
expect(exception4 && exception4.message).toMatch(
/threshold or thresholdUncovered option is mandatory/
);
});
4 changes: 4 additions & 0 deletions src/lib/cli/args.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ export default function processArgv(argv: Array<string>): any {
type: 'number',
describe: `the minimum coverage percent requested (defaults to ${defaultConfig.threshold})`
})
.options('threshold-uncovered', {
type: 'number',
describe: `the maximum number of uncovered lines`
})
.options('percent-decimals', {
alias: ['percentDecimanls'],
type: 'number',
Expand Down
4 changes: 3 additions & 1 deletion src/lib/cli/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type ConfigParams = {|
globExcludePatterns?: Array<string>,
globIncludePatterns: Array<string>,
threshold: number,
thresholdUncovered: number,
percentDecimals: number,
outputDir: string,
concurrentFiles?: number,
Expand Down Expand Up @@ -58,11 +59,12 @@ export const defaultConfig: DefaultConfigParams = {
projectDir: path.resolve(process.cwd()),
globExcludePatterns: ['node_modules/**'],
globIncludePatterns: [],
threshold: 80,
percentDecimals: 0,
outputDir: './flow-coverage',
concurrentFiles: 1,
strictCoverage: false,
threshold: 80,
thresholdUncovered: 0,
excludeNonFlow: false,
noConfig: false,
htmlTemplateOptions: {
Expand Down
11 changes: 10 additions & 1 deletion src/lib/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,16 @@ exports.run = () => {
}

generateFlowCoverageReport({...args}).then(([coverageSummaryData]) => {
const {percent, threshold} = coverageSummaryData;
/* eslint-disable camelcase */
const {percent, threshold, thresholdUncovered, uncovered_count} = coverageSummaryData;

if (uncovered_count > thresholdUncovered) {
/* eslint-enable camelcase */
console.error(
`Flow Coverage uncovered count is higher than the uncoveredThreshold`
);
process.exit(2); // eslint-disable-line unicorn/no-process-exit
}
if (percent < threshold) {
console.error(
`Flow Coverage ${percent}% is below the required threshold ${threshold}%`
Expand Down
7 changes: 5 additions & 2 deletions src/lib/components/body-coverage-sourcefile.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export default function HTMLReportBodySourceFile(props: FlowCoverageSourceFileRe
}
const {percent} = coverageData;

const threshold = coverageSummaryData.threshold;
const {threshold, thresholdUncovered} = coverageSummaryData;

if (!threshold) {
throw new Error('Missing threshold in coverageSummaryData');
Expand All @@ -84,9 +84,11 @@ export default function HTMLReportBodySourceFile(props: FlowCoverageSourceFileRe

let meterBar;

/* eslint-disable camelcase */
if (props.htmlTemplateOptions && props.htmlTemplateOptions.showMeterBar) {
meterBar = <FlowCoverageMeterBar percent={percent} threshold={threshold}/>;
meterBar = <FlowCoverageMeterBar percent={percent} threshold={threshold} thresholdUncovered={thresholdUncovered} uncoveredCount={uncovered_count}/>;
}
/* eslint-enable camelcase */

return (
<body>
Expand Down Expand Up @@ -117,6 +119,7 @@ export default function HTMLReportBodySourceFile(props: FlowCoverageSourceFileRe
isFlow: coverageData.isFlow,
percent,
threshold,
thresholdUncovered,
/* eslint-disable camelcase */
covered_count,
uncovered_count
Expand Down
10 changes: 9 additions & 1 deletion src/lib/components/body-coverage-summary.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default function HTMLReportBodySummary(props: FlowCoverageSummaryReportPr
flowCoverageStderr: fileSummary.flowCoverageStderr,
disableLink: false,
threshold: summary.threshold,
thresholdUncovered: summary.thresholdUncovered,
annotation: fileSummary.annotation,
percent: fileSummary.percent,
/* eslint-disable camelcase */
Expand All @@ -61,7 +62,14 @@ export default function HTMLReportBodySummary(props: FlowCoverageSummaryReportPr
let meterBar;

if (props.htmlTemplateOptions && props.htmlTemplateOptions.showMeterBar) {
meterBar = <FlowCoverageMeterBar percent={percent} threshold={summary.threshold}/>;
meterBar = (
<FlowCoverageMeterBar
percent={percent}
threshold={summary.threshold}
thresholdUncovered={summary.thresholdUncovered}
uncoveredCount={summary.uncovered_count}
/>
);
}

return (
Expand Down
13 changes: 11 additions & 2 deletions src/lib/components/coverage-file-table-row.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default function FlowCoverageFileTableRow(
percent: number,
disableLink: boolean,
threshold: number,
thresholdUncovered: number,
isError: boolean,
isFlow: boolean,
flowCoverageError: ?string,
Expand All @@ -37,10 +38,18 @@ export default function FlowCoverageFileTableRow(
isError,
isFlow,
disableLink,
threshold
threshold,
thresholdUncovered
} = props;

const aboveThreshold = percent >= threshold;
let aboveThreshold;

if (thresholdUncovered) {
aboveThreshold = uncovered_count <= thresholdUncovered;
} else {
aboveThreshold = percent >= threshold;
}

let className = (!isError && isFlow && aboveThreshold) ?
'positive' : 'negative';

Expand Down
23 changes: 20 additions & 3 deletions src/lib/components/coverage-meter-bar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,27 @@
import React from 'react';

export default function FlowCoverageMeterBar(
props: {percent: number, threshold: number}
props: {
percent: number,
threshold: number,
thresholdUncovered: number,
uncoveredCount: number
}
) {
const threshold = props.threshold;
const color = props.percent >= threshold ? 'green' : 'red';
const {
threshold,
thresholdUncovered,
uncoveredCount,
percent
} = props;

let color;
if (thresholdUncovered) {
color = uncoveredCount <= thresholdUncovered ? 'green' : 'red';
} else {
color = percent >= threshold ? 'green' : 'red';
}

const style = {
padding: 0, height: 12
};
Expand Down
9 changes: 8 additions & 1 deletion src/lib/components/coverage-summary-table.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ export default function FlowCoverageSummaryTable(props: FlowCoverageSummaryRepor
}
const summary = props.coverageSummaryData;
const percent = summary.percent;
const className = percent >= summary.threshold ? 'positive' : 'negative';
const threshold = summary.thresholdUncovered || summary.threshold;
let className;

if (summary.thresholdUncovered) {
className = summary.uncovered_count <= threshold ? 'positive' : 'negative';
} else {
className = percent >= threshold ? 'positive' : 'negative';
}

return (
<table className="ui small celled table">
Expand Down
3 changes: 3 additions & 0 deletions src/lib/flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ export type FlowCoverageSummaryData = {
uncovered_count: number,
percent: number,
threshold: number,
thresholdUncovered: number,
strictCoverage: boolean,
generatedAt: string,
flowStatus: FlowStatus,
Expand Down Expand Up @@ -387,6 +388,7 @@ export function collectFlowCoverage(
globIncludePatterns,
globExcludePatterns = [],
threshold,
thresholdUncovered,
percentDecimals,
concurrentFiles = 1,
strictCoverage,
Expand All @@ -407,6 +409,7 @@ export function collectFlowCoverage(

const coverageSummaryData: FlowCoverageSummaryData = {
threshold,
thresholdUncovered,
covered_count: 0, uncovered_count: 0, // eslint-disable-line camelcase
percent: 0,
generatedAt: coverageGeneratedAt,
Expand Down
4 changes: 2 additions & 2 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ export default async function generateFlowCoverageReport(opts: FlowCoverageRepor
return Promise.reject(new TypeError('empty globIncludePatterns option'));
}

if (!opts.threshold) {
return Promise.reject(new TypeError('threshold option is mandatory'));
if (!opts.threshold && !opts.thresholdUncovered) {
return Promise.reject(new TypeError('threshold or thresholdUncovered option is mandatory'));
}

const coverageData: FlowCoverageSummaryData = await collectFlowCoverage(
Expand Down
Loading

0 comments on commit 5c853d6

Please sign in to comment.