Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Badge Color Config #91

Merged
merged 3 commits into from
May 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,13 @@
npm run istanbul-badges-readme --logo="jest"
```

#### Configure Badge Color
- You can configure the color threshold of the badges by passing the `--colors` argument. If you want red badges for a code coverage below 50% and yellow badges for a coverage below 60%, you'd do this:
```bash
npm run istanbul-badges-readme --colors=red:50,yellow:60
```


#### Exit code

- To exit with **1** code on validation errors (eg.: _README doesn't exist_, or _coverage directory doesn't exist_) or on editing errors (eg.: cannot write to README due to lack of permissions). The default exit code is **0**. Set a different one by using **--exitCode** argument.
Expand Down
34 changes: 29 additions & 5 deletions src/editor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from 'fs';
import { getArgumentValue } from './arguments';
import { readmePathConst, coveragePathConst, hashesConst, coverageUrlConst, badgeStyles } from './constants';
import { getCoveragePath, getReadmePath, readFileAsync } from './helpers';
import { getCoveragePath, getReadmePath, readFileAsync, parseColorConfig } from './helpers';
import { logger } from './logger';
import { Colors, Hashes, Report } from './types';

Expand All @@ -23,17 +23,40 @@ export const getReadmeHashes = (readmeFile: string): Hashes[] => {
return filteredHashes as unknown as Hashes[];
};

export const getCoverageColor = (coverage: number): Colors => {
if (coverage < 80) {


/**
* Determines the color representation of code coverage based on coverage percentage.
* Optionally uses a provided string to configure custom color thresholds.
*
* @param {number} coverage - The code coverage percentage to evaluate.
* @param {string | false} colorConfigString - A string to configure custom color thresholds, or false to use defaults.
* @returns {Colors} - The color associated with the given code coverage percentage, based on either default or custom thresholds.
*/
export const getCoverageColor = (coverage: number, colorConfigString?: string | false): Colors => {
const defaultThresholds = {
red: 80,
yellow: 90,
};

let colorThresholds = defaultThresholds;

if (colorConfigString) {
colorThresholds = parseColorConfig(colorConfigString);
}

// Adjusting to use dynamic color thresholds from colorConfigString if provided
if (coverage < colorThresholds.red) {
return 'red';
}
if (coverage < 90) {
if (coverage < colorThresholds.yellow) {
return 'yellow';
}

return 'brightgreen';
};


export const getCoverageBadge = (coverageFile: string, hashKey: string): string | boolean => {
logInfo(`- Getting coverage badge url for ${hashKey}...`);

Expand All @@ -45,7 +68,8 @@ export const getCoverageBadge = (coverageFile: string, hashKey: string): string
}

const coverage: number = parsedCoverage.total[hashKey].pct;
const color = getCoverageColor(coverage);
const customColors = getArgumentValue('colors');
const color = getCoverageColor(coverage, customColors);
const customLabel = getArgumentValue(`${hashKey}Label`);
const customBadgeStyle = getArgumentValue('style');
const customBadgeLogo = getArgumentValue('logo');
Expand Down
29 changes: 29 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,32 @@ export const getExitCodeOnError = (): number | undefined => {

return argExitCode && !isNaN(argExitCode) ? argExitCode : undefined;
};

/**
* Parses a string representing colors and their corresponding numeric values into an object.
* The input string should be formatted as "color:value", with multiple color-value pairs separated by commas.
* This function specifically expects "red", "yellow", and "brightgreen" as the only valid colors in the string.
*
* @param {string} colorsString - The string representation of colors and their values to parse.
* @returns {{ red: number; yellow: number; brightgreen: number }} An object with keys "red", "yellow", and "brightgreen",
* each mapped to their numeric value as specified in the input string.
*/
export const parseColorConfig = (colorsString: string): {
red: number;
yellow: number;
} => {
if (!colorsString) {
return { red: 80, yellow: 90 };
}

return colorsString.split(',').reduce((acc, colorPair) => {
const [color, value] = colorPair.split(':');
if (color === 'red' || color === 'yellow') {
acc[color] = parseInt(value, 10);
}
return acc;
}, {} as { red: number; yellow: number; });
};



56 changes: 43 additions & 13 deletions tests/editor.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fs from 'fs';
import path from 'path';
import { getReadmeHashes, getCoverageColor, getCoverageBadge, getNewReadme, writeNewReadme } from '../src/editor';
import { getReadmeHashes, getCoverageBadge, getCoverageColor, getNewReadme, writeNewReadme } from '../src/editor';
import { parseColorConfig } from '../src/helpers';

describe('Tests editor', () => {
afterEach(() => {
Expand All @@ -14,20 +15,49 @@ describe('Tests editor', () => {
expect(readmeHashes.length).toEqual(0);
});

it('should getCoverageColor from both red, yellow and green', () => {
const colorsMap: Record<any, string> = {
90: 'brightgreen',
80: 'yellow',
default: 'red',
};
describe('parseColorConfig', () => {
it('correctly parses a valid color configuration string', () => {
const inputString = 'red:70,yellow:85';
const expectedResult = { red: 70, yellow: 85 };
expect(parseColorConfig(inputString)).toEqual(expectedResult);
});

it('ignores invalid color names', () => {
const inputString = 'red:70,yellow:85,blue:95';
const expectedResult = { red: 70, yellow: 85 };
expect(parseColorConfig(inputString)).toEqual(expectedResult);
});

it('returns default colors for an empty string', () => {
const inputString = '';
const expectedResult = { red: 80, yellow: 90 };
expect(parseColorConfig(inputString)).toEqual(expectedResult);
});
});

const checkColor = (threshold: number) =>
expect(getCoverageColor(threshold)).toEqual(colorsMap[threshold] ? colorsMap[threshold] : colorsMap.default);
describe('getCoverageColor', () => {
test.each`
coverage | expectedColor
${75} | ${'red'}
${85} | ${'yellow'}
${95} | ${'brightgreen'}
`(
'returns $expectedColor for a coverage of $coverage using default thresholds',
({ coverage, expectedColor }) => {
expect(getCoverageColor(coverage)).toBe(expectedColor);
}
);

checkColor(90);
checkColor(80);
checkColor(79);
checkColor(10);
test('uses custom thresholds if provided', () => {
const customConfig = 'red:70,yellow:85';
expect(getCoverageColor(65, customConfig)).toBe('red');
expect(getCoverageColor(75, customConfig)).toBe('yellow');
expect(getCoverageColor(90, customConfig)).toBe('brightgreen');
});

test('returns brightgreen for coverage above 100', () => {
expect(getCoverageColor(105)).toBe('brightgreen');
});
});

it('should getCoverageBadge from coverageFile', () => {
Expand Down