2
0
mirror of https://github.com/iconify/iconify.git synced 2025-01-08 15:54:09 +00:00

Add class names to icons loaded from API in components, matching SVG framework functionality

This commit is contained in:
Vjacheslav Trushkin 2021-05-13 15:46:50 +03:00
parent 144604ce2b
commit 3f6265fdac
14 changed files with 313 additions and 85 deletions

View File

@ -2,7 +2,7 @@ import React from 'react';
import type { IconifyJSON } from '@iconify/types'; import type { IconifyJSON } from '@iconify/types';
// Core // Core
import type { IconifyIconName } from '@iconify/core/lib/icon/name'; import { IconifyIconName, stringToIcon } from '@iconify/core/lib/icon/name';
import type { import type {
IconifyIconSize, IconifyIconSize,
IconifyHorizontalIconAlignment, IconifyHorizontalIconAlignment,
@ -310,10 +310,13 @@ interface InternalIconProps extends IconProps {
_inline: boolean; _inline: boolean;
} }
type IconComponentData = Required<IconifyIcon> | null; interface IconComponentData {
data: Required<IconifyIcon>;
classes?: string[];
}
interface IconComponentState { interface IconComponentState {
data: IconComponentData; icon: IconComponentData | null;
} }
interface ComponentAbortData { interface ComponentAbortData {
@ -332,7 +335,7 @@ class IconComponent extends React.Component<
super(props); super(props);
this.state = { this.state = {
// Render placeholder before component is mounted // Render placeholder before component is mounted
data: null, icon: null,
}; };
} }
@ -349,10 +352,10 @@ class IconComponent extends React.Component<
/** /**
* Update state * Update state
*/ */
_setData(data: IconComponentData) { _setData(icon: IconComponentData | null) {
if (this.state.data !== data) { if (this.state.icon !== icon) {
this.setState({ this.setState({
data, icon,
}); });
} }
} }
@ -374,22 +377,28 @@ class IconComponent extends React.Component<
this._icon = ''; this._icon = '';
this._abortLoading(); this._abortLoading();
if (changed || state.data === null) { if (changed || state.icon === null) {
// Set data if it was changed // Set data if it was changed
this._setData(fullIcon(icon)); this._setData({
data: fullIcon(icon),
});
} }
return; return;
} }
// Invalid icon? // Invalid icon?
if (typeof icon !== 'string') { let iconName: IconifyIconName | null;
if (
typeof icon !== 'string' ||
(iconName = stringToIcon(icon, false, true)) === null
) {
this._abortLoading(); this._abortLoading();
this._setData(null); this._setData(null);
return; return;
} }
// Load icon // Load icon
const data = getIconData(icon); const data = getIconData(iconName);
if (data === null) { if (data === null) {
// Icon needs to be loaded // Icon needs to be loaded
if (!this._loading || this._loading.name !== icon) { if (!this._loading || this._loading.name !== icon) {
@ -400,7 +409,7 @@ class IconComponent extends React.Component<
this._loading = { this._loading = {
name: icon, name: icon,
abort: API.loadIcons( abort: API.loadIcons(
[icon], [iconName],
this._checkIcon.bind(this, false) this._checkIcon.bind(this, false)
), ),
}; };
@ -409,11 +418,25 @@ class IconComponent extends React.Component<
} }
// Icon data is available // Icon data is available
if (this._icon !== icon || state.data === null) { if (this._icon !== icon || state.icon === null) {
// New icon or icon has been loaded // New icon or icon has been loaded
this._abortLoading(); this._abortLoading();
this._icon = icon; this._icon = icon;
this._setData(data);
// Add classes
const classes: string[] = ['iconify'];
if (iconName.prefix !== '') {
classes.push('iconify--' + iconName.prefix);
}
if (iconName.provider !== '') {
classes.push('iconify--' + iconName.provider);
}
// Set data
this._setData({
data,
classes,
});
if (this.props.onLoad) { if (this.props.onLoad) {
this.props.onLoad(icon); this.props.onLoad(icon);
} }
@ -448,17 +471,28 @@ class IconComponent extends React.Component<
*/ */
render() { render() {
const props = this.props; const props = this.props;
const data = this.state.data; const icon = this.state.icon;
if (data === null) { if (icon === null) {
// Render placeholder // Render placeholder
return props.children return props.children
? (props.children as JSX.Element) ? (props.children as JSX.Element)
: React.createElement('span', {}); : React.createElement('span', {});
} }
// Add classes
let newProps = props;
if (icon.classes) {
newProps = merge(props, {
className:
(typeof props.className === 'string'
? props.className + ' '
: '') + icon.classes.join(' '),
} as typeof props);
}
// Render icon // Render icon
return render(data, props, props._inline, props._ref); return render(icon.data, newProps, props._inline, props._ref);
} }
} }

View File

@ -15,6 +15,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix(); const prefix = nextPrefix();
const name = 'render-test'; const name = 'render-test';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = false; let onLoadCalled = false;
mockAPIData({ mockAPIData({
@ -73,6 +74,7 @@ describe('Rendering icon', () => {
'height': '1em', 'height': '1em',
'preserveAspectRatio': 'xMidYMid meet', 'preserveAspectRatio': 'xMidYMid meet',
'viewBox': '0 0 ' + iconData.width + ' ' + iconData.height, 'viewBox': '0 0 ' + iconData.width + ' ' + iconData.height,
className,
}, },
children: null, children: null,
}); });
@ -88,6 +90,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix(); const prefix = nextPrefix();
const name = 'mock-test'; const name = 'mock-test';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = false; let onLoadCalled = false;
mockAPIData({ mockAPIData({
@ -137,6 +140,7 @@ describe('Rendering icon', () => {
iconData.width + iconData.width +
' ' + ' ' +
iconData.height, iconData.height,
'className': 'test ' + className,
}, },
children: null, children: null,
}); });
@ -157,6 +161,7 @@ describe('Rendering icon', () => {
const component = renderer.create( const component = renderer.create(
<Icon <Icon
icon={iconName} icon={iconName}
className="test"
onLoad={(name) => { onLoad={(name) => {
expect(name).toEqual(iconName); expect(name).toEqual(iconName);
expect(onLoadCalled).toEqual(false); expect(onLoadCalled).toEqual(false);

View File

@ -23,6 +23,7 @@ describe('Rendering icon', () => {
const name2 = 'changing-prop2'; const name2 = 'changing-prop2';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const iconName2 = `@${provider}:${prefix}:${name2}`; const iconName2 = `@${provider}:${prefix}:${name2}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = ''; // Name of icon from last onLoad call let onLoadCalled = ''; // Name of icon from last onLoad call
const onLoad = (name) => { const onLoad = (name) => {
@ -91,6 +92,7 @@ describe('Rendering icon', () => {
iconData.width + iconData.width +
' ' + ' ' +
iconData.height, iconData.height,
className,
}, },
children: null, children: null,
}); });
@ -151,6 +153,7 @@ describe('Rendering icon', () => {
iconData2.width + iconData2.width +
' ' + ' ' +
iconData2.height, iconData2.height,
className,
}, },
children: null, children: null,
}); });
@ -190,6 +193,7 @@ describe('Rendering icon', () => {
const name2 = 'changing-prop2'; const name2 = 'changing-prop2';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const iconName2 = `@${provider}:${prefix}:${name2}`; const iconName2 = `@${provider}:${prefix}:${name2}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let isSync = true; let isSync = true;
mockAPIData({ mockAPIData({
@ -257,6 +261,7 @@ describe('Rendering icon', () => {
iconData2.width + iconData2.width +
' ' + ' ' +
iconData2.height, iconData2.height,
className,
}, },
children: null, children: null,
}); });
@ -292,6 +297,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix(); const prefix = nextPrefix();
const name = 'multiple-props'; const name = 'multiple-props';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
mockAPIData({ mockAPIData({
provider, provider,
@ -336,6 +342,7 @@ describe('Rendering icon', () => {
iconData.width + iconData.width +
' ' + ' ' +
iconData.height, iconData.height,
className,
}, },
children: null, children: null,
}); });
@ -371,6 +378,7 @@ describe('Rendering icon', () => {
iconData.width + iconData.width +
' ' + ' ' +
iconData.height, iconData.height,
className,
}, },
children: null, children: null,
}); });

View File

@ -5,8 +5,7 @@ import { mockAPIData } from '@iconify/core/lib/api/modules/mock';
import { provider, nextPrefix } from './load'; import { provider, nextPrefix } from './load';
const iconData = { const iconData = {
body: body: '<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24, width: 24,
height: 24, height: 24,
}; };
@ -16,6 +15,7 @@ describe('Testing references', () => {
const prefix = nextPrefix(); const prefix = nextPrefix();
const name = 'render-test'; const name = 'render-test';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
mockAPIData({ mockAPIData({
provider, provider,
prefix, prefix,

View File

@ -26,7 +26,12 @@
// Generate data // Generate data
$: { $: {
counter; counter;
data = mounted ? generateIcon(checkIconState($$props.icon, state, loaded, $$props.onLoad), $$props) : null; const iconData = checkIconState($$props.icon, state, loaded, $$props.onLoad);
data = mounted && iconData ? generateIcon(iconData.data, $$props) : null;
if (data && iconData.classes) {
// Add classes
data.attributes['class'] = (typeof $$props['class'] === 'string' ? $$props['class'] + ' ' : '') + iconData.classes.join(' ');
}
} }
// Increase counter when loaded to force re-calculation of data // Increase counter when loaded to force re-calculation of data

View File

@ -1,7 +1,7 @@
import type { IconifyJSON } from '@iconify/types'; import type { IconifyJSON } from '@iconify/types';
// Core // Core
import type { IconifyIconName } from '@iconify/core/lib/icon/name'; import { IconifyIconName, stringToIcon } from '@iconify/core/lib/icon/name';
import type { import type {
IconifyIconSize, IconifyIconSize,
IconifyHorizontalIconAlignment, IconifyHorizontalIconAlignment,
@ -326,6 +326,14 @@ type IconStateCallback = () => void;
*/ */
export type IconifyIconOnLoad = (name: string) => void; export type IconifyIconOnLoad = (name: string) => void;
/**
* checkIconState result
*/
export interface CheckIconStateResult {
data: IconComponentData;
classes?: string[];
}
/** /**
* Check if component needs to be updated * Check if component needs to be updated
*/ */
@ -334,7 +342,7 @@ export function checkIconState(
state: IconState, state: IconState,
callback: IconStateCallback, callback: IconStateCallback,
onload?: IconifyIconOnLoad onload?: IconifyIconOnLoad
): IconComponentData { ): CheckIconStateResult | null {
// Abort loading icon // Abort loading icon
function abortLoading() { function abortLoading() {
if (state.loading) { if (state.loading) {
@ -352,17 +360,21 @@ export function checkIconState(
// Stop loading // Stop loading
state.name = ''; state.name = '';
abortLoading(); abortLoading();
return fullIcon(icon); return { data: fullIcon(icon) };
} }
// Invalid icon // Invalid icon?
if (typeof icon !== 'string') { let iconName: IconifyIconName | null;
if (
typeof icon !== 'string' ||
(iconName = stringToIcon(icon, false, true)) === null
) {
abortLoading(); abortLoading();
return null; return null;
} }
// Load icon // Load icon
const data = getIconData(icon); const data = getIconData(iconName);
if (data === null) { if (data === null) {
// Icon needs to be loaded // Icon needs to be loaded
if (!state.loading || state.loading.name !== icon) { if (!state.loading || state.loading.name !== icon) {
@ -371,7 +383,7 @@ export function checkIconState(
state.name = ''; state.name = '';
state.loading = { state.loading = {
name: icon, name: icon,
abort: API.loadIcons([icon], callback), abort: API.loadIcons([iconName], callback),
}; };
} }
return null; return null;
@ -385,7 +397,17 @@ export function checkIconState(
onload(icon); onload(icon);
} }
} }
return data;
// Add classes
const classes: string[] = ['iconify'];
if (iconName.prefix !== '') {
classes.push('iconify--' + iconName.prefix);
}
if (iconName.provider !== '') {
classes.push('iconify--' + iconName.provider);
}
return { data, classes };
} }
/** /**

View File

@ -4,8 +4,7 @@ import { mockAPIData } from '@iconify/core/lib/api/modules/mock';
import { provider, nextPrefix } from './load'; import { provider, nextPrefix } from './load';
const iconData = { const iconData = {
body: body: '<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24, width: 24,
height: 24, height: 24,
}; };
@ -15,6 +14,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix(); const prefix = nextPrefix();
const name = 'render-test'; const name = 'render-test';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = false; let onLoadCalled = false;
mockAPIData({ mockAPIData({
@ -59,7 +59,9 @@ describe('Rendering icon', () => {
// Check HTML // Check HTML
expect(html).toEqual( expect(html).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="' +
className +
'"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
); );
// Make sure onLoad has been called // Make sure onLoad has been called
@ -73,6 +75,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix(); const prefix = nextPrefix();
const name = 'mock-test'; const name = 'mock-test';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = false; let onLoadCalled = false;
mockAPIData({ mockAPIData({
@ -106,7 +109,9 @@ describe('Rendering icon', () => {
// Check HTML // Check HTML
expect(html).toEqual( expect(html).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="test ' +
className +
'" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
); );
// onLoad should have been called // onLoad should have been called
@ -124,6 +129,8 @@ describe('Rendering icon', () => {
// Render component // Render component
const component = render(Icon, { const component = render(Icon, {
icon: iconName, icon: iconName,
// Also testing simple class
class: 'test',
onLoad: (name) => { onLoad: (name) => {
expect(name).toEqual(iconName); expect(name).toEqual(iconName);
expect(onLoadCalled).toEqual(false); expect(onLoadCalled).toEqual(false);

View File

@ -6,15 +6,13 @@ import ChangeIcon from './fixtures/ChangeIcon.svelte';
import ChangeProps from './fixtures/ChangeProps.svelte'; import ChangeProps from './fixtures/ChangeProps.svelte';
const iconData = { const iconData = {
body: body: '<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24, width: 24,
height: 24, height: 24,
}; };
const iconData2 = { const iconData2 = {
body: body: '<path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"/>',
'<path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"/>',
width: 32, width: 32,
height: 32, height: 32,
}; };
@ -26,6 +24,7 @@ describe('Rendering icon', () => {
const name2 = 'changing-prop2'; const name2 = 'changing-prop2';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const iconName2 = `@${provider}:${prefix}:${name2}`; const iconName2 = `@${provider}:${prefix}:${name2}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = ''; // Name of icon from last onLoad call let onLoadCalled = ''; // Name of icon from last onLoad call
let triggerSwap; let triggerSwap;
@ -63,7 +62,9 @@ describe('Rendering icon', () => {
// Check HTML // Check HTML
expect(html).toEqual( expect(html).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="' +
className +
'"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
); );
// onLoad should have been called // onLoad should have been called
@ -107,7 +108,9 @@ describe('Rendering icon', () => {
// Check HTML // Check HTML
expect(html).toEqual( expect(html).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32" class="' +
className +
'"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>'
); );
// onLoad should have been called for second icon // onLoad should have been called for second icon
@ -163,6 +166,7 @@ describe('Rendering icon', () => {
const name2 = 'changing-prop2'; const name2 = 'changing-prop2';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const iconName2 = `@${provider}:${prefix}:${name2}`; const iconName2 = `@${provider}:${prefix}:${name2}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = ''; // Name of icon from last onLoad call let onLoadCalled = ''; // Name of icon from last onLoad call
let isSync = true; let isSync = true;
let triggerSwap; let triggerSwap;
@ -219,7 +223,9 @@ describe('Rendering icon', () => {
// Check HTML // Check HTML
expect(html).toEqual( expect(html).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32" class="' +
className +
'"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>'
); );
// onLoad should have been called for second icon // onLoad should have been called for second icon
@ -269,6 +275,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix(); const prefix = nextPrefix();
const name = 'multiple-props'; const name = 'multiple-props';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = false; let onLoadCalled = false;
let triggerSwap; let triggerSwap;
@ -303,7 +310,9 @@ describe('Rendering icon', () => {
// Check HTML // Check HTML
expect(html).toEqual( expect(html).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="' +
className +
'"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
); );
// onLoad should have been called // onLoad should have been called
@ -316,13 +325,14 @@ describe('Rendering icon', () => {
setTimeout(() => { setTimeout(() => {
setTimeout(() => { setTimeout(() => {
// Check HTML again // Check HTML again
const node = component.container.querySelector( const node =
'svg' component.container.querySelector('svg');
);
const html = node.parentNode.innerHTML; const html = node.parentNode.innerHTML;
expect(html).toEqual( expect(html).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g transform="translate(24 0) scale(-1 1)"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></g></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="' +
className +
'"><g transform="translate(24 0) scale(-1 1)"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></g></svg>'
); );
done(); done();

View File

@ -11,7 +11,7 @@ import {
import { IconifyJSON } from '@iconify/types'; import { IconifyJSON } from '@iconify/types';
// Core // Core
import { IconifyIconName } from '@iconify/core/lib/icon/name'; import { IconifyIconName, stringToIcon } from '@iconify/core/lib/icon/name';
import { import {
IconifyIconSize, IconifyIconSize,
IconifyHorizontalIconAlignment, IconifyHorizontalIconAlignment,
@ -27,8 +27,9 @@ import {
IconifyBuilderFunctions, IconifyBuilderFunctions,
builderFunctions, builderFunctions,
} from '@iconify/core/lib/builder/functions'; } from '@iconify/core/lib/builder/functions';
import type { IconifyIconBuildResult } from '@iconify/core/lib/builder'; import { IconifyIconBuildResult } from '@iconify/core/lib/builder';
import { fullIcon, IconifyIcon } from '@iconify/core/lib/icon'; import { fullIcon, IconifyIcon } from '@iconify/core/lib/icon';
import { merge } from '@iconify/core/lib/misc/merge';
// Modules // Modules
import { coreModules } from '@iconify/core/lib/modules'; import { coreModules } from '@iconify/core/lib/modules';
@ -312,6 +313,11 @@ if (typeof document !== 'undefined' && typeof window !== 'undefined') {
/** /**
* Component * Component
*/ */
interface IconComponentData {
data: Required<IconifyIcon>;
classes?: string[];
}
export const Icon = defineComponent({ export const Icon = defineComponent({
// Do not inherit other attributes: it is handled by render() // Do not inherit other attributes: it is handled by render()
inheritAttrs: false, inheritAttrs: false,
@ -350,7 +356,10 @@ export const Icon = defineComponent({
} }
}, },
// Get data for icon to render or null // Get data for icon to render or null
getIcon(icon: IconifyIcon | string, onload?: IconifyIconOnLoad) { getIcon(
icon: IconifyIcon | string,
onload?: IconifyIconOnLoad
): IconComponentData | null {
// Icon is an object // Icon is an object
if ( if (
typeof icon === 'object' && typeof icon === 'object' &&
@ -360,17 +369,23 @@ export const Icon = defineComponent({
// Stop loading // Stop loading
this._name = ''; this._name = '';
this.abortLoading(); this.abortLoading();
return fullIcon(icon); return {
data: fullIcon(icon),
};
} }
// Invalid icon? // Invalid icon?
if (typeof icon !== 'string') { let iconName: IconifyIconName | null;
if (
typeof icon !== 'string' ||
(iconName = stringToIcon(icon, false, true)) === null
) {
this.abortLoading(); this.abortLoading();
return null; return null;
} }
// Load icon // Load icon
const data = getIconData(icon); const data = getIconData(iconName);
if (data === null) { if (data === null) {
// Icon needs to be loaded // Icon needs to be loaded
if (!this._loadingIcon || this._loadingIcon.name !== icon) { if (!this._loadingIcon || this._loadingIcon.name !== icon) {
@ -379,7 +394,7 @@ export const Icon = defineComponent({
this._name = ''; this._name = '';
this._loadingIcon = { this._loadingIcon = {
name: icon, name: icon,
abort: API.loadIcons([icon], () => { abort: API.loadIcons([iconName], () => {
this.counter++; this.counter++;
}), }),
}; };
@ -395,7 +410,17 @@ export const Icon = defineComponent({
onload(icon); onload(icon);
} }
} }
return data;
// Add classes
const classes: string[] = ['iconify'];
if (iconName.prefix !== '') {
classes.push('iconify--' + iconName.prefix);
}
if (iconName.provider !== '') {
classes.push('iconify--' + iconName.provider);
}
return { data, classes };
}, },
}, },
@ -410,14 +435,28 @@ export const Icon = defineComponent({
// Get icon data // Get icon data
const props = this.$attrs; const props = this.$attrs;
const icon = this.getIcon(props.icon, props.onLoad); const icon: IconComponentData | null = this.getIcon(
props.icon,
props.onLoad
);
// Validate icon object // Validate icon object
if (!icon) { if (!icon) {
return this.$slots.default ? this.$slots.default() : null; return this.$slots.default ? this.$slots.default() : null;
} }
// Valid icon: render it // Add classes
return render(icon, props); let newProps = props;
if (icon.classes) {
newProps = merge(props, {
class:
(typeof props['class'] === 'string'
? props['class'] + ' '
: '') + icon.classes.join(' '),
});
}
// Render icon
return render(icon.data, newProps);
}, },
}); });

View File

@ -15,6 +15,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix(); const prefix = nextPrefix();
const name = 'render-test'; const name = 'render-test';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = false; let onLoadCalled = false;
mockAPIData({ mockAPIData({
@ -48,7 +49,8 @@ describe('Rendering icon', () => {
// Render component // Render component
const Wrapper = { const Wrapper = {
components: { Icon }, components: { Icon },
template: `<Icon icon="${iconName}" :onLoad='onLoad' />`, // Also test class string
template: `<Icon icon="${iconName}" :onLoad="onLoad" class="test" />`,
methods: { methods: {
onLoad(name) { onLoad(name) {
expect(name).toEqual(iconName); expect(name).toEqual(iconName);
@ -62,7 +64,9 @@ describe('Rendering icon', () => {
// Check HTML // Check HTML
expect(html).toEqual( expect(html).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="test ' +
className +
'" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
); );
// Make sure onLoad has been called // Make sure onLoad has been called
@ -76,6 +80,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix(); const prefix = nextPrefix();
const name = 'mock-test'; const name = 'mock-test';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = false; let onLoadCalled = false;
mockAPIData({ mockAPIData({
@ -106,7 +111,9 @@ describe('Rendering icon', () => {
setTimeout(() => { setTimeout(() => {
// Check HTML // Check HTML
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual( expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="foo ' +
className +
'" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
); );
// onLoad should have been called // onLoad should have been called
@ -124,7 +131,7 @@ describe('Rendering icon', () => {
// Render component // Render component
const Wrapper = { const Wrapper = {
components: { Icon }, components: { Icon },
template: `<Icon icon="${iconName}" :onLoad='onLoad' />`, template: `<Icon icon="${iconName}" :onLoad="onLoad" :class="testClass" />`,
methods: { methods: {
onLoad(name) { onLoad(name) {
expect(name).toEqual(iconName); expect(name).toEqual(iconName);
@ -132,6 +139,15 @@ describe('Rendering icon', () => {
onLoadCalled = true; onLoadCalled = true;
}, },
}, },
data() {
// Test dynamic class
return {
testClass: {
foo: true,
bar: false,
},
};
},
}; };
const wrapper = mount(Wrapper, {}); const wrapper = mount(Wrapper, {});

View File

@ -24,6 +24,7 @@ describe('Rendering icon', () => {
const name2 = 'changing-prop2'; const name2 = 'changing-prop2';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const iconName2 = `@${provider}:${prefix}:${name2}`; const iconName2 = `@${provider}:${prefix}:${name2}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = ''; // Name of icon from last onLoad call let onLoadCalled = ''; // Name of icon from last onLoad call
const onLoad = name => { const onLoad = name => {
@ -72,7 +73,9 @@ describe('Rendering icon', () => {
setTimeout(() => { setTimeout(() => {
setTimeout(() => { setTimeout(() => {
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual( expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="' +
className +
'" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
); );
// onLoad should have been called // onLoad should have been called
@ -113,7 +116,9 @@ describe('Rendering icon', () => {
setTimeout(() => { setTimeout(() => {
setTimeout(() => { setTimeout(() => {
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual( expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="' +
className +
'" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>'
); );
// onLoad should have been called for second icon // onLoad should have been called for second icon
@ -151,6 +156,7 @@ describe('Rendering icon', () => {
const name2 = 'changing-prop2'; const name2 = 'changing-prop2';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const iconName2 = `@${provider}:${prefix}:${name2}`; const iconName2 = `@${provider}:${prefix}:${name2}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let isSync = true; let isSync = true;
mockAPIData({ mockAPIData({
@ -198,7 +204,9 @@ describe('Rendering icon', () => {
setTimeout(() => { setTimeout(() => {
setTimeout(() => { setTimeout(() => {
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual( expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="' +
className +
'" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>'
); );
done(); done();
@ -234,6 +242,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix(); const prefix = nextPrefix();
const name = 'multiple-props'; const name = 'multiple-props';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
mockAPIData({ mockAPIData({
provider, provider,
@ -259,7 +268,9 @@ describe('Rendering icon', () => {
setTimeout(() => { setTimeout(() => {
setTimeout(() => { setTimeout(() => {
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual( expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="' +
className +
'" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
); );
// Add horizontal flip and style // Add horizontal flip and style
@ -276,7 +287,9 @@ describe('Rendering icon', () => {
expect( expect(
wrapper.html().replace(/\s*\n\s*/g, '') wrapper.html().replace(/\s*\n\s*/g, '')
).toEqual( ).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" style="color: red;"><g transform="translate(24 0) scale(-1 1)"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></g></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="' +
className +
'" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" style="color: red;"><g transform="translate(24 0) scale(-1 1)"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></g></svg>'
); );
done(); done();

View File

@ -3,7 +3,7 @@ import { ExtendedVue } from 'vue/types/vue';
import { IconifyJSON } from '@iconify/types'; import { IconifyJSON } from '@iconify/types';
// Core // Core
import { IconifyIconName } from '@iconify/core/lib/icon/name'; import { IconifyIconName, stringToIcon } from '@iconify/core/lib/icon/name';
import { import {
IconifyIconSize, IconifyIconSize,
IconifyHorizontalIconAlignment, IconifyHorizontalIconAlignment,
@ -19,8 +19,9 @@ import {
IconifyBuilderFunctions, IconifyBuilderFunctions,
builderFunctions, builderFunctions,
} from '@iconify/core/lib/builder/functions'; } from '@iconify/core/lib/builder/functions';
import type { IconifyIconBuildResult } from '@iconify/core/lib/builder'; import { IconifyIconBuildResult } from '@iconify/core/lib/builder';
import { fullIcon, IconifyIcon } from '@iconify/core/lib/icon'; import { fullIcon, IconifyIcon } from '@iconify/core/lib/icon';
import { merge } from '@iconify/core/lib/misc/merge';
// Modules // Modules
import { coreModules } from '@iconify/core/lib/modules'; import { coreModules } from '@iconify/core/lib/modules';
@ -304,6 +305,11 @@ if (typeof document !== 'undefined' && typeof window !== 'undefined') {
/** /**
* Component * Component
*/ */
interface IconComponentData {
data: Required<IconifyIcon>;
classes?: string[];
}
export const Icon = Vue.extend({ export const Icon = Vue.extend({
// Do not inherit other attributes: it is handled by render() // Do not inherit other attributes: it is handled by render()
// In Vue 2 style is still passed! // In Vue 2 style is still passed!
@ -340,7 +346,10 @@ export const Icon = Vue.extend({
} }
}, },
// Get data for icon to render or null // Get data for icon to render or null
getIcon(icon: IconifyIcon | string, onload?: IconifyIconOnLoad) { getIcon(
icon: IconifyIcon | string,
onload?: IconifyIconOnLoad
): IconComponentData | null {
// Icon is an object // Icon is an object
if ( if (
typeof icon === 'object' && typeof icon === 'object' &&
@ -350,17 +359,23 @@ export const Icon = Vue.extend({
// Stop loading // Stop loading
this._name = ''; this._name = '';
this.abortLoading(); this.abortLoading();
return fullIcon(icon); return {
data: fullIcon(icon),
};
} }
// Invalid icon? // Invalid icon?
if (typeof icon !== 'string') { let iconName: IconifyIconName | null;
if (
typeof icon !== 'string' ||
(iconName = stringToIcon(icon, false, true)) === null
) {
this.abortLoading(); this.abortLoading();
return null; return null;
} }
// Load icon // Load icon
const data = getIconData(icon); const data = getIconData(iconName);
if (data === null) { if (data === null) {
// Icon needs to be loaded // Icon needs to be loaded
if (!this._loadingIcon || this._loadingIcon.name !== icon) { if (!this._loadingIcon || this._loadingIcon.name !== icon) {
@ -369,7 +384,7 @@ export const Icon = Vue.extend({
this._name = ''; this._name = '';
this._loadingIcon = { this._loadingIcon = {
name: icon, name: icon,
abort: API.loadIcons([icon], () => { abort: API.loadIcons([iconName], () => {
this.$forceUpdate(); this.$forceUpdate();
}), }),
}; };
@ -385,7 +400,17 @@ export const Icon = Vue.extend({
onload(icon); onload(icon);
} }
} }
return data;
// Add classes
const classes: string[] = ['iconify'];
if (iconName.prefix !== '') {
classes.push('iconify--' + iconName.prefix);
}
if (iconName.provider !== '') {
classes.push('iconify--' + iconName.provider);
}
return { data, classes };
}, },
}, },
@ -410,7 +435,10 @@ export const Icon = Vue.extend({
// Get icon data // Get icon data
const props = this.$attrs; const props = this.$attrs;
const icon = this.getIcon(props.icon, props.onLoad); const icon: IconComponentData | null = this.getIcon(
props.icon,
props.onLoad
);
// Validate icon object // Validate icon object
if (!icon) { if (!icon) {
@ -418,7 +446,18 @@ export const Icon = Vue.extend({
return placeholder(this.$slots); return placeholder(this.$slots);
} }
// Valid icon: render it // Add classes
return render(createElement, props, this.$data, icon); let context = this.$data;
if (icon.classes) {
context = merge(context, {
class:
(typeof context['class'] === 'string'
? context['class'] + ' '
: '') + icon.classes.join(' '),
});
}
// Render icon
return render(createElement, props, context, icon.data);
}, },
}); });

View File

@ -14,6 +14,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix(); const prefix = nextPrefix();
const name = 'render-test'; const name = 'render-test';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = false; let onLoadCalled = false;
mockAPIData({ mockAPIData({
@ -47,7 +48,8 @@ describe('Rendering icon', () => {
// Render component // Render component
const Wrapper = { const Wrapper = {
components: { Icon }, components: { Icon },
template: `<Icon icon="${iconName}" :onLoad='onLoad' />`, // Also test class string
template: `<Icon icon="${iconName}" :onLoad="onLoad" class="test" />`,
methods: { methods: {
onLoad(name) { onLoad(name) {
expect(name).toEqual(iconName); expect(name).toEqual(iconName);
@ -61,7 +63,9 @@ describe('Rendering icon', () => {
// Check HTML // Check HTML
expect(html).toEqual( expect(html).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="test ' +
className +
'"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
); );
// Make sure onLoad has been called // Make sure onLoad has been called
@ -75,6 +79,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix(); const prefix = nextPrefix();
const name = 'mock-test'; const name = 'mock-test';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = false; let onLoadCalled = false;
mockAPIData({ mockAPIData({
@ -105,7 +110,10 @@ describe('Rendering icon', () => {
setTimeout(() => { setTimeout(() => {
// Check HTML // Check HTML
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual( expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="' +
className +
// 'foo' is appended because of weird Vue 2 behavior. Fixed in Vue 3
' foo"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
); );
// onLoad should have been called // onLoad should have been called
@ -123,7 +131,7 @@ describe('Rendering icon', () => {
// Render component // Render component
const Wrapper = { const Wrapper = {
components: { Icon }, components: { Icon },
template: `<Icon icon="${iconName}" :onLoad='onLoad' />`, template: `<Icon icon="${iconName}" :onLoad="onLoad" :class="testClass" />`,
methods: { methods: {
onLoad(name) { onLoad(name) {
expect(name).toEqual(iconName); expect(name).toEqual(iconName);
@ -131,6 +139,15 @@ describe('Rendering icon', () => {
onLoadCalled = true; onLoadCalled = true;
}, },
}, },
data() {
// Test dynamic class
return {
testClass: {
foo: true,
bar: false,
},
};
},
}; };
const wrapper = mount(Wrapper, {}); const wrapper = mount(Wrapper, {});
@ -177,7 +194,7 @@ describe('Rendering icon', () => {
// Render component // Render component
const Wrapper = { const Wrapper = {
components: { Icon }, components: { Icon },
template: `<Icon icon="${iconName}" :onLoad='onLoad' />`, template: `<Icon icon="${iconName}" :onLoad="onLoad" />`,
methods: { methods: {
onLoad() { onLoad() {
throw new Error('onLoad called for empty icon!'); throw new Error('onLoad called for empty icon!');

View File

@ -22,6 +22,7 @@ describe('Rendering icon', () => {
const name2 = 'changing-prop2'; const name2 = 'changing-prop2';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const iconName2 = `@${provider}:${prefix}:${name2}`; const iconName2 = `@${provider}:${prefix}:${name2}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = ''; // Name of icon from last onLoad call let onLoadCalled = ''; // Name of icon from last onLoad call
const onLoad = (name) => { const onLoad = (name) => {
@ -70,7 +71,9 @@ describe('Rendering icon', () => {
setTimeout(() => { setTimeout(() => {
setTimeout(() => { setTimeout(() => {
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual( expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="' +
className +
'"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
); );
// onLoad should have been called // onLoad should have been called
@ -111,7 +114,9 @@ describe('Rendering icon', () => {
setTimeout(() => { setTimeout(() => {
setTimeout(() => { setTimeout(() => {
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual( expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32" class="' +
className +
'"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>'
); );
// onLoad should have been called for second icon // onLoad should have been called for second icon
@ -147,6 +152,7 @@ describe('Rendering icon', () => {
const name2 = 'changing-prop2'; const name2 = 'changing-prop2';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const iconName2 = `@${provider}:${prefix}:${name2}`; const iconName2 = `@${provider}:${prefix}:${name2}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let isSync = true; let isSync = true;
mockAPIData({ mockAPIData({
@ -194,7 +200,9 @@ describe('Rendering icon', () => {
setTimeout(() => { setTimeout(() => {
setTimeout(() => { setTimeout(() => {
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual( expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32" class="' +
className +
'"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>'
); );
done(); done();
@ -229,6 +237,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix(); const prefix = nextPrefix();
const name = 'multiple-props'; const name = 'multiple-props';
const iconName = `@${provider}:${prefix}:${name}`; const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
mockAPIData({ mockAPIData({
provider, provider,
@ -254,7 +263,9 @@ describe('Rendering icon', () => {
setTimeout(() => { setTimeout(() => {
setTimeout(() => { setTimeout(() => {
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual( expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="' +
className +
'"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
); );
// Add horizontal flip and style // Add horizontal flip and style
@ -271,7 +282,9 @@ describe('Rendering icon', () => {
expect( expect(
wrapper.html().replace(/\s*\n\s*/g, '') wrapper.html().replace(/\s*\n\s*/g, '')
).toEqual( ).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" style="color: red;"><g transform="translate(24 0) scale(-1 1)"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></g></svg>' '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="' +
className +
'" style="color: red;"><g transform="translate(24 0) scale(-1 1)"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></g></svg>'
); );
done(); done();