diff --git a/core/base-service/base-non-memory-caching.js b/core/base-service/base-non-memory-caching.js index 9ffe066c5804c699355a198d25480e6373c131be..a988e27ee4fe38286f999f758c215381308f96cd 100644 --- a/core/base-service/base-non-memory-caching.js +++ b/core/base-service/base-non-memory-caching.js @@ -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 diff --git a/core/base-service/base-static.js b/core/base-service/base-static.js index d9afd34672cc6db0b8d89c868376fc930a41d59e..8f27cde417a20960fefa6187aa0eeab5b71ef551 100644 --- a/core/base-service/base-static.js +++ b/core/base-service/base-static.js @@ -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 diff --git a/core/base-service/base.js b/core/base-service/base.js index e9a6485022134465a3e7fdcfa3b91e759cfe44f6..92b1efb7952e2bd8a6a7487a27d11ba5ce148041 100644 --- a/core/base-service/base.js +++ b/core/base-service/base.js @@ -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) diff --git a/core/base-service/base.spec.js b/core/base-service/base.spec.js index c8df17ade48cb35c1b1df43385f509977cf9f1fd..d6062b153a241087b1e3e3a5c158169ab49b5797 100644 --- a/core/base-service/base.spec.js +++ b/core/base-service/base.spec.js @@ -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)$/ diff --git a/core/base-service/coalesce-badge.js b/core/base-service/coalesce-badge.js new file mode 100644 index 0000000000000000000000000000000000000000..2b9dae2639ace43df3a73f915d04ee4bca54da89 --- /dev/null +++ b/core/base-service/coalesce-badge.js @@ -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), + } +} diff --git a/core/base-service/coalesce-badge.spec.js b/core/base-service/coalesce-badge.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..4c9ba4ba060f3a7aae7b0d40fd0bad895a837685 --- /dev/null +++ b/core/base-service/coalesce-badge.spec.js @@ -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) + }) + }) +}) diff --git a/core/base-service/transform-example.js b/core/base-service/transform-example.js index f6bed3930d3321c49990fc63dcec49316ddf693d..1f803eb19d097daad9584c74b5703a3f8da58307 100644 --- a/core/base-service/transform-example.js +++ b/core/base-service/transform-example.js @@ -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 = {