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:
parent
e1fa93db6b
commit
b46498b8fd
@ -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,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
8
packages/react-demo/src/App.js
vendored
8
packages/react-demo/src/App.js
vendored
@ -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>
|
||||
);
|
||||
}
|
||||
|
17
packages/react-demo/src/demo-components/UsageFull.jsx
Normal file
17
packages/react-demo/src/demo-components/UsageFull.jsx
Normal 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>
|
||||
);
|
||||
}
|
243
packages/react-demo/src/test-components/TestsFull.jsx
Normal file
243
packages/react-demo/src/test-components/TestsFull.jsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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
|
||||
if (typeof icon === 'object' && typeof icon.body === 'string') {
|
||||
return render(fullIcon(icon), props, inline, ref);
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// TODO: icon is missing
|
||||
/**
|
||||
* Abort loading icon
|
||||
*/
|
||||
_abortLoading() {
|
||||
if (this._loading) {
|
||||
this._loading.abort();
|
||||
this._loading = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Error
|
||||
return props.children
|
||||
? (props.children as JSX.Element)
|
||||
: React.createElement('span', {});
|
||||
/**
|
||||
* 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') {
|
||||
// Stop loading
|
||||
this._icon = '';
|
||||
this._abortLoading();
|
||||
|
||||
if (changed || state.data === null) {
|
||||
// Set data if it was changed
|
||||
this._setData(fullIcon(icon));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalid icon?
|
||||
if (typeof icon !== 'string') {
|
||||
this._abortLoading();
|
||||
this._setData(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
);
|
||||
|
@ -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'
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
197
packages/react/tests/api/20-rendering-from-api.test.js
Normal file
197
packages/react/tests/api/20-rendering-from-api.test.js
Normal 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,
|
||||
});
|
||||
});
|
||||
});
|
256
packages/react/tests/api/30-changing-props.test.js
Normal file
256
packages/react/tests/api/30-changing-props.test.js
Normal 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...');
|
||||
});
|
||||
});
|
188
packages/react/tests/api/30-ref.test.js
Normal file
188
packages/react/tests/api/30-ref.test.js
Normal 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);
|
||||
});
|
||||
});
|
56
packages/react/tests/iconify/20-ref.test.js
Normal file
56
packages/react/tests/iconify/20-ref.test.js
Normal 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);
|
||||
});
|
||||
});
|
56
packages/react/tests/offline/20-ref.test.js
Normal file
56
packages/react/tests/offline/20-ref.test.js
Normal 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);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user