Add support for rgb, rgba, hsl, hsla, css named colors (#1635)
* add support for rgb, rgb, css named colors
* add support for hsl, hsla & add color-validate
* update makeBadge test, better coverage
* re-add comment
* add comment for supported colors
* dynamic badge gen, remove 'hex'
* add support for 1.0 opacity & fix 101-109
* fix colorscheme tests
* remove extra tests
* add test for negative values
* add test for uppercase & mixed case colors
* fix mixed case/uppercase test
* allow whitespace around color
* update test error messages
* add comments
* add more uppercase test
* update error message
* default to grey/red if invalid color chosen
default colorscheme:
colorA: grey
colorB: red
* Revert "default to grey/red if invalid color chosen"
This reverts commit 10db0c6d74.
Reverted as this affects the CLI version/when no color specified.
* validColor -> isCSSColor
* assignColor function
* update tests to use sazerac
This commit is contained in:
@@ -64,7 +64,7 @@ export default class DynamicBadgeMaker extends React.Component {
|
||||
className="short"
|
||||
value={this.state.color}
|
||||
onChange={event => this.setState({ color: event.target.value })}
|
||||
placeholder="hex color" /> {}
|
||||
placeholder="color" /> {}
|
||||
<input
|
||||
className="short"
|
||||
value={this.state.prefix}
|
||||
|
||||
@@ -190,13 +190,13 @@ export default class Usage extends React.PureComponent {
|
||||
<td>
|
||||
<code>?colorA=abcdef</code>
|
||||
</td>
|
||||
<td>Set background of the left part (hex color only)</td>
|
||||
<td>Set background of the left part (hex, rgb, rgba, hsl, hsla and css named colors supported)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>?colorB=fedcba</code>
|
||||
</td>
|
||||
<td>Set background of the right part (hex color only)</td>
|
||||
<td>Set background of the right part (hex, rgb, rgba, hsl, hsla and css named colors supported)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
+14
-6
@@ -1,6 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
const isCSSColor = require('is-css-color');
|
||||
const logos = require('./load-logos')();
|
||||
const colorschemes = require('./colorscheme.json');
|
||||
|
||||
function toArray(val) {
|
||||
if (val === undefined) {
|
||||
@@ -30,12 +32,12 @@ function prependPrefix(s, prefix) {
|
||||
}
|
||||
}
|
||||
|
||||
function isSixHex (s){
|
||||
return s !== undefined && /^[0-9a-fA-F]{6}$/.test(s);
|
||||
function isHexColor (s = ''){
|
||||
return /^([\da-f]{3}){1,2}$/i.test(s);
|
||||
}
|
||||
|
||||
function makeColor(color) {
|
||||
if (isSixHex(color)) {
|
||||
if (isHexColor(color)) {
|
||||
return '#' + color;
|
||||
} else {
|
||||
return color;
|
||||
@@ -47,12 +49,18 @@ function makeColorB(defaultColor, overrides) {
|
||||
}
|
||||
|
||||
function setBadgeColor(badgeData, color) {
|
||||
if (isSixHex(color)) {
|
||||
if (isHexColor(color)) {
|
||||
badgeData.colorB = '#' + color;
|
||||
delete badgeData.colorscheme;
|
||||
} else {
|
||||
} else if (colorschemes[color] !== undefined){
|
||||
badgeData.colorscheme = color;
|
||||
delete badgeData.colorB;
|
||||
} else if (isCSSColor(color)){
|
||||
badgeData.colorB = color;
|
||||
delete badgeData.colorscheme;
|
||||
} else {
|
||||
badgeData.colorscheme = 'red';
|
||||
delete badgeData.colorB;
|
||||
}
|
||||
return badgeData;
|
||||
}
|
||||
@@ -108,7 +116,7 @@ module.exports = {
|
||||
toArray,
|
||||
prependPrefix,
|
||||
isDataUri,
|
||||
isSixHex,
|
||||
isHexColor,
|
||||
makeLabel,
|
||||
makeLogo,
|
||||
makeBadgeData,
|
||||
|
||||
+10
-3
@@ -5,7 +5,7 @@ const { test, given, forCases } = require('sazerac');
|
||||
const {
|
||||
isDataUri,
|
||||
prependPrefix,
|
||||
isSixHex,
|
||||
isHexColor,
|
||||
makeLabel,
|
||||
makeLogo,
|
||||
makeBadgeData,
|
||||
@@ -27,8 +27,11 @@ describe('Badge data helpers', function() {
|
||||
]).expect(false);
|
||||
});
|
||||
|
||||
test(isSixHex, () => {
|
||||
given('f00bae').expect(true);
|
||||
test(isHexColor, () => {
|
||||
forCases([
|
||||
given('f00bae'),
|
||||
given('4c1'),
|
||||
]).expect(true);
|
||||
forCases([
|
||||
given('f00bar'),
|
||||
given(''),
|
||||
@@ -81,6 +84,10 @@ describe('Badge data helpers', function() {
|
||||
given({}, 'red').expect({ colorscheme: 'red' });
|
||||
given({}, 'f00f00').expect({ colorB: '#f00f00' });
|
||||
given({ colorB: '#f00f00', colorscheme: 'blue' }, 'red').expect({ colorscheme: 'red' });
|
||||
given({ colorB: '#f00f00', colorscheme: 'blue' }, 'blue').expect({ colorscheme: 'blue' });
|
||||
given({ colorB: '#f00f00', colorscheme: 'blue' }, 'papayawhip').expect({ colorB: 'papayawhip' });
|
||||
given({ colorB: '#f00f00', colorscheme: 'blue' }, 'purple').expect({ colorB: 'purple' });
|
||||
given({ colorB: '#b00b00', colorscheme: 'blue' }, '4c1').expect({ colorB: '#4c1' });
|
||||
given({ colorB: '#b00b00', colorscheme: 'blue' }, 'f00f00').expect({ colorB: '#f00f00' });
|
||||
});
|
||||
});
|
||||
|
||||
+17
-13
@@ -5,6 +5,7 @@ const path = require('path');
|
||||
const SVGO = require('svgo');
|
||||
const dot = require('dot');
|
||||
const LruCache = require('./lru-cache');
|
||||
const isCSSColor = require('is-css-color');
|
||||
|
||||
// Holds widths of badge keys (left hand side of badge).
|
||||
const badgeKeyWidthCache = new LruCache(1000);
|
||||
@@ -86,10 +87,18 @@ function capitalize(s) {
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
}
|
||||
|
||||
const definedColorschemes = require(path.join(__dirname, 'colorscheme.json'));
|
||||
// check if colorA/B is a colorscheme else check if it's a valid css3 color else return undefined and let the badge assign the default color
|
||||
function assignColor(color = '', colorschemeType = 'colorB') {
|
||||
if (definedColorschemes[color] !== undefined) {
|
||||
return definedColorschemes[color][colorschemeType] || undefined;
|
||||
} else if (isCSSColor(color)) {
|
||||
return color;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Optimized hex color regex - #1602
|
||||
const cssColor = /^#([\da-f]{3}){1,2}$/i;
|
||||
const definedColorschemes = require(path.join(__dirname, 'colorscheme.json'));
|
||||
|
||||
// Inject the measurer to avoid placing any persistent state in this module.
|
||||
function makeBadge (measurer, {
|
||||
@@ -119,16 +128,11 @@ function makeBadge (measurer, {
|
||||
text = text.map(value => value.toUpperCase());
|
||||
}
|
||||
|
||||
if (colorscheme) {
|
||||
let pickedColorscheme = definedColorschemes[colorscheme];
|
||||
if (!pickedColorscheme) {
|
||||
pickedColorscheme = definedColorschemes.red;
|
||||
}
|
||||
colorA = colorA || pickedColorscheme.colorA;
|
||||
colorB = colorB || pickedColorscheme.colorB;
|
||||
}
|
||||
if (!cssColor.test(colorA)) { colorA = undefined; }
|
||||
if (!cssColor.test(colorB)) { colorB = undefined; }
|
||||
// colorA/B have a higher priority than colorscheme
|
||||
colorA = colorA || colorscheme || undefined;
|
||||
colorB = colorB || colorscheme || undefined;
|
||||
colorA = assignColor(colorA, 'colorA');
|
||||
colorB = assignColor(colorB, 'colorB');
|
||||
|
||||
const [left, right] = text;
|
||||
let leftWidth = badgeKeyWidthCache.get(left);
|
||||
|
||||
+61
-26
@@ -1,33 +1,68 @@
|
||||
'use strict';
|
||||
|
||||
const { test, given, forCases } = require('sazerac');
|
||||
const { expect } = require('chai');
|
||||
const snapshot = require('snap-shot-it');
|
||||
const { _badgeKeyWidthCache } = require('./make-badge');
|
||||
const isSvg = require('is-svg');
|
||||
const testHelpers = require('./make-badge-test-helpers');
|
||||
const colorschemes = require('./colorscheme.json');
|
||||
|
||||
const makeBadge = testHelpers.makeBadge();
|
||||
|
||||
describe('The badge generator', function () {
|
||||
beforeEach(function () {
|
||||
function testColor(color = ''){
|
||||
return JSON.parse(makeBadge({ text: ['name', 'Bob'], colorB: color, format: 'json', template: '_shields_test' })).colorB;
|
||||
}
|
||||
|
||||
describe('The badge generator', () => {
|
||||
beforeEach(() => {
|
||||
_badgeKeyWidthCache.clear();
|
||||
});
|
||||
|
||||
describe('css color regex', function () {
|
||||
it('should check if given valid hex color', function () {
|
||||
const colorsTrue = ["#4c1","#4C1","#abc123","#ABC123"];
|
||||
const colorsFalse = ["red","#123red","#red"];
|
||||
colorsTrue.forEach((color) => {
|
||||
expect(JSON.parse(makeBadge({ text: ['name', 'Bob'], colorB: color, format: 'json', template: '_shields_test' })).colorB).to.equal(color);
|
||||
});
|
||||
colorsFalse.forEach((color) => {
|
||||
expect(JSON.parse(makeBadge({ text: ['name', 'Bob'], colorB: color, format: 'json', template: '_shields_test' })).colorB).to.be.undefined;
|
||||
});
|
||||
describe('color test', () => {
|
||||
test(testColor, () => {
|
||||
// valid hex
|
||||
given('#4c1').expect('#4c1');
|
||||
given('#4C1').expect('#4C1');
|
||||
given('#abc123').expect('#abc123');
|
||||
given('#ABC123').expect('#ABC123');
|
||||
// valid rgb(a)
|
||||
given('rgb(0,128,255)').expect('rgb(0,128,255)');
|
||||
given('rgba(0,128,255,0)').expect('rgba(0,128,255,0)');
|
||||
// valid hsl(a)
|
||||
given('hsl(100, 56%, 10%)').expect('hsl(100, 56%, 10%)');
|
||||
given('hsla(25,20%,0%,0.1)').expect('hsla(25,20%,0%,0.1)');
|
||||
// either a css named color or colorscheme
|
||||
given('papayawhip').expect('papayawhip');
|
||||
given('red').expect(colorschemes['red'].colorB);
|
||||
given('green').expect(colorschemes['green'].colorB);
|
||||
given('blue').expect(colorschemes['blue'].colorB);
|
||||
given('yellow').expect(colorschemes['yellow'].colorB);
|
||||
|
||||
forCases(
|
||||
// invalid hex
|
||||
given('#123red'), // contains letter above F
|
||||
given('#red'), // contains letter above F
|
||||
given('123456'), // contains no # symbol
|
||||
given('123'), // contains no # symbol
|
||||
// invalid rgb(a)
|
||||
given('rgb(220,128,255,0.5)'), // has alpha
|
||||
given('rgba(0,0,255)'), // no alpha
|
||||
// invalid hsl(a)
|
||||
given('hsl(360,50%,50%,0.5)'), // has alpha
|
||||
given('hsla(0,50%,101%)'), // no alpha
|
||||
// neither a css named color nor colorscheme
|
||||
given('notacolor'),
|
||||
given('bluish'),
|
||||
given('almostred'),
|
||||
given('brightmaroon'),
|
||||
given('cactus'),
|
||||
).expect(undefined)
|
||||
});
|
||||
});
|
||||
|
||||
describe('SVG', function () {
|
||||
it('should produce SVG', function () {
|
||||
describe('SVG', () => {
|
||||
it('should produce SVG', () => {
|
||||
const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' });
|
||||
expect(svg)
|
||||
.to.satisfy(isSvg)
|
||||
@@ -35,31 +70,31 @@ describe('The badge generator', function () {
|
||||
.and.to.include('grown');
|
||||
});
|
||||
|
||||
it('should always produce the same SVG (unless we have changed something!)', function () {
|
||||
it('should always produce the same SVG (unless we have changed something!)', () => {
|
||||
const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' });
|
||||
snapshot(svg);
|
||||
});
|
||||
|
||||
it('should cache width of badge key', function () {
|
||||
it('should cache width of badge key', () => {
|
||||
makeBadge({ text: ['cached', 'not-cached'], format: 'svg' });
|
||||
expect(_badgeKeyWidthCache.cache).to.have.keys('cached');
|
||||
});
|
||||
});
|
||||
|
||||
describe('JSON', function () {
|
||||
it('should always produce the same JSON (unless we have changed something!)', function () {
|
||||
describe('JSON', () => {
|
||||
it('should always produce the same JSON (unless we have changed something!)', () => {
|
||||
const json = makeBadge({ text: ['cactus', 'grown'], format: 'json' });
|
||||
snapshot(json);
|
||||
});
|
||||
|
||||
it('should replace unknown json template with "default"', function () {
|
||||
it('should replace unknown json template with "default"', () => {
|
||||
const jsonBadgeWithUnknownStyle = makeBadge({ text: ['name', 'Bob'], format: 'json', template: 'unknown_style' });
|
||||
const jsonBadgeWithDefaultStyle = makeBadge({ text: ['name', 'Bob'], format: 'json', template: 'default' });
|
||||
expect(jsonBadgeWithUnknownStyle).to.equal(jsonBadgeWithDefaultStyle);
|
||||
expect(JSON.parse(jsonBadgeWithUnknownStyle)).to.deep.equal({name: "name", value: "Bob"})
|
||||
});
|
||||
|
||||
it('should replace unknown svg template with "flat"', function () {
|
||||
it('should replace unknown svg template with "flat"', () => {
|
||||
const jsonBadgeWithUnknownStyle = makeBadge({ text: ['name', 'Bob'], format: 'svg', template: 'unknown_style' });
|
||||
const jsonBadgeWithDefaultStyle = makeBadge({ text: ['name', 'Bob'], format: 'svg', template: 'flat' });
|
||||
expect(jsonBadgeWithUnknownStyle).to.equal(jsonBadgeWithDefaultStyle)
|
||||
@@ -67,27 +102,27 @@ describe('The badge generator', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('"for-the-badge" template badge generation', function () {
|
||||
describe('"for-the-badge" template badge generation', () => {
|
||||
// https://github.com/badges/shields/issues/1280
|
||||
it('numbers should produce a string', function () {
|
||||
it('numbers should produce a string', () => {
|
||||
const svg = makeBadge({ text: [1998, 1999], format: 'svg', template: 'for-the-badge' });
|
||||
expect(svg).to.include('1998').and.to.include('1999');
|
||||
});
|
||||
|
||||
it('lowercase/mixedcase string should produce uppercase string', function () {
|
||||
it('lowercase/mixedcase string should produce uppercase string', () => {
|
||||
const svg = makeBadge({ text: ["Label", "1 string"], format: 'svg', template: 'for-the-badge' });
|
||||
expect(svg).to.include('LABEL').and.to.include('1 STRING');
|
||||
});
|
||||
});
|
||||
|
||||
describe('"social" template badge generation', function () {
|
||||
it('should produce capitalized string for badge key', function () {
|
||||
describe('"social" template badge generation', () => {
|
||||
it('should produce capitalized string for badge key', () => {
|
||||
const svg = makeBadge({ text: ["some-key", "some-value"], format: 'svg', template: 'social' });
|
||||
expect(svg).to.include('Some-key').and.to.include('some-value');
|
||||
});
|
||||
|
||||
// https://github.com/badges/shields/issues/1606
|
||||
it('should handle empty strings used as badge keys', function () {
|
||||
it('should handle empty strings used as badge keys', () => {
|
||||
const svg = makeBadge({ text: ["", "some-value"], format: 'json', template: 'social' });
|
||||
expect(svg).to.include('""').and.to.include('some-value');
|
||||
});
|
||||
|
||||
Generated
+5
@@ -6947,6 +6947,11 @@
|
||||
"ci-info": "1.1.3"
|
||||
}
|
||||
},
|
||||
"is-css-color": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-css-color/-/is-css-color-1.0.0.tgz",
|
||||
"integrity": "sha1-EQGYzd2xVTw5Nl4px1/btQIXC78="
|
||||
},
|
||||
"is-data-descriptor": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"glob": "^7.1.1",
|
||||
"gm": "^1.23.0",
|
||||
"is-css-color": "^1.0.0",
|
||||
"js-yaml": "^3.11.0",
|
||||
"json-autosave": "~1.1.2",
|
||||
"jsonpath": "~1.0.0",
|
||||
|
||||
@@ -62,7 +62,6 @@ const {
|
||||
} = require('./lib/color-formatters');
|
||||
const {
|
||||
makeColorB,
|
||||
isSixHex: sixHex,
|
||||
makeLabel: getLabel,
|
||||
makeLogo: getLogo,
|
||||
makeBadgeData: getBadgeData,
|
||||
@@ -7880,16 +7879,9 @@ function(data, match, end, ask) {
|
||||
// Badge creation.
|
||||
try {
|
||||
var badgeData = getBadgeData(subject, data);
|
||||
badgeData.colorscheme = undefined;
|
||||
if (data.label !== undefined) { badgeData.text[0] = '' + data.label; }
|
||||
badgeData.text[1] = status;
|
||||
if (badgeData.colorB === undefined) {
|
||||
if (sixHex(color)) {
|
||||
badgeData.colorB = '#' + color;
|
||||
} else if (badgeData.colorA === undefined) {
|
||||
badgeData.colorscheme = color;
|
||||
}
|
||||
}
|
||||
setBadgeColor(badgeData, color);
|
||||
badgeData.template = data.style;
|
||||
if (config.profiling.makeBadge) {
|
||||
console.time('makeBadge total');
|
||||
|
||||
Reference in New Issue
Block a user