Skip to content

Commit

Permalink
chore: extract and test construction of icon url
Browse files Browse the repository at this point in the history
  • Loading branch information
achou11 committed Nov 16, 2023
1 parent 658da21 commit ecc260c
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 7 deletions.
52 changes: 47 additions & 5 deletions src/icon-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,23 @@ export class IconApi {

const mimeExtension = MIME_TO_EXTENSION[opts.mimeType]

if (opts.mimeType === 'image/svg+xml') {
return base + `${iconId}/${opts.size}${mimeExtension}`
}

return base + `${iconId}/${opts.size}@${opts.pixelDensity}x${mimeExtension}`
const pixelDensity =
opts.mimeType === 'image/svg+xml' ||
// if the pixel density is 1, we can omit the density suffix in the resulting url
// and assume the pixel density is 1 for applicable mime types when using the url
opts.pixelDensity === 1
? undefined
: opts.pixelDensity

return (
base +
constructIconPath({
pixelDensity,
size: opts.size,
extension: mimeExtension,
iconId,
})
)
}
}

Expand Down Expand Up @@ -236,3 +248,33 @@ function determineSortValue(target, a, b) {
// Mix of smaller and larger than desired, prefer smaller of the two
return a < b ? -1 : 1
}

/**
* General purpose path builder for an icon
*
* @param {object} opts
* @param {string} opts.iconId
* @param {string} opts.size
* @param {number} [opts.pixelDensity]
* @param {string} opts.extension
*
* @returns {string}
*/
export function constructIconPath({ size, pixelDensity, iconId, extension }) {
if (iconId.length === 0 || size.length === 0 || extension.length === 0) {
throw new Error('iconId, size, and extension cannot be empty strings')
}

let result = `${iconId}/${size}`

if (typeof pixelDensity === 'number') {
if (pixelDensity < 1) {
throw new Error('pixelDensity must be a positive number')
}
result += `@${pixelDensity}x`
}

result += extension.startsWith('.') ? extension : '.' + extension

return result
}
66 changes: 64 additions & 2 deletions tests/icon-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import { drizzle } from 'drizzle-orm/better-sqlite3'
import { migrate } from 'drizzle-orm/better-sqlite3/migrator'
import { randomBytes } from 'node:crypto'

import { IconApi, kGetIconBlob, getBestVariant } from '../src/icon-api.js'
import {
IconApi,
kGetIconBlob,
getBestVariant,
constructIconPath,
} from '../src/icon-api.js'
import { DataType } from '../src/datatype/index.js'
import { DataStore } from '../src/datastore/index.js'
import { createCoreManager } from './helpers/core-manager.js'
Expand Down Expand Up @@ -167,7 +172,7 @@ test(`getIconUrl()`, async (t) => {

t.is(
url,
mediaBaseUrl + `${iconId}/small@1x.png`,
mediaBaseUrl + `${iconId}/small.png`,
'returns expected bitmap icon url'
)
}
Expand Down Expand Up @@ -616,6 +621,63 @@ test(
}
)

test('constructIconPath() - bad inputs', (t) => {
// Array of [input, test message]
/** @type {Array<[Parameters<typeof constructIconPath>[0], string]>} */
const fixtures = [
[
{ iconId: '', size: 'small', extension: 'svg' },
'throws when iconId is empty string',
],
[
{ iconId: 'abc', size: '', extension: 'png' },
'throws when size is empty string',
],
[
{ iconId: 'abc', size: 'small', extension: '' },
'throws when extension is empty string',
],
[
{ iconId: 'abc', size: 'small', extension: 'png', pixelDensity: 0 },
'throws when pixelDensity is zero',
],
[
{ iconId: 'abc', size: 'small', extension: 'png', pixelDensity: -1 },
'throws when pixelDensity is a negative number',
],
]

for (const [input, message] of fixtures) {
t.exception(() => constructIconPath(input), message)
}
})

test('constructIconPath() - good inputs', (t) => {
// Array of [input, expected, test message]
/** @type {Array<[Parameters<typeof constructIconPath>[0], string, string]>} */
const fixtures = [
[
{ iconId: 'abc', size: 'small', extension: 'svg' },
'abc/small.svg',
'omitting pixelDensity leaves out density suffix',
],
[
{ iconId: 'abc', size: 'small', extension: 'png', pixelDensity: 2 },
'abc/small@2x.png',
'including pixelDensity includes density suffix',
],
[
{ iconId: 'abc', size: 'small', extension: '.png' },
'abc/small.png',
'handles extension starting with `.`',
],
]

for (const [input, expected, message] of fixtures) {
t.is(constructIconPath(input), expected, message)
}
})

/**
*
* @param {{ getMediaBaseUrl?: () => Promise<string> }} [opts]
Expand Down

0 comments on commit ecc260c

Please sign in to comment.