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 #168
  • Loading branch information
guyfedwards committed Nov 2, 2018
1 parent ea09e05 commit ee62702
Show file tree
Hide file tree
Showing 23 changed files with 6,008 additions and 38 deletions.
20 changes: 18 additions & 2 deletions __tests__/integrations/cli/__snapshots__/test-exit-value.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@ Object {
}
`;

exports[`CLI exit value should exit with code 0 when uncovered_count <= thresholdUncovered 1`] = `
Object {
"error": null,
"exitCode": 0,
}
`;

exports[`CLI exit value should exit with code 2 when total coverage is lower than the custom threshold 1`] = `
Object {
"error": [Error: Command failed: flow-coverage-report.js -i "src/*.js" --threshold 22
Flow Coverage 16% is below the required threshold 22%
Flow Coverage: 16% is below the required threshold 22%
],
"exitCode": 2,
}
Expand All @@ -19,7 +26,16 @@ Flow Coverage 16% is below the required threshold 22%
exports[`CLI exit value should exit with code 2 when total coverage is lower than the default threshold 1`] = `
Object {
"error": [Error: Command failed: flow-coverage-report.js -i "src/*.js"
Flow Coverage 16% is below the required threshold 80%
Flow Coverage: 16% is below the required threshold 80%
],
"exitCode": 2,
}
`;

exports[`CLI exit value should exit with code 2 when uncovered_count > thresholdUncovered 1`] = `
Object {
"error": [Error: Command failed: flow-coverage-report.js -i "src/*.js" --threshold-uncovered 3
Flow Coverage: uncovered count is higher than the uncoveredThreshold
],
"exitCode": 2,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
exports[`--percent-decimals option should round percent values using the requested precision 1`] = `
Object {
"error": [Error: Command failed: flow-coverage-report.js --percent-decimals 2 -i "src/**.js"
Flow Coverage 16.67% is below the required threshold 80%
Flow Coverage: 16.67% is below the required threshold 80%
],
"exitCode": 2,
"filteredStdout": Array [
"│ src/main.js │ flow │ 16.67 % │ 6 │ 1 │ 5 │",
"│ project-decimal-coverage │ 16.67 % │ 6 │ 1 │ 5 │",
],
"stderr": "Flow Coverage 16.67% is below the required threshold 80%
"stderr": "Flow Coverage: 16.67% is below the required threshold 80%
",
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Object {
"filteredStdout": Array [
"│ src/url.js │ flow │ 0 % │ 9 │ 0 │ 9 │",
],
"stderr": "Flow Coverage 0% is below the required threshold 80%
"stderr": "Flow Coverage: 0% is below the required threshold 80%
",
}
`;
18 changes: 18 additions & 0 deletions __tests__/integrations/cli/test-exit-value.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,22 @@ describe('CLI exit value', async () => {

expect({exitCode, error}).toMatchSnapshot();
});

it('should exit with code 2 when uncovered_count > thresholdUncovered', async () => {
const {exitCode, error} = await runFlowCoverageReport([
'-i', `"src/*.js"`,
'--threshold-uncovered', '3'
], {cwd: testProjectDir});

expect({exitCode, error}).toMatchSnapshot();
});

it('should exit with code 0 when uncovered_count <= thresholdUncovered', async () => {
const {exitCode, error} = await runFlowCoverageReport([
'-i', `"src/*.js"`,
'--threshold-uncovered', '10'
], {cwd: testProjectDir});

expect({exitCode, error}).toMatchSnapshot();
});
});
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
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
15 changes: 12 additions & 3 deletions src/lib/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,19 @@ exports.run = () => {
}

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

if (thresholdUncovered && 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 (!thresholdUncovered && percent < threshold) {
console.error(
`Flow Coverage ${percent}% is below the required threshold ${threshold}%`
`Flow Coverage: ${percent}% is below the required threshold ${threshold}%`
);
process.exit(2); // eslint-disable-line unicorn/no-process-exit
}
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
Loading

0 comments on commit ee62702

Please sign in to comment.