2
0
mirror of https://github.com/iconify/iconify.git synced 2024-12-04 18:23:17 +00:00

feat(utils): do not force naming convension when validating icon sets and icon names

This commit is contained in:
Vjacheslav Trushkin 2024-11-02 09:02:07 +02:00
parent c5d1a469d1
commit 3ae86d5f0d
6 changed files with 147 additions and 47 deletions

View File

@ -1,5 +1,4 @@
import type { IconifyAliases, IconifyJSON } from '@iconify/types';
import { matchIconName } from '../icon/name';
import {
defaultIconDimensions,
defaultExtendedIconProps,
@ -63,8 +62,11 @@ export function quicklyValidateIconSet(obj: unknown): IconifyJSON | null {
for (const name in icons) {
const icon = icons[name];
if (
!name.match(matchIconName) ||
// Name cannot be empty
!name ||
// Must have body
typeof icon.body !== 'string' ||
// Check other props
!checkOptionalProps(
icon as unknown as PropsList,
defaultExtendedIconProps
@ -80,9 +82,12 @@ export function quicklyValidateIconSet(obj: unknown): IconifyJSON | null {
const icon = aliases[name];
const parent = icon.parent;
if (
!name.match(matchIconName) ||
// Name cannot be empty
!name ||
// Parent must be set and point to existing icon
typeof parent !== 'string' ||
(!icons[parent] && !aliases[parent]) ||
// Check other props
!checkOptionalProps(
icon as unknown as PropsList,
defaultExtendedIconProps

View File

@ -4,7 +4,6 @@ import type {
IconifyJSON,
IconifyOptional,
} from '@iconify/types';
import { matchIconName } from '../icon/name';
import { defaultExtendedIconProps } from '../icon/defaults';
import { getIconsTree } from './tree';
@ -99,8 +98,9 @@ export function validateIconSet(
if (options && typeof options.prefix === 'string') {
data.prefix = options.prefix;
} else if (
// Prefix must be a string and not empty
typeof data.prefix !== 'string' ||
!data.prefix.match(matchIconName)
!data.prefix
) {
throw new Error('Invalid prefix');
}
@ -110,10 +110,7 @@ export function validateIconSet(
data.provider = options.provider;
} else if (data.provider !== void 0) {
const value = data.provider;
if (
typeof value !== 'string' ||
(value !== '' && !value.match(matchIconName))
) {
if (typeof value !== 'string') {
if (fix) {
delete data.provider;
} else {
@ -150,7 +147,7 @@ export function validateIconSet(
throw new Error(`Invalid alias: ${name}`);
}
if (!name.match(matchIconName)) {
if (!name) {
if (fix) {
delete parentObj[name];
continue;

View File

@ -14,11 +14,14 @@ export type IconifyIconSource = Omit<IconifyIconName, 'name'>;
/**
* Expression to test part of icon name.
*
* Used when loading icons from Iconify API due to project naming convension.
* Ignored when using custom icon sets - convension does not apply.
*/
export const matchIconName = /^[a-z0-9]+(-[a-z0-9]+)*$/;
/**
* Convert string to Icon object.
* Convert string icon name to IconifyIconName object.
*/
export const stringToIcon = (
value: string,
@ -96,9 +99,11 @@ export const validateIconName = (
}
return !!(
(icon.provider === '' || icon.provider.match(matchIconName)) &&
((allowSimpleName && icon.prefix === '') ||
icon.prefix.match(matchIconName)) &&
icon.name.match(matchIconName)
// Check prefix: cannot be empty, unless allowSimpleName is enabled
// Check name: cannot be empty
(
((allowSimpleName && icon.prefix === '') || !!icon.prefix) &&
!!icon.name
)
);
};

View File

@ -58,7 +58,7 @@ describe('Testing icon name', () => {
expect(validateIconName(icon)).toBe(false);
expect(validateIconName(icon, true)).toBe(false);
// Underscore is not an acceptable separator
// No prefix
icon = stringToIcon('fa_home');
expect(icon).toEqual(null);
expect(validateIconName(icon)).toBe(false);
@ -71,30 +71,30 @@ describe('Testing icon name', () => {
name: 'fa_home',
});
expect(validateIconName(icon)).toBe(false);
expect(validateIconName(icon, true)).toBe(false);
expect(validateIconName(icon, true)).toBe(true);
// Invalid character '_': fail validateIcon
// Underscore is allowed now
icon = stringToIcon('fa:home_outline') as IconifyIconName;
expect(icon).toEqual({
provider: '',
prefix: 'fa',
name: 'home_outline',
});
expect(validateIconName(icon)).toBe(false);
expect(validateIconName(icon)).toBe(true);
// Too many colons: fail stringToIcon
icon = stringToIcon('mdi:light:home:outline');
expect(icon).toEqual(null);
expect(validateIconName(icon)).toBe(false);
// Upper case: fail validateIcon
// Upper case: allowed now
icon = stringToIcon('MD:Home') as IconifyIconName;
expect(icon).toEqual({
provider: '',
prefix: 'MD',
name: 'Home',
});
expect(validateIconName(icon)).toBe(false);
expect(validateIconName(icon)).toBe(true);
// Numbers: pass
icon = stringToIcon('1:foo') as IconifyIconName;
@ -105,14 +105,14 @@ describe('Testing icon name', () => {
});
expect(validateIconName(icon)).toBe(true);
// Accented letters: fail validateIcon
// Accented letters: allowed now
icon = stringToIcon('md-fõö') as IconifyIconName;
expect(icon).toEqual({
provider: '',
prefix: 'md',
name: 'fõö',
});
expect(validateIconName(icon)).toBe(false);
expect(validateIconName(icon)).toBe(true);
});
it('Providers', () => {
@ -167,13 +167,13 @@ describe('Testing icon name', () => {
icon = stringToIcon('@mdi:light:home:outline', false, true);
expect(icon).toEqual(null);
// Upper case: fail validateIcon
// Upper case: allowed now
icon = stringToIcon('@MD:home-outline') as IconifyIconName;
expect(icon).toEqual({
provider: 'MD',
prefix: 'home',
name: 'outline',
});
expect(validateIconName(icon)).toBe(false);
expect(validateIconName(icon)).toBe(true);
});
});

View File

@ -33,9 +33,11 @@ describe('Testing validation', () => {
expect(
quicklyValidateIconSet({
prefix: 'foo',
// Characters that used to be invalid
prefix: 'fòó_bār',
icons: {
bar: {
// Characters that used to be invalid
bär: {
body: '<g />',
width: 32,
height: 32,
@ -47,8 +49,9 @@ describe('Testing validation', () => {
},
},
aliases: {
baz: {
parent: 'bar',
// Characters that used to be invalid
Bär_Bāz: {
parent: 'bär',
hFlip: true,
},
},
@ -56,9 +59,9 @@ describe('Testing validation', () => {
height: 24,
})
).toEqual({
prefix: 'foo',
prefix: 'fòó_bār',
icons: {
bar: {
bär: {
body: '<g />',
width: 32,
height: 32,
@ -69,8 +72,8 @@ describe('Testing validation', () => {
},
},
aliases: {
baz: {
parent: 'bar',
Bär_Bāz: {
parent: 'bär',
hFlip: true,
},
},
@ -104,6 +107,19 @@ describe('Testing validation', () => {
).toBe(null);
});
test('Empty icon name', () => {
expect(
quicklyValidateIconSet({
prefix: 'foo',
icons: {
'': {
body: '<g />',
},
},
})
).toBe(null);
});
test('Invalid optional properties', () => {
expect(
quicklyValidateIconSet({

View File

@ -5,7 +5,7 @@ describe('Testing validation', () => {
return new Promise((fulfill, reject) => {
try {
validateIconSet(void 0);
reject('Expected to throw error on undefined');
reject(new Error('Expected to throw error on undefined'));
return;
} catch {
//
@ -13,7 +13,7 @@ describe('Testing validation', () => {
try {
validateIconSet({});
reject('Expected to throw error on empty object');
reject(new Error('Expected to throw error on empty object'));
return;
} catch {
//
@ -21,7 +21,7 @@ describe('Testing validation', () => {
try {
validateIconSet(null);
reject('Expected to throw error on null');
reject(new Error('Expected to throw error on null'));
return;
} catch {
//
@ -29,7 +29,7 @@ describe('Testing validation', () => {
try {
validateIconSet([]);
reject('Expected to throw error on array');
reject(new Error('Expected to throw error on array'));
return;
} catch {
//
@ -42,19 +42,74 @@ describe('Testing validation', () => {
test('Valid set', () => {
expect(
validateIconSet({
prefix: 'foo',
prefix: 'fòó_bār',
icons: {
bar: {
body: '<g />',
},
// Characters that used to be invalid
fòó_: {
body: '<g />',
},
},
})
).toEqual({
prefix: 'foo',
prefix: 'fòó_bār',
icons: {
bar: {
body: '<g />',
},
fòó_: {
body: '<g />',
},
},
});
});
test('Bad icon names', () => {
// Empty name, not fixed
let threw = false;
try {
validateIconSet({
prefix: 'foo',
icons: {
// Cannot be empty
'': {
body: '<g />',
},
},
});
} catch {
threw = true;
}
expect(threw).toBeTruthy();
// Empty name, fixed
expect(
validateIconSet(
{
prefix: 'foo',
icons: {
// Empty name
'': {
body: '<g />',
},
// Characters that used to be invalid
'fòó_': {
body: '<g />',
},
},
},
{
fix: true,
}
)
).toEqual({
prefix: 'foo',
icons: {
fòó_: {
body: '<g />',
},
},
});
});
@ -65,7 +120,9 @@ describe('Testing validation', () => {
validateIconSet({
prefix: 'foo',
});
reject('Expected to throw error when icons are missing');
reject(
new Error('Expected to throw error when icons are missing')
);
return;
} catch {
//
@ -76,7 +133,9 @@ describe('Testing validation', () => {
prefix: 'foo',
icons: {},
});
reject('Expected to throw error when icons are empty');
reject(
new Error('Expected to throw error when icons are empty')
);
return;
} catch {
//
@ -84,7 +143,7 @@ describe('Testing validation', () => {
try {
validateIconSet([]);
reject('Expected to throw error on array');
reject(new Error('Expected to throw error on array'));
return;
} catch {
//
@ -149,7 +208,9 @@ describe('Testing validation', () => {
},
});
reject(
'Expected to throw error when character points to missing icon'
new Error(
'Expected to throw error when character points to missing icon'
)
);
return;
} catch {
@ -193,7 +254,11 @@ describe('Testing validation', () => {
test: 'bar',
},
});
reject('Expected to throw error when character is invalid');
reject(
new Error(
'Expected to throw error when character is invalid'
)
);
return;
} catch {
//
@ -253,7 +318,11 @@ describe('Testing validation', () => {
},
{ fix: true }
);
reject('Expected to throw error for bad default properties');
reject(
new Error(
'Expected to throw error for bad default properties'
)
);
return;
} catch {
//
@ -274,7 +343,11 @@ describe('Testing validation', () => {
},
{ fix: true }
);
reject('Expected to throw error for bad default properties');
reject(
new Error(
'Expected to throw error for bad default properties'
)
);
return;
} catch {
//
@ -295,7 +368,11 @@ describe('Testing validation', () => {
},
{ fix: true }
);
reject('Expected to throw error for bad default properties');
reject(
new Error(
'Expected to throw error for bad default properties'
)
);
return;
} catch {
//