Refactor _makeBadgeData -> coalesceBadge (#2859)
`base.js` is pretty long and `_makeBadgeData` is one of the most complex functions in it. It's a pure function so it's easy to test in isolation. This moves the function into its own module and reorganizes the tests in a way that makes it evaluate what it's doing, and easier to test what is and isn't covered. Ref https://github.com/badges/shields/pull/2796#discussion_r249251008
This commit is contained in:
@@ -4,6 +4,7 @@ const makeBadge = require('../../gh-badges/lib/make-badge')
|
||||
const BaseService = require('./base')
|
||||
const { setCacheHeaders } = require('./cache-headers')
|
||||
const { makeSend } = require('./legacy-result-sender')
|
||||
const coalesceBadge = require('./coalesce-badge')
|
||||
|
||||
// Badges are subject to two independent types of caching: in-memory and
|
||||
// downstream.
|
||||
@@ -35,7 +36,13 @@ module.exports = class NonMemoryCachingBaseService extends BaseService {
|
||||
queryParams
|
||||
)
|
||||
|
||||
const badgeData = this._makeBadgeData(queryParams, serviceData)
|
||||
const badgeData = coalesceBadge(
|
||||
queryParams,
|
||||
serviceData,
|
||||
this.defaultBadgeData,
|
||||
this
|
||||
)
|
||||
|
||||
// The final capture group is the extension.
|
||||
const format = match.slice(-1)[0]
|
||||
badgeData.format = format
|
||||
|
||||
@@ -8,6 +8,7 @@ const {
|
||||
setCacheHeadersForStaticResource,
|
||||
} = require('./cache-headers')
|
||||
const { makeSend } = require('./legacy-result-sender')
|
||||
const coalesceBadge = require('./coalesce-badge')
|
||||
|
||||
module.exports = class BaseStaticService extends BaseService {
|
||||
static register({ camp }, serviceConfig) {
|
||||
@@ -33,7 +34,13 @@ module.exports = class BaseStaticService extends BaseService {
|
||||
queryParams
|
||||
)
|
||||
|
||||
const badgeData = this._makeBadgeData(queryParams, serviceData)
|
||||
const badgeData = coalesceBadge(
|
||||
queryParams,
|
||||
serviceData,
|
||||
this.defaultBadgeData,
|
||||
this
|
||||
)
|
||||
|
||||
// The final capture group is the extension.
|
||||
const format = match.slice(-1)[0]
|
||||
badgeData.format = format
|
||||
|
||||
@@ -5,14 +5,8 @@ const emojic = require('emojic')
|
||||
const pathToRegexp = require('path-to-regexp')
|
||||
const Joi = require('joi')
|
||||
const { checkErrorResponse } = require('../../lib/error-helper')
|
||||
const { toArray } = require('../../lib/badge-data')
|
||||
const { svg2base64 } = require('../../lib/svg-helpers')
|
||||
const {
|
||||
decodeDataUrlFromQueryParam,
|
||||
prepareNamedLogo,
|
||||
} = require('../../lib/logos')
|
||||
const { assertValidCategory } = require('../../services/categories')
|
||||
const coalesce = require('./coalesce')
|
||||
const coalesceBadge = require('./coalesce-badge')
|
||||
const {
|
||||
NotFound,
|
||||
InvalidResponse,
|
||||
@@ -348,143 +342,6 @@ class BaseService {
|
||||
return serviceData
|
||||
}
|
||||
|
||||
// Translate modern badge data to the legacy schema understood by the badge
|
||||
// maker. Allow the user to override the label, color, logo, etc. through
|
||||
// the query string. Provide support for most badge options via
|
||||
// `serviceData` so the Endpoint badge can specify logos and colors, though
|
||||
// allow that the user's logo or color to take precedence. A notable
|
||||
// exception is the case of errors. When the service specifies that an error
|
||||
// has occurred, the user's requested color does not override the error color.
|
||||
//
|
||||
// Logos are resolved in this manner:
|
||||
//
|
||||
// 1. When `?logo=` contains the name of one of the Shields logos, or contains
|
||||
// base64-encoded SVG, that logo is used. In the case of a named logo, when
|
||||
// a `&logoColor=` is specified, that color is used. Otherwise the default
|
||||
// color is used. `logoColor` will not be applied to a custom
|
||||
// (base64-encoded) logo; if a custom color is desired the logo should be
|
||||
// recolored prior to making the request. The appearance of the logo can be
|
||||
// customized using `logoWidth`, and in the case of the popout badge,
|
||||
// `logoPosition`. When `?logo=` is specified, any logo-related parameters
|
||||
// specified dynamically by the service, or by default in the service, are
|
||||
// ignored.
|
||||
// 2. The second precedence is the dynamic logo returned by a service. This is
|
||||
// used only by the Endpoint badge. The `logoColor` can be overridden by the
|
||||
// query string.
|
||||
// 3. In the case of the `social` style only, the last precedence is the
|
||||
// service's default logo. The `logoColor` can be overridden by the query
|
||||
// string.
|
||||
static _makeBadgeData(overrides, serviceData) {
|
||||
const {
|
||||
style: overrideStyle,
|
||||
label: overrideLabel,
|
||||
logoColor: overrideLogoColor,
|
||||
link: overrideLink,
|
||||
} = overrides
|
||||
// Scoutcamp converts numeric query params to numbers. Convert them back.
|
||||
let {
|
||||
colorB: overrideColor,
|
||||
colorA: overrideLabelColor,
|
||||
logoWidth: overrideLogoWidth,
|
||||
logoPosition: overrideLogoPosition,
|
||||
} = overrides
|
||||
if (typeof overrideColor === 'number') {
|
||||
overrideColor = `${overrideColor}`
|
||||
}
|
||||
if (typeof overrideLabelColor === 'number') {
|
||||
overrideLabelColor = `${overrideLabelColor}`
|
||||
}
|
||||
overrideLogoWidth = +overrideLogoWidth || undefined
|
||||
overrideLogoPosition = +overrideLogoPosition || undefined
|
||||
// `?logo=` could be a named logo or encoded svg. Split up these cases.
|
||||
const overrideLogoSvgBase64 = decodeDataUrlFromQueryParam(overrides.logo)
|
||||
const overrideNamedLogo = overrideLogoSvgBase64 ? undefined : overrides.logo
|
||||
|
||||
const {
|
||||
isError,
|
||||
label: serviceLabel,
|
||||
message: serviceMessage,
|
||||
color: serviceColor,
|
||||
labelColor: serviceLabelColor,
|
||||
logoSvg: serviceLogoSvg,
|
||||
namedLogo: serviceNamedLogo,
|
||||
logoColor: serviceLogoColor,
|
||||
logoWidth: serviceLogoWidth,
|
||||
logoPosition: serviceLogoPosition,
|
||||
link: serviceLink,
|
||||
cacheSeconds: serviceCacheSeconds,
|
||||
style: serviceStyle,
|
||||
} = serviceData
|
||||
const serviceLogoSvgBase64 = serviceLogoSvg
|
||||
? svg2base64(serviceLogoSvg)
|
||||
: undefined
|
||||
|
||||
const {
|
||||
color: defaultColor,
|
||||
namedLogo: defaultNamedLogo,
|
||||
label: defaultLabel,
|
||||
labelColor: defaultLabelColor,
|
||||
} = this.defaultBadgeData
|
||||
const defaultCacheSeconds = this._cacheLength
|
||||
|
||||
const style = coalesce(overrideStyle, serviceStyle)
|
||||
|
||||
const namedLogoSvgBase64 = prepareNamedLogo({
|
||||
name: coalesce(
|
||||
overrideNamedLogo,
|
||||
serviceNamedLogo,
|
||||
style === 'social' ? defaultNamedLogo : undefined
|
||||
),
|
||||
color: coalesce(
|
||||
overrideLogoColor,
|
||||
// If the logo has been overridden it does not make sense to inherit
|
||||
// the color.
|
||||
overrideNamedLogo ? undefined : serviceLogoColor
|
||||
),
|
||||
style,
|
||||
})
|
||||
|
||||
return {
|
||||
text: [
|
||||
// Use `coalesce()` to support empty labels and messages, as in the
|
||||
// static badge.
|
||||
coalesce(overrideLabel, serviceLabel, defaultLabel, this.category),
|
||||
coalesce(serviceMessage, 'n/a'),
|
||||
],
|
||||
color: coalesce(
|
||||
// In case of an error, disregard user's color override.
|
||||
isError ? undefined : overrideColor,
|
||||
serviceColor,
|
||||
defaultColor,
|
||||
'lightgrey'
|
||||
),
|
||||
labelColor: coalesce(
|
||||
// In case of an error, disregard user's color override.
|
||||
isError ? undefined : overrideLabelColor,
|
||||
serviceLabelColor,
|
||||
defaultLabelColor
|
||||
),
|
||||
template: style,
|
||||
logo: coalesce(
|
||||
overrideLogoSvgBase64,
|
||||
serviceLogoSvgBase64,
|
||||
namedLogoSvgBase64
|
||||
),
|
||||
logoWidth: coalesce(
|
||||
overrideLogoWidth,
|
||||
// If the logo has been overridden it does not make sense to inherit
|
||||
// the width or position.
|
||||
overrideNamedLogo ? undefined : serviceLogoWidth
|
||||
),
|
||||
logoPosition: coalesce(
|
||||
overrideLogoPosition,
|
||||
overrideNamedLogo ? undefined : serviceLogoPosition
|
||||
),
|
||||
links: toArray(overrideLink || serviceLink),
|
||||
cacheLengthSeconds: coalesce(serviceCacheSeconds, defaultCacheSeconds),
|
||||
}
|
||||
}
|
||||
|
||||
static register({ camp, handleRequest, githubApiProvider }, serviceConfig) {
|
||||
const { cacheHeaders: cacheHeaderConfig, fetchLimitBytes } = serviceConfig
|
||||
camp.route(
|
||||
@@ -504,7 +361,12 @@ class BaseService {
|
||||
queryParams
|
||||
)
|
||||
|
||||
const badgeData = this._makeBadgeData(queryParams, serviceData)
|
||||
const badgeData = coalesceBadge(
|
||||
queryParams,
|
||||
serviceData,
|
||||
this.defaultBadgeData,
|
||||
this
|
||||
)
|
||||
// The final capture group is the extension.
|
||||
const format = match.slice(-1)[0]
|
||||
sendBadge(format, badgeData)
|
||||
|
||||
@@ -4,7 +4,6 @@ const Joi = require('joi')
|
||||
const { expect } = require('chai')
|
||||
const { test, given, forCases } = require('sazerac')
|
||||
const sinon = require('sinon')
|
||||
const { getShieldsIcon } = require('../../lib/logos')
|
||||
const trace = require('./trace')
|
||||
|
||||
const {
|
||||
@@ -371,245 +370,6 @@ describe('BaseService', function() {
|
||||
})
|
||||
})
|
||||
|
||||
describe('_makeBadgeData', function() {
|
||||
describe('Overrides', function() {
|
||||
it('overrides the label', function() {
|
||||
const badgeData = DummyService._makeBadgeData(
|
||||
{ label: 'purr count' },
|
||||
{ label: 'purrs' }
|
||||
)
|
||||
expect(badgeData.text).to.deep.equal(['purr count', 'n/a'])
|
||||
})
|
||||
|
||||
it('overrides the label color', function() {
|
||||
const badgeData = DummyService._makeBadgeData(
|
||||
{ colorA: '42f483' },
|
||||
{ color: 'green' }
|
||||
)
|
||||
expect(badgeData.labelColor).to.equal('42f483')
|
||||
})
|
||||
|
||||
it('overrides the color', function() {
|
||||
const badgeData = DummyService._makeBadgeData(
|
||||
{ colorB: '10ADED' },
|
||||
{ color: 'red' }
|
||||
)
|
||||
expect(badgeData.color).to.equal('10ADED')
|
||||
})
|
||||
|
||||
it('converts a query-string numeric color to a string', function() {
|
||||
const badgeData = DummyService._makeBadgeData(
|
||||
// Scoutcamp converts numeric query params to numbers.
|
||||
{ colorB: 123 },
|
||||
{ color: 'green' }
|
||||
)
|
||||
expect(badgeData.color).to.equal('123')
|
||||
})
|
||||
|
||||
it('does not override the color in case of an error', function() {
|
||||
const badgeData = DummyService._makeBadgeData(
|
||||
{ colorB: '10ADED' },
|
||||
{ isError: true, color: 'lightgray' }
|
||||
)
|
||||
expect(badgeData.color).to.equal('lightgray')
|
||||
})
|
||||
|
||||
it('overrides the logo', function() {
|
||||
const badgeData = DummyService._makeBadgeData(
|
||||
{ logo: 'github' },
|
||||
{ namedLogo: 'appveyor' }
|
||||
)
|
||||
// .not.be.empty for confidence that nothing has changed with `getShieldsIcon()`.
|
||||
expect(badgeData.logo).to.equal(getShieldsIcon({ name: 'github' })).and
|
||||
.not.be.empty
|
||||
})
|
||||
|
||||
it('overrides the logo with a color', function() {
|
||||
const badgeData = DummyService._makeBadgeData(
|
||||
{ logo: 'github', logoColor: 'blue' },
|
||||
{ namedLogo: 'appveyor' }
|
||||
)
|
||||
expect(badgeData.logo).to.equal(
|
||||
getShieldsIcon({ name: 'github', color: 'blue' })
|
||||
).and.not.be.empty
|
||||
})
|
||||
|
||||
it("when the logo is overridden, it ignores the service's logo color, position, and width", function() {
|
||||
const badgeData = DummyService._makeBadgeData(
|
||||
{ logo: 'github' },
|
||||
{
|
||||
namedLogo: 'appveyor',
|
||||
logoColor: 'red',
|
||||
logoPosition: -3,
|
||||
logoWidth: 100,
|
||||
}
|
||||
)
|
||||
expect(badgeData.logo).to.equal(getShieldsIcon({ name: 'github' })).and
|
||||
.not.be.empty
|
||||
})
|
||||
|
||||
it("overrides the service logo's color", function() {
|
||||
const badgeData = DummyService._makeBadgeData(
|
||||
{ logoColor: 'blue' },
|
||||
{ namedLogo: 'github', logoColor: 'red' }
|
||||
)
|
||||
expect(badgeData.logo).to.equal(
|
||||
getShieldsIcon({ name: 'github', color: 'blue' })
|
||||
).and.not.be.empty
|
||||
})
|
||||
|
||||
it('overrides the logo with custom svg', function() {
|
||||
const logoSvg = 'data:image/svg+xml;base64,PHN2ZyB4bWxu'
|
||||
const badgeData = DummyService._makeBadgeData(
|
||||
{ logo: logoSvg },
|
||||
{ namedLogo: 'appveyor' }
|
||||
)
|
||||
expect(badgeData.logo).to.equal(logoSvg)
|
||||
})
|
||||
|
||||
it('ignores the color when custom svg is provided', function() {
|
||||
const logoSvg = 'data:image/svg+xml;base64,PHN2ZyB4bWxu'
|
||||
const badgeData = DummyService._makeBadgeData(
|
||||
{ logo: logoSvg, logoColor: 'brightgreen' },
|
||||
{ namedLogo: 'appveyor' }
|
||||
)
|
||||
expect(badgeData.logo).to.equal(logoSvg)
|
||||
})
|
||||
|
||||
it('overrides the logoWidth', function() {
|
||||
const badgeData = DummyService._makeBadgeData({ logoWidth: 20 }, {})
|
||||
expect(badgeData.logoWidth).to.equal(20)
|
||||
})
|
||||
|
||||
it('overrides the logoPosition', function() {
|
||||
const badgeData = DummyService._makeBadgeData({ logoPosition: -10 }, {})
|
||||
expect(badgeData.logoPosition).to.equal(-10)
|
||||
})
|
||||
|
||||
it('overrides the links', function() {
|
||||
const badgeData = DummyService._makeBadgeData(
|
||||
{ link: 'https://circleci.com/gh/badges/daily-tests' },
|
||||
{
|
||||
link:
|
||||
'https://circleci.com/workflow-run/184ef3de-4836-4805-a2e4-0ceba099f92d',
|
||||
}
|
||||
)
|
||||
expect(badgeData.links).to.deep.equal([
|
||||
'https://circleci.com/gh/badges/daily-tests',
|
||||
])
|
||||
})
|
||||
|
||||
it('overrides the template', function() {
|
||||
const badgeData = DummyService._makeBadgeData({ style: 'pill' }, {})
|
||||
expect(badgeData.template).to.equal('pill')
|
||||
})
|
||||
|
||||
it('overrides the cache length', function() {
|
||||
const badgeData = DummyService._makeBadgeData(
|
||||
{ style: 'pill' },
|
||||
{ cacheSeconds: 123 }
|
||||
)
|
||||
expect(badgeData.cacheLengthSeconds).to.equal(123)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Service data', function() {
|
||||
it('applies the service message', function() {
|
||||
const badgeData = DummyService._makeBadgeData({}, { message: '10k' })
|
||||
expect(badgeData.text).to.deep.equal(['cat', '10k'])
|
||||
})
|
||||
|
||||
it('preserves an empty label', function() {
|
||||
const badgeData = DummyService._makeBadgeData(
|
||||
{},
|
||||
{ label: '', message: '10k' }
|
||||
)
|
||||
expect(badgeData.text).to.deep.equal(['', '10k'])
|
||||
})
|
||||
|
||||
it('applies a numeric service message', function() {
|
||||
// While a number of badges use this, in the long run we may want
|
||||
// `render()` to always return a string.
|
||||
const badgeData = DummyService._makeBadgeData({}, { message: 10 })
|
||||
expect(badgeData.text).to.deep.equal(['cat', 10])
|
||||
})
|
||||
|
||||
it('applies the service color', function() {
|
||||
const badgeData = DummyService._makeBadgeData({}, { color: 'red' })
|
||||
expect(badgeData.color).to.equal('red')
|
||||
})
|
||||
|
||||
it('applies the named logo', function() {
|
||||
const badgeData = DummyService._makeBadgeData(
|
||||
{},
|
||||
{ namedLogo: 'github' }
|
||||
)
|
||||
// .not.be.empty for confidence that nothing has changed with `getShieldsIcon()`.
|
||||
expect(badgeData.logo).to.equal(getShieldsIcon({ name: 'github' })).and
|
||||
.not.to.be.empty
|
||||
})
|
||||
|
||||
it('applies the named logo with color', function() {
|
||||
const badgeData = DummyService._makeBadgeData(
|
||||
{},
|
||||
{ namedLogo: 'github', logoColor: 'blue' }
|
||||
)
|
||||
expect(badgeData.logo).to.equal(
|
||||
getShieldsIcon({ name: 'github', color: 'blue' })
|
||||
).and.not.to.be.empty
|
||||
})
|
||||
|
||||
it('applies the logo width', function() {
|
||||
const badgeData = DummyService._makeBadgeData(
|
||||
{},
|
||||
{ namedLogo: 'github', logoWidth: 275 }
|
||||
)
|
||||
expect(badgeData.logoWidth).to.equal(275)
|
||||
})
|
||||
|
||||
it('applies the logo position', function() {
|
||||
const badgeData = DummyService._makeBadgeData(
|
||||
{},
|
||||
{ namedLogo: 'github', logoPosition: -10 }
|
||||
)
|
||||
expect(badgeData.logoPosition).to.equal(-10)
|
||||
})
|
||||
|
||||
it('applies the service label color', function() {
|
||||
const badgeData = DummyService._makeBadgeData({}, { labelColor: 'red' })
|
||||
expect(badgeData.labelColor).to.equal('red')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Defaults', function() {
|
||||
it('uses the default label', function() {
|
||||
const badgeData = DummyService._makeBadgeData({}, {})
|
||||
expect(badgeData.text).to.deep.equal(['cat', 'n/a'])
|
||||
})
|
||||
|
||||
it('uses the default color', function() {
|
||||
const badgeData = DummyService._makeBadgeData({}, {})
|
||||
expect(badgeData.color).to.equal('lightgrey')
|
||||
})
|
||||
|
||||
it('provides no default label color', function() {
|
||||
const badgeData = DummyService._makeBadgeData({}, {})
|
||||
expect(badgeData.labelColor).to.be.undefined
|
||||
})
|
||||
|
||||
it('when not a social badge, ignores the default named logo', function() {
|
||||
const badgeData = DummyService._makeBadgeData({}, {})
|
||||
expect(badgeData.logo).to.be.undefined
|
||||
})
|
||||
|
||||
it('when a social badge, uses the default named logo', function() {
|
||||
const badgeData = DummyService._makeBadgeData({ style: 'social' }, {})
|
||||
expect(badgeData.logo).to.equal(getShieldsIcon({ name: 'appveyor' }))
|
||||
.and.not.be.empty
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('ScoutCamp integration', function() {
|
||||
const expectedRouteRegex = /^\/foo\/([^/]+?)\.(svg|png|gif|jpg|json)$/
|
||||
|
||||
|
||||
151
core/base-service/coalesce-badge.js
Normal file
151
core/base-service/coalesce-badge.js
Normal file
@@ -0,0 +1,151 @@
|
||||
'use strict'
|
||||
|
||||
const {
|
||||
decodeDataUrlFromQueryParam,
|
||||
prepareNamedLogo,
|
||||
} = require('../../lib/logos')
|
||||
const { toArray } = require('../../lib/badge-data')
|
||||
const { svg2base64 } = require('../../lib/svg-helpers')
|
||||
const coalesce = require('./coalesce')
|
||||
|
||||
// Translate modern badge data to the legacy schema understood by the badge
|
||||
// maker. Allow the user to override the label, color, logo, etc. through the
|
||||
// query string. Provide support for most badge options via `serviceData` so
|
||||
// the Endpoint badge can specify logos and colors, though allow that the
|
||||
// user's logo or color to take precedence. A notable exception is the case of
|
||||
// errors. When the service specifies that an error has occurred, the user's
|
||||
// requested color does not override the error color.
|
||||
//
|
||||
// Logos are resolved in this manner:
|
||||
//
|
||||
// 1. When `?logo=` contains the name of one of the Shields logos, or contains
|
||||
// base64-encoded SVG, that logo is used. In the case of a named logo, when
|
||||
// a `&logoColor=` is specified, that color is used. Otherwise the default
|
||||
// color is used. `logoColor` will not be applied to a custom
|
||||
// (base64-encoded) logo; if a custom color is desired the logo should be
|
||||
// recolored prior to making the request. The appearance of the logo can be
|
||||
// customized using `logoWidth`, and in the case of the popout badge,
|
||||
// `logoPosition`. When `?logo=` is specified, any logo-related parameters
|
||||
// specified dynamically by the service, or by default in the service, are
|
||||
// ignored.
|
||||
// 2. The second precedence is the dynamic logo returned by a service. This is
|
||||
// used only by the Endpoint badge. The `logoColor` can be overridden by the
|
||||
// query string.
|
||||
// 3. In the case of the `social` style only, the last precedence is the
|
||||
// service's default logo. The `logoColor` can be overridden by the query
|
||||
// string.
|
||||
module.exports = function coalesceBadge(
|
||||
overrides,
|
||||
serviceData,
|
||||
// These two parameters were kept separate to make tests clearer.
|
||||
defaultBadgeData,
|
||||
{ category, _cacheLength: defaultCacheSeconds } = {}
|
||||
) {
|
||||
const {
|
||||
style: overrideStyle,
|
||||
label: overrideLabel,
|
||||
logoColor: overrideLogoColor,
|
||||
link: overrideLink,
|
||||
} = overrides
|
||||
// Scoutcamp converts numeric query params to numbers. Convert them back.
|
||||
let {
|
||||
colorB: overrideColor,
|
||||
colorA: overrideLabelColor,
|
||||
logoWidth: overrideLogoWidth,
|
||||
logoPosition: overrideLogoPosition,
|
||||
} = overrides
|
||||
if (typeof overrideColor === 'number') {
|
||||
overrideColor = `${overrideColor}`
|
||||
}
|
||||
if (typeof overrideLabelColor === 'number') {
|
||||
overrideLabelColor = `${overrideLabelColor}`
|
||||
}
|
||||
overrideLogoWidth = +overrideLogoWidth || undefined
|
||||
overrideLogoPosition = +overrideLogoPosition || undefined
|
||||
// `?logo=` could be a named logo or encoded svg. Split up these cases.
|
||||
const overrideLogoSvgBase64 = decodeDataUrlFromQueryParam(overrides.logo)
|
||||
const overrideNamedLogo = overrideLogoSvgBase64 ? undefined : overrides.logo
|
||||
|
||||
const {
|
||||
isError,
|
||||
label: serviceLabel,
|
||||
message: serviceMessage,
|
||||
color: serviceColor,
|
||||
labelColor: serviceLabelColor,
|
||||
logoSvg: serviceLogoSvg,
|
||||
namedLogo: serviceNamedLogo,
|
||||
logoColor: serviceLogoColor,
|
||||
logoWidth: serviceLogoWidth,
|
||||
logoPosition: serviceLogoPosition,
|
||||
link: serviceLink,
|
||||
cacheSeconds: serviceCacheSeconds,
|
||||
style: serviceStyle,
|
||||
} = serviceData
|
||||
const serviceLogoSvgBase64 = serviceLogoSvg
|
||||
? svg2base64(serviceLogoSvg)
|
||||
: undefined
|
||||
|
||||
const {
|
||||
color: defaultColor,
|
||||
namedLogo: defaultNamedLogo,
|
||||
label: defaultLabel,
|
||||
labelColor: defaultLabelColor,
|
||||
} = defaultBadgeData
|
||||
|
||||
const style = coalesce(overrideStyle, serviceStyle)
|
||||
|
||||
const namedLogoSvgBase64 = prepareNamedLogo({
|
||||
name: coalesce(
|
||||
overrideNamedLogo,
|
||||
serviceNamedLogo,
|
||||
style === 'social' ? defaultNamedLogo : undefined
|
||||
),
|
||||
color: coalesce(
|
||||
overrideLogoColor,
|
||||
// If the logo has been overridden it does not make sense to inherit
|
||||
// the color.
|
||||
overrideNamedLogo ? undefined : serviceLogoColor
|
||||
),
|
||||
style,
|
||||
})
|
||||
|
||||
return {
|
||||
text: [
|
||||
// Use `coalesce()` to support empty labels and messages, as in the
|
||||
// static badge.
|
||||
coalesce(overrideLabel, serviceLabel, defaultLabel, category),
|
||||
coalesce(serviceMessage, 'n/a'),
|
||||
],
|
||||
color: coalesce(
|
||||
// In case of an error, disregard user's color override.
|
||||
isError ? undefined : overrideColor,
|
||||
serviceColor,
|
||||
defaultColor,
|
||||
'lightgrey'
|
||||
),
|
||||
labelColor: coalesce(
|
||||
// In case of an error, disregard user's color override.
|
||||
isError ? undefined : overrideLabelColor,
|
||||
serviceLabelColor,
|
||||
defaultLabelColor
|
||||
),
|
||||
template: style,
|
||||
logo: coalesce(
|
||||
overrideLogoSvgBase64,
|
||||
serviceLogoSvgBase64,
|
||||
namedLogoSvgBase64
|
||||
),
|
||||
logoWidth: coalesce(
|
||||
overrideLogoWidth,
|
||||
// If the logo has been overridden it does not make sense to inherit
|
||||
// the width or position.
|
||||
overrideNamedLogo ? undefined : serviceLogoWidth
|
||||
),
|
||||
logoPosition: coalesce(
|
||||
overrideLogoPosition,
|
||||
overrideNamedLogo ? undefined : serviceLogoPosition
|
||||
),
|
||||
links: toArray(overrideLink || serviceLink),
|
||||
cacheLengthSeconds: coalesce(serviceCacheSeconds, defaultCacheSeconds),
|
||||
}
|
||||
}
|
||||
257
core/base-service/coalesce-badge.spec.js
Normal file
257
core/base-service/coalesce-badge.spec.js
Normal file
@@ -0,0 +1,257 @@
|
||||
'use strict'
|
||||
|
||||
const { expect } = require('chai')
|
||||
const { getShieldsIcon } = require('../../lib/logos')
|
||||
const coalesceBadge = require('./coalesce-badge')
|
||||
|
||||
describe('coalesceBadge', function() {
|
||||
describe('Label', function() {
|
||||
it('uses the default label', function() {
|
||||
expect(coalesceBadge({}, {}, { label: 'heyo' }).text).to.deep.equal([
|
||||
'heyo',
|
||||
'n/a',
|
||||
])
|
||||
})
|
||||
|
||||
// This behavior isn't great and we might want to remove it.
|
||||
it('uses the category as a default label', function() {
|
||||
expect(coalesceBadge({}, {}, {}, { category: 'cat' }).text).to.deep.equal(
|
||||
['cat', 'n/a']
|
||||
)
|
||||
})
|
||||
|
||||
it('preserves an empty label', function() {
|
||||
expect(
|
||||
coalesceBadge({}, { label: '', message: '10k' }, {}).text
|
||||
).to.deep.equal(['', '10k'])
|
||||
})
|
||||
|
||||
it('overrides the label', function() {
|
||||
expect(
|
||||
coalesceBadge({ label: 'purr count' }, { label: 'purrs' }, {}).text
|
||||
).to.deep.equal(['purr count', 'n/a'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Message', function() {
|
||||
it('applies the service message', function() {
|
||||
expect(coalesceBadge({}, { message: '10k' }, {}).text).to.deep.equal([
|
||||
undefined,
|
||||
'10k',
|
||||
])
|
||||
})
|
||||
|
||||
it('applies a numeric service message', function() {
|
||||
// While a number of badges use this, in the long run we may want
|
||||
// `render()` to always return a string.
|
||||
expect(coalesceBadge({}, { message: 10 }, {}).text).to.deep.equal([
|
||||
undefined,
|
||||
10,
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Right color', function() {
|
||||
it('uses the default color', function() {
|
||||
expect(coalesceBadge({}, {}, {}).color).to.equal('lightgrey')
|
||||
})
|
||||
|
||||
it('overrides the color', function() {
|
||||
expect(
|
||||
coalesceBadge({ colorB: '10ADED' }, { color: 'red' }, {}).color
|
||||
).to.equal('10ADED')
|
||||
})
|
||||
|
||||
context('In case of an error', function() {
|
||||
it('does not override the color', function() {
|
||||
expect(
|
||||
coalesceBadge(
|
||||
{ colorB: '10ADED' },
|
||||
{ isError: true, color: 'lightgray' },
|
||||
{}
|
||||
).color
|
||||
).to.equal('lightgray')
|
||||
})
|
||||
})
|
||||
|
||||
it('applies the service color', function() {
|
||||
expect(coalesceBadge({}, { color: 'red' }, {}).color).to.equal('red')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Left color', function() {
|
||||
it('provides no default label color', function() {
|
||||
expect(coalesceBadge({}, {}, {}).labelColor).to.be.undefined
|
||||
})
|
||||
|
||||
it('applies the service label color', function() {
|
||||
expect(coalesceBadge({}, { labelColor: 'red' }, {}).labelColor).to.equal(
|
||||
'red'
|
||||
)
|
||||
})
|
||||
|
||||
it('overrides the label color', function() {
|
||||
expect(
|
||||
coalesceBadge({ colorA: '42f483' }, { color: 'green' }, {}).labelColor
|
||||
).to.equal('42f483')
|
||||
})
|
||||
|
||||
it('converts a query-string numeric color to a string', function() {
|
||||
expect(
|
||||
coalesceBadge(
|
||||
// Scoutcamp converts numeric query params to numbers.
|
||||
{ colorB: 123 },
|
||||
{ color: 'green' },
|
||||
{}
|
||||
).color
|
||||
).to.equal('123')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Named logos', function() {
|
||||
it('when not a social badge, ignores the default named logo', function() {
|
||||
expect(coalesceBadge({}, {}, { namedLogo: 'appveyor' }).logo).to.be
|
||||
.undefined
|
||||
})
|
||||
|
||||
it('when a social badge, uses the default named logo', function() {
|
||||
// .not.be.empty for confidence that nothing has changed with `getShieldsIcon()`.
|
||||
expect(
|
||||
coalesceBadge({ style: 'social' }, {}, { namedLogo: 'appveyor' }).logo
|
||||
).to.equal(getShieldsIcon({ name: 'appveyor' })).and.not.be.empty
|
||||
})
|
||||
|
||||
it('applies the named logo', function() {
|
||||
expect(coalesceBadge({}, { namedLogo: 'github' }, {}).logo).to.equal(
|
||||
getShieldsIcon({ name: 'github' })
|
||||
).and.not.to.be.empty
|
||||
})
|
||||
|
||||
it('applies the named logo with color', function() {
|
||||
expect(
|
||||
coalesceBadge({}, { namedLogo: 'github', logoColor: 'blue' }, {}).logo
|
||||
).to.equal(getShieldsIcon({ name: 'github', color: 'blue' })).and.not.to
|
||||
.be.empty
|
||||
})
|
||||
|
||||
it('overrides the logo', function() {
|
||||
expect(
|
||||
coalesceBadge({ logo: 'github' }, { namedLogo: 'appveyor' }, {}).logo
|
||||
).to.equal(getShieldsIcon({ name: 'github' })).and.not.be.empty
|
||||
})
|
||||
|
||||
it('overrides the logo with a color', function() {
|
||||
expect(
|
||||
coalesceBadge(
|
||||
{ logo: 'github', logoColor: 'blue' },
|
||||
{ namedLogo: 'appveyor' },
|
||||
{}
|
||||
).logo
|
||||
).to.equal(getShieldsIcon({ name: 'github', color: 'blue' })).and.not.be
|
||||
.empty
|
||||
})
|
||||
|
||||
it("when the logo is overridden, it ignores the service's logo color, position, and width", function() {
|
||||
expect(
|
||||
coalesceBadge(
|
||||
{ logo: 'github' },
|
||||
{
|
||||
namedLogo: 'appveyor',
|
||||
logoColor: 'red',
|
||||
logoPosition: -3,
|
||||
logoWidth: 100,
|
||||
},
|
||||
{}
|
||||
).logo
|
||||
).to.equal(getShieldsIcon({ name: 'github' })).and.not.be.empty
|
||||
})
|
||||
|
||||
it("overrides the service logo's color", function() {
|
||||
expect(
|
||||
coalesceBadge(
|
||||
{ logoColor: 'blue' },
|
||||
{ namedLogo: 'github', logoColor: 'red' },
|
||||
{}
|
||||
).logo
|
||||
).to.equal(getShieldsIcon({ name: 'github', color: 'blue' })).and.not.be
|
||||
.empty
|
||||
})
|
||||
})
|
||||
|
||||
describe('Custom logos', function() {
|
||||
it('overrides the logo with custom svg', function() {
|
||||
const logoSvg = 'data:image/svg+xml;base64,PHN2ZyB4bWxu'
|
||||
expect(
|
||||
coalesceBadge({ logo: logoSvg }, { namedLogo: 'appveyor' }, {}).logo
|
||||
).to.equal(logoSvg)
|
||||
})
|
||||
|
||||
it('ignores the color when custom svg is provided', function() {
|
||||
const logoSvg = 'data:image/svg+xml;base64,PHN2ZyB4bWxu'
|
||||
expect(
|
||||
coalesceBadge(
|
||||
{ logo: logoSvg, logoColor: 'brightgreen' },
|
||||
{ namedLogo: 'appveyor' },
|
||||
{}
|
||||
).logo
|
||||
).to.equal(logoSvg)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Logo width', function() {
|
||||
it('overrides the logoWidth', function() {
|
||||
expect(coalesceBadge({ logoWidth: 20 }, {}, {}).logoWidth).to.equal(20)
|
||||
})
|
||||
|
||||
it('applies the logo width', function() {
|
||||
expect(
|
||||
coalesceBadge({}, { namedLogo: 'github', logoWidth: 275 }, {}).logoWidth
|
||||
).to.equal(275)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Logo position', function() {
|
||||
it('overrides the logoPosition', function() {
|
||||
expect(
|
||||
coalesceBadge({ logoPosition: -10 }, {}, {}).logoPosition
|
||||
).to.equal(-10)
|
||||
})
|
||||
|
||||
it('applies the logo position', function() {
|
||||
expect(
|
||||
coalesceBadge({}, { namedLogo: 'github', logoPosition: -10 }, {})
|
||||
.logoPosition
|
||||
).to.equal(-10)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Links', function() {
|
||||
it('overrides the links', function() {
|
||||
expect(
|
||||
coalesceBadge(
|
||||
{ link: 'https://circleci.com/gh/badges/daily-tests' },
|
||||
{
|
||||
link:
|
||||
'https://circleci.com/workflow-run/184ef3de-4836-4805-a2e4-0ceba099f92d',
|
||||
},
|
||||
{}
|
||||
).links
|
||||
).to.deep.equal(['https://circleci.com/gh/badges/daily-tests'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Style', function() {
|
||||
it('overrides the template', function() {
|
||||
expect(coalesceBadge({ style: 'pill' }, {}, {}).template).to.equal('pill')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Cache length', function() {
|
||||
it('overrides the cache length', function() {
|
||||
expect(
|
||||
coalesceBadge({ style: 'pill' }, { cacheSeconds: 123 }, {})
|
||||
.cacheLengthSeconds
|
||||
).to.equal(123)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
const Joi = require('joi')
|
||||
const pathToRegexp = require('path-to-regexp')
|
||||
const coalesceBadge = require('./coalesce-badge')
|
||||
|
||||
const optionalObjectOfKeyValues = Joi.object().pattern(
|
||||
/./,
|
||||
@@ -142,7 +143,12 @@ function transformExample(inExample, index, ServiceClass) {
|
||||
const {
|
||||
text: [label, message],
|
||||
color,
|
||||
} = ServiceClass._makeBadgeData({}, staticPreview)
|
||||
} = coalesceBadge(
|
||||
{},
|
||||
staticPreview,
|
||||
ServiceClass.defaultBadgeData,
|
||||
ServiceClass
|
||||
)
|
||||
preview = { label, message: `${message}`, color }
|
||||
} else {
|
||||
preview = {
|
||||
|
||||
Reference in New Issue
Block a user