Skip to content

Commit 934e67a

Browse files
committed
fix: normalize icon ids for comapeocat
1 parent 200192f commit 934e67a

File tree

2 files changed

+47
-3
lines changed

2 files changed

+47
-3
lines changed

src/services/comapeocatBuilder.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,8 @@ function mapCategory(category: any, index: number): MappedCategory {
219219
const fields = category.fields || category.defaultFieldIds || [];
220220

221221
let icon = category.icon || category.iconId;
222-
if (icon && typeof icon === 'string' && !icon.endsWith('.svg')) {
223-
icon = `${icon}.svg`;
222+
if (icon && typeof icon === 'string') {
223+
icon = normalizeIconId(icon);
224224
}
225225

226226
const definition = {
@@ -300,7 +300,7 @@ function deriveCategorySelection(categories: MappedCategory[]) {
300300
async function resolveIcon(icon: any): Promise<MappedIcon> {
301301
if (!icon?.id) throw new ValidationError('Icon id is required');
302302

303-
const id = icon.id.endsWith('.svg') ? icon.id : `${icon.id}.svg`;
303+
const id = normalizeIconId(icon.id);
304304

305305
if (icon.svgData) {
306306
enforceIconSize(icon.svgData, id);
@@ -324,6 +324,26 @@ async function resolveIcon(icon: any): Promise<MappedIcon> {
324324
throw new ValidationError(`Icon ${id} must include svgData or svgUrl`);
325325
}
326326

327+
const SVG_EXTENSION_PATTERN = /(\.svg)+$/i;
328+
329+
function normalizeIconId(value: unknown): string {
330+
if (typeof value !== 'string') {
331+
throw new ValidationError('Icon id must be a non-empty string');
332+
}
333+
334+
const trimmed = value.trim();
335+
if (!trimmed) {
336+
throw new ValidationError('Icon id must be a non-empty string');
337+
}
338+
339+
const normalized = trimmed.replace(SVG_EXTENSION_PATTERN, '');
340+
if (!normalized) {
341+
throw new ValidationError('Icon id must include characters other than ".svg"');
342+
}
343+
344+
return normalized;
345+
}
346+
327347
function decodeDataUri(dataUri: string): string {
328348
if (!dataUri.startsWith('data:image/svg+xml,')) {
329349
throw new ValidationError('Invalid data URI format. Must start with "data:image/svg+xml,"');
@@ -536,4 +556,6 @@ export const __test__ = {
536556
enforceEntryCap,
537557
sanitizePathComponent,
538558
decodeDataUri,
559+
normalizeIconId,
560+
resolveIcon,
539561
};

src/tests/unit/services/comapeocatBuilder.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,18 @@ describe('comapeocatBuilder helpers', () => {
107107
expect(mapped.definition.appliesTo.sort()).toEqual(['observation', 'track']);
108108
});
109109

110+
it('normalizes category icon references by stripping .svg suffix', () => {
111+
const category = {
112+
id: 'cat',
113+
name: 'Cat',
114+
appliesTo: ['observation'],
115+
tags: { categoryId: 'cat' },
116+
icon: 'animal-terrs.svg',
117+
};
118+
const mapped = __test__.mapCategory(category, 0);
119+
expect(mapped.definition.icon).toBe('animal-terrs');
120+
});
121+
110122
it('throws when icon exceeds size cap', async () => {
111123
const payload = createBasePayload();
112124
payload.icons = [{ id: 'big', svgData: 'a'.repeat(2_000_001) }];
@@ -171,6 +183,16 @@ describe('icon resolution with all three formats', () => {
171183
expect(result.fileName).toContain('test');
172184
});
173185

186+
it('normalizes icon ids to prevent duplicate .svg suffixes', async () => {
187+
const icon = await __test__.resolveIcon({ id: 'animal-terrs.svg', svgData: '<svg xmlns="http://www.w3.org/2000/svg"></svg>' });
188+
expect(icon.id).toBe('animal-terrs');
189+
});
190+
191+
it('strips repeated .svg suffixes when normalizing icon ids', async () => {
192+
const icon = await __test__.resolveIcon({ id: 'animal-terrs.svg.svg', svgData: '<svg xmlns="http://www.w3.org/2000/svg"></svg>' });
193+
expect(icon.id).toBe('animal-terrs');
194+
});
195+
174196
it('successfully processes svgUrl with data URI', async () => {
175197
const payload = createBasePayload();
176198
const dataUri = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%3e%3ccircle%20r='10'/%3e%3c/svg%3e";

0 commit comments

Comments
 (0)