2
0
mirror of https://github.com/iconify/iconify.git synced 2024-11-09 23:00:56 +00:00

Refectoring React: working API component

This commit is contained in:
Vjacheslav Trushkin 2021-04-27 19:21:35 +03:00
parent e1fa93db6b
commit b46498b8fd
13 changed files with 1225 additions and 100 deletions

View File

@ -7,7 +7,7 @@ import { setAPIModule } from '../../lib/api/modules';
import { API } from '../../lib/api/';
import type { IconifyMockAPIDelayDoneCallback } from '../../lib/api/modules/mock';
import { mockAPIModule, mockAPIData } from '../../lib/api/modules/mock';
import { allowSimpleNames } from '../../lib/storage/functions';
import { getStorage, iconExists } from '../../lib/storage/storage';
describe('Testing mock API module', () => {
let prefixCounter = 0;
@ -238,4 +238,46 @@ describe('Testing mock API module', () => {
}
);
});
// This is useful for testing component where loadIcons() cannot be accessed
it('Using timer in callback for second test', (done) => {
const prefix = nextPrefix();
const name = 'test1';
// Mock data
mockAPIData({
provider,
prefix,
response: {
prefix,
icons: {
[name]: {
body: '<g />',
},
},
},
delay: (next) => {
// Icon should not be loaded yet
const storage = getStorage(provider, prefix);
expect(iconExists(storage, name)).to.be.equal(false);
// Set data
next();
// Icon should be loaded now
expect(iconExists(storage, name)).to.be.equal(true);
done();
},
});
// Load icons
API.loadIcons([
{
provider,
prefix,
name,
},
]);
});
});

View File

@ -30,6 +30,7 @@
"devDependencies": {
"@iconify-icons/mdi-light": "^1.1.0",
"@iconify-icons/uil": "^1.1.1",
"@iconify/core": "^1.0.0-rc.4",
"@iconify/react": "^3.0.0-dev"
}
}

View File

@ -6,6 +6,7 @@ import {
import {
addIcon as addOnlineIcon,
addCollection as addOnlineCollection,
disableCache,
} from '@iconify/react/dist/iconify';
import presentationPlay from '@iconify-icons/mdi-light/presentation-play';
import playIcon from '@iconify-icons/mdi-light/play';
@ -14,11 +15,16 @@ import { Checkbox } from './demo-components/Checkbox';
import { InlineDemo } from './demo-components/Inline';
import { OfflineUsageDemo } from './demo-components/UsageOffline';
import { FullOfflineUsageDemo } from './demo-components/UsageFullOffline';
import { FullUsageDemo } from './demo-components/UsageFull';
import { TestsOffline } from './test-components/TestsOffline';
import { TestsFullOffline } from './test-components/TestsFullOffline';
import { TestsFull } from './test-components/TestsFull';
import './App.css';
// Disable cache
disableCache('all');
// Add 'mdi-light:presentation-play' as 'demo' for offline module
addOfflineIcon('demo', presentationPlay);
@ -76,6 +82,7 @@ function App() {
<div className="App">
<OfflineUsageDemo />
<FullOfflineUsageDemo />
<FullUsageDemo />
<section>
<h1>Checkbox</h1>
@ -97,6 +104,7 @@ function App() {
<TestsOffline />
<TestsFullOffline />
<TestsFull />
</div>
);
}

View File

@ -0,0 +1,17 @@
import React from 'react';
import { Icon } from '@iconify/react/dist/iconify';
export function FullUsageDemo() {
return (
<section className="icon-24">
<h1>Usage (full module)</h1>
<div>
Icon referenced by name: <Icon icon="mdi:home" />
</div>
<div className="alert">
<Icon icon="mdi-light:alert" />
Important notice with alert icon!
</div>
</section>
);
}

View File

@ -0,0 +1,243 @@
import React from 'react';
import { InlineIcon, addAPIProvider, _api } from '@iconify/react/dist/iconify';
import { mockAPIModule, mockAPIData } from '@iconify/core/lib/api/modules/mock';
import { TestIcons, toggleTest } from './TestIcons';
import playIcon from '@iconify-icons/mdi-light/map-marker';
// API provider for tests
const provider = 'mock-api';
const prefix = 'demo';
// Set API module for provider
addAPIProvider(provider, {
resources: 'http://localhost',
rotate: 10000,
timeout: 10000,
});
_api.setAPIModule(provider, mockAPIModule);
// Set mock data
mockAPIData({
provider,
prefix,
response: {
prefix,
icons: {
icon: playIcon,
},
},
delay: 2000,
});
export function TestsFull() {
const icon = `@${provider}:${prefix}:icon`;
return (
<section className="tests">
<h1>Tests (full module, with API)</h1>
<h2>References</h2>
<p>Icons should load 2 seconds after page load</p>
<div className="test-row">
<TestIcons id="full-ref1" />
Getting reference
<InlineIcon
icon={icon}
ref={(element) => {
const key = 'full-ref1';
if (element && element.tagName === 'svg') {
toggleTest(key, 'success');
} else {
toggleTest(key, 'failed');
}
}}
/>
</div>
<div className="test-row">
<TestIcons id="full-ref-missing" status="success" />
Getting reference for empty icon
<InlineIcon
ref={(element) => {
// Cannot be called because there is no SVG to render!
toggleTest('full-ref-missing', 'failed');
}}
/>
</div>
<div className="test-row">
<TestIcons id="full-ref-missing2" status="success" />
Getting reference for missing icon with fallback text{' '}
<InlineIcon
ref={(element) => {
// Cannot be called because there is no SVG to render!
toggleTest('full-ref-missing2', 'failed');
}}
>
😀
</InlineIcon>
</div>
<h2>Style</h2>
<div className="test-row">
<TestIcons id="full-style" />
Inline style for icon
<InlineIcon
icon={icon}
style={{
color: '#1769aa',
fontSize: '24px',
lineHeight: '1em',
verticalAlign: '-0.25em',
}}
ref={(element) => {
const key = 'full-style';
if (element && element.tagName === 'svg') {
let errors = false;
// Get style
const style = element.style;
switch (style.color.toLowerCase()) {
case 'rgb(23, 105, 170)':
case '#1769aa':
break;
default:
console.log('Invalid color:', style.color);
errors = true;
}
if (style.fontSize !== '24px') {
console.log(
'Invalid font-size:',
style.fontSize
);
errors = true;
}
if (style.verticalAlign !== '-0.25em') {
console.log(
'Invalid vertical-align:',
style.verticalAlign
);
errors = true;
}
toggleTest(key, !errors);
} else {
toggleTest(key, 'failed');
}
}}
/>
</div>
<div className="test-row">
<TestIcons id="full-color1" />
Green color from attribute:{' '}
<InlineIcon
icon={icon}
color="green"
ref={(element) => {
const key = 'full-color1';
if (element && element.tagName === 'svg') {
let errors = false;
// Get style
const style = element.style;
switch (style.color.toLowerCase()) {
case 'rgb(0, 128, 0)':
case '#008000':
case 'green':
break;
default:
console.log('Invalid color:', style.color);
errors = true;
}
toggleTest(key, !errors);
} else {
toggleTest(key, 'failed');
}
}}
/>
</div>
<div className="test-row">
<TestIcons id="full-color2" />
Green color from style:{' '}
<InlineIcon
icon={icon}
style={{
color: 'green',
}}
ref={(element) => {
const key = 'full-color2';
if (element && element.tagName === 'svg') {
let errors = false;
// Get style
const style = element.style;
switch (style.color.toLowerCase()) {
case 'rgb(0, 128, 0)':
case '#008000':
case 'green':
break;
default:
console.log('Invalid color:', style.color);
errors = true;
}
toggleTest(key, !errors);
} else {
toggleTest(key, 'failed');
}
}}
/>
</div>
<div className="test-row">
<TestIcons id="full-color3" />
Green color from attribute (overrides style) + red from style:{' '}
<InlineIcon
icon={icon}
color="green"
style={{
color: 'red',
}}
ref={(element) => {
const key = 'full-color3';
if (element && element.tagName === 'svg') {
let errors = false;
// Get style
const style = element.style;
switch (style.color.toLowerCase()) {
case 'rgb(0, 128, 0)':
case '#008000':
case 'green':
break;
default:
console.log('Invalid color:', style.color);
errors = true;
}
toggleTest(key, !errors);
} else {
toggleTest(key, 'failed');
}
}}
/>
</div>
</section>
);
}

View File

@ -74,6 +74,7 @@ import type {
// Render SVG
import { render } from './render';
import { merge } from '@iconify/core/lib/misc/merge';
/**
* Export required types
@ -292,37 +293,154 @@ if (typeof document !== 'undefined' && typeof window !== 'undefined') {
/**
* Component
*/
function component(
props: IconProps,
inline: boolean,
ref?: IconRef
): JSX.Element {
const icon = props.icon;
interface InternalIconProps extends IconProps {
_ref?: IconRef;
_inline: boolean;
}
// Check if icon is an object
type IconComponentData = Required<IconifyIcon> | null;
interface IconComponentState {
data: IconComponentData;
}
interface ComponentAbortData {
name: string;
abort: IconifyIconLoaderAbort;
}
class IconComponent extends React.Component<
InternalIconProps,
IconComponentState
> {
protected _icon: string;
protected _loading: ComponentAbortData | null;
constructor(props: InternalIconProps) {
super(props);
this.state = {
// Render placeholder before component is mounted
data: null,
};
}
/**
* Abort loading icon
*/
_abortLoading() {
if (this._loading) {
this._loading.abort();
this._loading = null;
}
}
/**
* Update state
*/
_setData(data: IconComponentData) {
if (this.state.data !== data) {
this.setState({
data,
});
}
}
/**
* Check if icon should be loaded
*/
_checkIcon(changed: boolean) {
const state = this.state;
const icon = this.props.icon;
// Icon is an object
if (typeof icon === 'object' && typeof icon.body === 'string') {
return render(fullIcon(icon), props, inline, ref);
// Stop loading
this._icon = '';
this._abortLoading();
if (changed || state.data === null) {
// Set data if it was changed
this._setData(fullIcon(icon));
}
return;
}
// Check if icon is a string
if (typeof icon === 'string') {
const iconName = stringToIcon(icon, true, true);
if (iconName) {
// Valid icon name
const iconData = getIconData(iconName);
if (iconData) {
// Icon is available
return render(iconData, props, inline, ref);
// Invalid icon?
if (typeof icon !== 'string') {
this._abortLoading();
this._setData(null);
return;
}
// TODO: icon is missing
// Load icon
const data = getIconData(icon);
if (data === null) {
// Icon needs to be loaded
if (!this._loading || this._loading.name !== icon) {
// New icon to load
this._abortLoading();
this._icon = '';
this._setData(null);
this._loading = {
name: icon,
abort: API.loadIcons(
[icon],
this._checkIcon.bind(this, false)
),
};
}
return;
}
// Icon data is available
if (this._icon !== icon || state.data === null) {
// New icon or icon has been loaded
this._abortLoading();
this._icon = icon;
this._setData(data);
}
}
// Error
/**
* Component mounted
*/
componentDidMount() {
this._checkIcon(false);
}
/**
* Component updated
*/
componentDidUpdate(oldProps) {
if (oldProps.icon !== this.props.icon) {
this._checkIcon(true);
}
}
/**
* Abort loading
*/
componentWillUnmount() {
this._abortLoading();
}
/**
* Render
*/
render() {
const props = this.props;
const data = this.state.data;
if (data === null) {
// Render placeholder
return props.children
? (props.children as JSX.Element)
: React.createElement('span', {});
}
// Render icon
return render(data, props, props._inline, props._ref);
}
}
/**
@ -336,7 +454,13 @@ export type Component = (props: IconProps) => JSX.Element;
* @param props - Component properties
*/
export const Icon: Component = React.forwardRef(
(props: IconProps, ref?: IconRef) => component(props, false, ref)
(props: IconProps, ref?: IconRef) => {
const newProps = merge(props as Partial<InternalIconProps>, {
_ref: ref,
_inline: false,
}) as InternalIconProps;
return React.createElement(IconComponent, newProps);
}
);
/**
@ -345,5 +469,11 @@ export const Icon: Component = React.forwardRef(
* @param props - Component properties
*/
export const InlineIcon: Component = React.forwardRef(
(props: IconProps, ref?: IconRef) => component(props, true, ref)
(props: IconProps, ref?: IconRef) => {
const newProps = merge(props as Partial<InternalIconProps>, {
_ref: ref,
_inline: true,
}) as InternalIconProps;
return React.createElement(IconComponent, newProps);
}
);

View File

@ -75,6 +75,9 @@ export const render = (
// Properties to ignore
case 'icon':
case 'style':
case 'children':
case '_ref':
case '_inline':
break;
// Flip as string: 'horizontal,vertical'

View File

@ -1,72 +0,0 @@
import React from 'react';
import { Icon, loadIcons, iconExists } from '../../lib/iconify';
import { mockAPIData } from '@iconify/core/lib/api/modules/mock';
import { provider, nextPrefix } from './load';
const iconData = {
body:
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24,
height: 24,
};
describe('Rendering icon', () => {
test('rendering icon after loading it', (done) => {
const prefix = nextPrefix();
const name = 'mock-test';
const iconName = `@${provider}:${prefix}:${name}`;
mockAPIData({
provider,
prefix,
response: {
prefix,
icons: {
[name]: iconData,
},
},
});
// Check if icon has been loaded
expect(iconExists(iconName)).toEqual(false);
// Load icon
loadIcons([iconName], (loaded, missing, pending) => {
// Make sure icon has been loaded
expect(loaded).toMatchObject([
{
provider,
prefix,
name,
},
]);
expect(missing).toMatchObject([]);
expect(pending).toMatchObject([]);
expect(iconExists(iconName)).toEqual(true);
// Render component
const component = renderer.create(<Icon icon={iconName} />);
const tree = component.toJSON();
expect(tree).toMatchObject({
type: 'svg',
props: {
'xmlns': 'http://www.w3.org/2000/svg',
'xmlnsXlink': 'http://www.w3.org/1999/xlink',
'aria-hidden': true,
'role': 'img',
'style': {},
'dangerouslySetInnerHTML': {
__html: iconData.body,
},
'width': '1em',
'height': '1em',
'preserveAspectRatio': 'xMidYMid meet',
'viewBox': '0 0 ' + iconData.width + ' ' + iconData.height,
},
children: null,
});
done();
});
});
});

View File

@ -0,0 +1,197 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { Icon, loadIcons, iconExists } from '../../lib/iconify';
import { mockAPIData } from '@iconify/core/lib/api/modules/mock';
import { provider, nextPrefix } from './load';
const iconData = {
body:
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24,
height: 24,
};
describe('Rendering icon', () => {
test('rendering icon after loading it', (done) => {
const prefix = nextPrefix();
const name = 'render-test';
const iconName = `@${provider}:${prefix}:${name}`;
mockAPIData({
provider,
prefix,
response: {
prefix,
icons: {
[name]: iconData,
},
},
});
// Check if icon has been loaded
expect(iconExists(iconName)).toEqual(false);
// Load icon
loadIcons([iconName], (loaded, missing, pending) => {
// Make sure icon has been loaded
expect(loaded).toMatchObject([
{
provider,
prefix,
name,
},
]);
expect(missing).toMatchObject([]);
expect(pending).toMatchObject([]);
expect(iconExists(iconName)).toEqual(true);
// Render component
const component = renderer.create(<Icon icon={iconName} />);
const tree = component.toJSON();
expect(tree).toMatchObject({
type: 'svg',
props: {
'xmlns': 'http://www.w3.org/2000/svg',
'xmlnsXlink': 'http://www.w3.org/1999/xlink',
'aria-hidden': true,
'role': 'img',
'style': {},
'dangerouslySetInnerHTML': {
__html: iconData.body,
},
'width': '1em',
'height': '1em',
'preserveAspectRatio': 'xMidYMid meet',
'viewBox': '0 0 ' + iconData.width + ' ' + iconData.height,
},
children: null,
});
done();
});
});
test('rendering icon before loading it', (done) => {
const prefix = nextPrefix();
const name = 'mock-test';
const iconName = `@${provider}:${prefix}:${name}`;
mockAPIData({
provider,
prefix,
response: {
prefix,
icons: {
[name]: iconData,
},
},
delay: (next) => {
// Icon should not have loaded yet
expect(iconExists(iconName)).toEqual(false);
// Send icon data
next();
// Test it again
expect(iconExists(iconName)).toEqual(true);
// Check if state was changed
// Wrapped in double setTimeout() because re-render takes 2 ticks
setTimeout(() => {
setTimeout(() => {
const tree = component.toJSON();
expect(tree).toMatchObject({
type: 'svg',
props: {
'xmlns': 'http://www.w3.org/2000/svg',
'xmlnsXlink': 'http://www.w3.org/1999/xlink',
'aria-hidden': true,
'role': 'img',
'style': {},
'dangerouslySetInnerHTML': {
__html: iconData.body,
},
'width': '1em',
'height': '1em',
'preserveAspectRatio': 'xMidYMid meet',
'viewBox':
'0 0 ' +
iconData.width +
' ' +
iconData.height,
},
children: null,
});
done();
}, 0);
}, 0);
},
});
// Check if icon has been loaded
expect(iconExists(iconName)).toEqual(false);
// Render component
const component = renderer.create(<Icon icon={iconName} />);
const tree = component.toJSON();
// Should render placeholder
expect(tree).toMatchObject({
type: 'span',
props: {},
children: null,
});
});
test('missing icon', (done) => {
const prefix = nextPrefix();
const name = 'missing-icon';
const iconName = `@${provider}:${prefix}:${name}`;
mockAPIData({
provider,
prefix,
response: 404,
delay: (next) => {
// Icon should not have loaded yet
expect(iconExists(iconName)).toEqual(false);
// Send icon data
next();
// Test it again
expect(iconExists(iconName)).toEqual(false);
// Check if state was changed
// Wrapped in double setTimeout() because re-render takes 2 ticks
setTimeout(() => {
setTimeout(() => {
const tree = component.toJSON();
expect(tree).toMatchObject({
type: 'span',
props: {},
children: null,
});
done();
}, 0);
}, 0);
},
});
// Check if icon has been loaded
expect(iconExists(iconName)).toEqual(false);
// Render component
const component = renderer.create(<Icon icon={iconName}></Icon>);
const tree = component.toJSON();
// Should render placeholder
expect(tree).toMatchObject({
type: 'span',
props: {},
children: null,
});
});
});

View File

@ -0,0 +1,256 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { Icon, iconExists } from '../../lib/iconify';
import { mockAPIData } from '@iconify/core/lib/api/modules/mock';
import { provider, nextPrefix } from './load';
const iconData = {
body:
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24,
height: 24,
};
const iconData2 = {
body:
'<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,
height: 32,
};
describe('Rendering icon', () => {
test('changing icon property', (done) => {
const prefix = nextPrefix();
const name = 'changing-prop';
const name2 = 'changing-prop2';
const iconName = `@${provider}:${prefix}:${name}`;
const iconName2 = `@${provider}:${prefix}:${name2}`;
mockAPIData({
provider,
prefix,
response: {
prefix,
icons: {
[name]: iconData,
},
},
delay: (next) => {
// Icon should not have loaded yet
expect(iconExists(iconName)).toEqual(false);
// Send icon data
next();
// Test it again
expect(iconExists(iconName)).toEqual(true);
// Check if state was changed
// Wrapped in double setTimeout() because re-render takes 2 ticks
setTimeout(() => {
setTimeout(() => {
const tree = component.toJSON();
expect(tree).toMatchObject({
type: 'svg',
props: {
'xmlns': 'http://www.w3.org/2000/svg',
'xmlnsXlink': 'http://www.w3.org/1999/xlink',
'aria-hidden': true,
'role': 'img',
'style': {},
'dangerouslySetInnerHTML': {
__html: iconData.body,
},
'width': '1em',
'height': '1em',
'preserveAspectRatio': 'xMidYMid meet',
'viewBox':
'0 0 ' +
iconData.width +
' ' +
iconData.height,
},
children: null,
});
component.update(<Icon icon={iconName2} />);
}, 0);
}, 0);
},
});
mockAPIData({
provider,
prefix,
response: {
prefix,
icons: {
[name2]: iconData2,
},
},
delay: (next) => {
// Icon should not have loaded yet
expect(iconExists(iconName2)).toEqual(false);
// Send icon data
next();
// Test it again
expect(iconExists(iconName2)).toEqual(true);
// Check if state was changed
// Wrapped in double setTimeout() because re-render takes 2 ticks
setTimeout(() => {
setTimeout(() => {
const tree = component.toJSON();
expect(tree).toMatchObject({
type: 'svg',
props: {
'xmlns': 'http://www.w3.org/2000/svg',
'xmlnsXlink': 'http://www.w3.org/1999/xlink',
'aria-hidden': true,
'role': 'img',
'style': {},
'dangerouslySetInnerHTML': {
__html: iconData2.body,
},
'width': '1em',
'height': '1em',
'preserveAspectRatio': 'xMidYMid meet',
'viewBox':
'0 0 ' +
iconData2.width +
' ' +
iconData2.height,
},
children: null,
});
done();
}, 0);
}, 0);
},
});
// Check if icon has been loaded
expect(iconExists(iconName)).toEqual(false);
// Render component
const component = renderer.create(<Icon icon={iconName} />);
const tree = component.toJSON();
// Should render placeholder
expect(tree).toMatchObject({
type: 'span',
props: {},
children: null,
});
});
test('changing multiple properties', (done) => {
const prefix = nextPrefix();
const name = 'multiple-props';
const iconName = `@${provider}:${prefix}:${name}`;
mockAPIData({
provider,
prefix,
response: {
prefix,
icons: {
[name]: iconData,
},
},
delay: (next) => {
// Icon should not have loaded yet
expect(iconExists(iconName)).toEqual(false);
// Send icon data
next();
// Test it again
expect(iconExists(iconName)).toEqual(true);
// Check if state was changed
// Wrapped in double setTimeout() because re-render takes 2 ticks
setTimeout(() => {
setTimeout(() => {
let tree = component.toJSON();
expect(tree).toMatchObject({
type: 'svg',
props: {
'xmlns': 'http://www.w3.org/2000/svg',
'xmlnsXlink': 'http://www.w3.org/1999/xlink',
'aria-hidden': true,
'role': 'img',
'style': {},
'dangerouslySetInnerHTML': {
__html: iconData.body,
},
'width': '1em',
'height': '1em',
'preserveAspectRatio': 'xMidYMid meet',
'viewBox':
'0 0 ' +
iconData.width +
' ' +
iconData.height,
},
children: null,
});
// Add horizontal flip and style
component.update(
<Icon
icon={iconName}
hFlip={true}
style={{ color: 'red' }}
/>
);
tree = component.toJSON();
expect(tree).toMatchObject({
type: 'svg',
props: {
'xmlns': 'http://www.w3.org/2000/svg',
'xmlnsXlink': 'http://www.w3.org/1999/xlink',
'aria-hidden': true,
'role': 'img',
'style': {
color: 'red',
},
'dangerouslySetInnerHTML': {
__html: `<g transform="translate(${iconData.width} 0) scale(-1 1)">${iconData.body}</g>`,
},
'width': '1em',
'height': '1em',
'preserveAspectRatio': 'xMidYMid meet',
'viewBox':
'0 0 ' +
iconData.width +
' ' +
iconData.height,
},
children: null,
});
done();
}, 0);
}, 0);
},
});
// Check if icon has been loaded
expect(iconExists(iconName)).toEqual(false);
// Render component with placeholder text
const component = renderer.create(
<Icon icon={iconName}>loading...</Icon>
);
const tree = component.toJSON();
// Should render placeholder
expect(tree).toEqual('loading...');
});
});

View File

@ -0,0 +1,188 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { Icon, InlineIcon, loadIcons, iconExists } from '../../lib/iconify';
import { mockAPIData } from '@iconify/core/lib/api/modules/mock';
import { provider, nextPrefix } from './load';
const iconData = {
body:
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24,
height: 24,
};
describe('Testing references', () => {
test('reference for preloaded icon', (done) => {
const prefix = nextPrefix();
const name = 'render-test';
const iconName = `@${provider}:${prefix}:${name}`;
mockAPIData({
provider,
prefix,
response: {
prefix,
icons: {
[name]: iconData,
},
},
});
// Check if icon has been loaded
expect(iconExists(iconName)).toEqual(false);
// Load icon
loadIcons([iconName], (loaded, missing, pending) => {
let gotRef = false;
let gotInlineRef = false;
// Make sure icon has been loaded
expect(loaded).toMatchObject([
{
provider,
prefix,
name,
},
]);
expect(missing).toMatchObject([]);
expect(pending).toMatchObject([]);
expect(iconExists(iconName)).toEqual(true);
// Render components
renderer.create(
<Icon
icon={iconName}
ref={(element) => {
gotRef = true;
}}
/>
);
renderer.create(
<InlineIcon
icon={iconName}
ref={(element) => {
gotInlineRef = true;
}}
/>
);
// References should be called immediately in test
expect(gotRef).toEqual(true);
expect(gotInlineRef).toEqual(true);
done();
});
});
test('reference to pending icon', (done) => {
const prefix = nextPrefix();
const name = 'mock-test';
const iconName = `@${provider}:${prefix}:${name}`;
let gotRef = false;
mockAPIData({
provider,
prefix,
response: {
prefix,
icons: {
[name]: iconData,
},
},
delay: (next) => {
// Icon should not have loaded yet
expect(iconExists(iconName)).toEqual(false);
// Reference should not have been called yet
expect(gotRef).toEqual(false);
// Send icon data
next();
// Test it again
expect(iconExists(iconName)).toEqual(true);
expect(gotRef).toEqual(false);
// Check if state was changed
// Wrapped in double setTimeout() because re-render takes 2 ticks
setTimeout(() => {
setTimeout(() => {
expect(gotRef).toEqual(true);
done();
}, 0);
}, 0);
},
});
// Check if icon has been loaded
expect(iconExists(iconName)).toEqual(false);
// Render component
renderer.create(
<Icon
icon={iconName}
ref={(element) => {
gotRef = true;
}}
/>
);
// Reference should not have been called yet
expect(gotRef).toEqual(false);
});
test('missing icon', (done) => {
const prefix = nextPrefix();
const name = 'missing-icon';
const iconName = `@${provider}:${prefix}:${name}`;
let gotRef = false;
mockAPIData({
provider,
prefix,
response: 404,
delay: (next) => {
// Icon should not have loaded yet
expect(iconExists(iconName)).toEqual(false);
// Reference should not have been called
expect(gotRef).toEqual(false);
// Send icon data
next();
// Test it again
expect(iconExists(iconName)).toEqual(false);
expect(gotRef).toEqual(false);
// Check if state was changed
// Wrapped in double setTimeout() because re-render takes 2 ticks
setTimeout(() => {
setTimeout(() => {
// Reference should not have been called
expect(gotRef).toEqual(false);
done();
}, 0);
}, 0);
},
});
// Check if icon has been loaded
expect(iconExists(iconName)).toEqual(false);
// Render component
const component = renderer.create(
<Icon
icon={iconName}
ref={(element) => {
gotRef = true;
}}
></Icon>
);
// Reference should not have been called
expect(gotRef).toEqual(false);
});
});

View File

@ -0,0 +1,56 @@
import React from 'react';
import { Icon, InlineIcon } from '../../lib/iconify';
import renderer from 'react-test-renderer';
const iconData = {
body:
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24,
height: 24,
};
describe('Testing references', () => {
test('basic icon reference', () => {
let gotRef = false;
const component = renderer.create(
<Icon
icon={iconData}
ref={(element) => {
gotRef = true;
}}
/>
);
// Ref should have been called by now
expect(gotRef).toEqual(true);
});
test('inline icon reference', () => {
let gotRef = false;
const component = renderer.create(
<InlineIcon
icon={iconData}
ref={(element) => {
gotRef = true;
}}
/>
);
// Ref should have been called by now
expect(gotRef).toEqual(true);
});
test('placeholder reference', () => {
let gotRef = false;
const component = renderer.create(
<Icon
ref={(element) => {
gotRef = true;
}}
/>
);
// Ref should not have been called
expect(gotRef).toEqual(false);
});
});

View File

@ -0,0 +1,56 @@
import React from 'react';
import { Icon, InlineIcon } from '../../lib/offline';
import renderer from 'react-test-renderer';
const iconData = {
body:
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24,
height: 24,
};
describe('Testing references', () => {
test('basic icon reference', () => {
let gotRef = false;
const component = renderer.create(
<Icon
icon={iconData}
ref={(element) => {
gotRef = true;
}}
/>
);
// Ref should have been called by now
expect(gotRef).toEqual(true);
});
test('inline icon reference', () => {
let gotRef = false;
const component = renderer.create(
<InlineIcon
icon={iconData}
ref={(element) => {
gotRef = true;
}}
/>
);
// Ref should have been called by now
expect(gotRef).toEqual(true);
});
test('placeholder reference', () => {
let gotRef = false;
const component = renderer.create(
<Icon
ref={(element) => {
gotRef = true;
}}
/>
);
// Ref should not have been called
expect(gotRef).toEqual(false);
});
});