diff --git a/packages/react-demo/src/test-components/TestIcons.jsx b/packages/react-demo/src/test-components/TestIcons.jsx
new file mode 100644
index 0000000..42922df
--- /dev/null
+++ b/packages/react-demo/src/test-components/TestIcons.jsx
@@ -0,0 +1,78 @@
+import React from 'react';
+import { InlineIcon } from '@iconify/react/dist/offline';
+import successIcon from '@iconify-icons/uil/check-circle';
+import pendingIcon from '@iconify-icons/uil/question-circle';
+import failedIcon from '@iconify-icons/uil/times-circle';
+
+function getStatus(status) {
+ switch (status) {
+ case 'success':
+ case 'default-success':
+ case true:
+ return 'success';
+
+ case 'failed':
+ case 'fail':
+ case false:
+ return 'failed';
+
+ default:
+ return 'pending';
+ }
+}
+
+export function TestIcons(props) {
+ if (!props.id) {
+ return null;
+ }
+
+ const id = 'test-icons-' + props.id;
+ const icon = getStatus(props.status);
+
+ return (
+
+
+
+
+
+ );
+}
+
+export function toggleTest(id, status) {
+ const node = document.getElementById('test-icons-' + id);
+ if (!node) {
+ return;
+ }
+
+ // Get icon to show
+ const icon = getStatus(status);
+
+ // Remove previous status
+ const visible = node.querySelector('.visible');
+ if (visible) {
+ visible.classList.remove('visible');
+ visible.classList.add('hidden');
+ }
+
+ // Show new icon
+ const toggle = node.querySelector('.' + icon);
+ if (toggle) {
+ toggle.classList.remove('hidden');
+ toggle.classList.add('visible');
+ }
+}
diff --git a/packages/react-demo/src/test-components/TestsFullOffline.jsx b/packages/react-demo/src/test-components/TestsFullOffline.jsx
new file mode 100644
index 0000000..4019488
--- /dev/null
+++ b/packages/react-demo/src/test-components/TestsFullOffline.jsx
@@ -0,0 +1,212 @@
+import React from 'react';
+import { InlineIcon } from '@iconify/react/dist/iconify';
+import { TestIcons, toggleTest } from './TestIcons';
+
+export function TestsFullOffline() {
+ return (
+
+ Tests (full module, without API)
+
+ References
+
+
+
+ Getting reference
+ {
+ const key = 'full-offline-ref1';
+ if (element && element.tagName === 'svg') {
+ toggleTest(key, 'success');
+ } else {
+ toggleTest(key, 'failed');
+ }
+ }}
+ />
+
+
+
+
+ Getting reference for empty icon
+ {
+ // Cannot be called because there is no SVG to render!
+ toggleTest('full-offline-ref-missing', 'failed');
+ }}
+ />
+
+
+
+
+ Getting reference for missing icon with fallback text{' '}
+ {
+ // Cannot be called because there is no SVG to render!
+ toggleTest('full-offline-ref-missing2', 'failed');
+ }}
+ >
+ 😀
+
+
+
+ Style
+
+
+
+ Inline style for icon
+ {
+ const key = 'full-offline-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');
+ }
+ }}
+ />
+
+
+
+
+ Green color from attribute:{' '}
+ {
+ const key = 'full-offline-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');
+ }
+ }}
+ />
+
+
+
+
+ Green color from style:{' '}
+ {
+ const key = 'full-offline-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');
+ }
+ }}
+ />
+
+
+
+
+ Green color from attribute (overrides style) + red from style:{' '}
+ {
+ const key = 'full-offline-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');
+ }
+ }}
+ />
+
+
+ );
+}
diff --git a/packages/react-demo/src/test-components/TestsOffline.jsx b/packages/react-demo/src/test-components/TestsOffline.jsx
new file mode 100644
index 0000000..3e18a10
--- /dev/null
+++ b/packages/react-demo/src/test-components/TestsOffline.jsx
@@ -0,0 +1,212 @@
+import React from 'react';
+import { InlineIcon } from '@iconify/react/dist/offline';
+import { TestIcons, toggleTest } from './TestIcons';
+
+export function TestsOffline() {
+ return (
+
+ Tests (offline module)
+
+ References
+
+
+
+ Getting reference
+ {
+ const key = 'offline-ref1';
+ if (element && element.tagName === 'svg') {
+ toggleTest(key, 'success');
+ } else {
+ toggleTest(key, 'failed');
+ }
+ }}
+ />
+
+
+
+
+ Getting reference for empty icon
+ {
+ // Cannot be called because there is no SVG to render!
+ toggleTest('offline-ref-missing', 'failed');
+ }}
+ />
+
+
+
+
+ Getting reference for missing icon with fallback text{' '}
+ {
+ // Cannot be called because there is no SVG to render!
+ toggleTest('offline-ref-missing2', 'failed');
+ }}
+ >
+ 😀
+
+
+
+ Style
+
+
+
+ Inline style for icon
+ {
+ const key = 'offline-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');
+ }
+ }}
+ />
+
+
+
+
+ Green color from attribute:{' '}
+ {
+ const key = 'offline-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');
+ }
+ }}
+ />
+
+
+
+
+ Green color from style:{' '}
+ {
+ const key = 'offline-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');
+ }
+ }}
+ />
+
+
+
+
+ Green color from attribute (overrides style) + red from style:{' '}
+ {
+ const key = 'offline-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');
+ }
+ }}
+ />
+
+
+ );
+}
diff --git a/packages/react/package-lock.json b/packages/react/package-lock.json
index b6dec3b..f9a56c3 100644
--- a/packages/react/package-lock.json
+++ b/packages/react/package-lock.json
@@ -12,6 +12,7 @@
"@babel/preset-env": "^7.13.15",
"@babel/preset-react": "^7.13.13",
"@iconify/core": "^1.0.0-rc.4",
+ "@iconify/types": "^1.0.6",
"@microsoft/api-extractor": "^7.13.5",
"@rollup/plugin-buble": "^0.21.3",
"@rollup/plugin-commonjs": "^18.0.0",
diff --git a/packages/react/package.json b/packages/react/package.json
index 4f78c2d..6c31d6b 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -27,6 +27,7 @@
"@babel/preset-env": "^7.13.15",
"@babel/preset-react": "^7.13.13",
"@iconify/core": "^1.0.0-rc.4",
+ "@iconify/types": "^1.0.6",
"@microsoft/api-extractor": "^7.13.5",
"@rollup/plugin-buble": "^0.21.3",
"@rollup/plugin-commonjs": "^18.0.0",
diff --git a/packages/react/src/iconify.ts b/packages/react/src/iconify.ts
index 6f17685..06e4c6a 100644
--- a/packages/react/src/iconify.ts
+++ b/packages/react/src/iconify.ts
@@ -1,40 +1,293 @@
import React from 'react';
-import type { IconifyIcon, IconifyJSON } from '@iconify/types';
+import type { IconifyJSON } from '@iconify/types';
+
+// Core
+import { stringToIcon } from '@iconify/core/lib/icon/name';
+import type { IconifyIconName } from '@iconify/core/lib/icon/name';
import type {
+ IconifyIconSize,
IconifyHorizontalIconAlignment,
IconifyVerticalIconAlignment,
- IconifyIconSize,
} from '@iconify/core/lib/customisations';
-import { fullIcon } from '@iconify/core/lib/icon';
-import { parseIconSet } from '@iconify/core/lib/icon/icon-set';
+import {
+ IconifyStorageFunctions,
+ storageFunctions,
+ getIconData,
+ allowSimpleNames,
+} from '@iconify/core/lib/storage/functions';
+import {
+ IconifyBuilderFunctions,
+ builderFunctions,
+} from '@iconify/core/lib/builder/functions';
+import { fullIcon, IconifyIcon } from '@iconify/core/lib/icon';
+
+// Modules
+import { coreModules } from '@iconify/core/lib/modules';
+
+// API
+import { API, IconifyAPIInternalStorage } from '@iconify/core/lib/api/';
+import {
+ IconifyAPIFunctions,
+ IconifyAPIInternalFunctions,
+ APIFunctions,
+ APIInternalFunctions,
+} from '@iconify/core/lib/api/functions';
+import {
+ setAPIModule,
+ IconifyAPIModule,
+ IconifyAPISendQuery,
+ IconifyAPIPrepareQuery,
+ GetIconifyAPIModule,
+} from '@iconify/core/lib/api/modules';
+import { getAPIModule as getJSONPAPIModule } from '@iconify/core/lib/api/modules/jsonp';
+import {
+ getAPIModule as getFetchAPIModule,
+ setFetch,
+} from '@iconify/core/lib/api/modules/fetch';
+import {
+ setAPIConfig,
+ PartialIconifyAPIConfig,
+ IconifyAPIConfig,
+ getAPIConfig,
+ GetAPIConfig,
+} from '@iconify/core/lib/api/config';
+import type {
+ IconifyIconLoaderCallback,
+ IconifyIconLoaderAbort,
+} from '@iconify/core/lib/interfaces/loader';
+
+// Cache
+import { storeCache, loadCache } from '@iconify/core/lib/browser-storage';
+import { toggleBrowserCache } from '@iconify/core/lib/browser-storage/functions';
+import type {
+ IconifyBrowserCacheType,
+ IconifyBrowserCacheFunctions,
+} from '@iconify/core/lib/browser-storage/functions';
+
+// Properties
import type {
IconifyIconCustomisations,
IconifyIconProps,
IconProps,
IconRef,
} from './props';
+
+// Render SVG
import { render } from './render';
/**
- * Export stuff from props.ts
- */
-export { IconifyIconCustomisations, IconifyIconProps, IconProps };
-
-/**
- * Export types that could be used in component
+ * Export required types
*/
+// Function sets
export {
- IconifyIcon,
- IconifyJSON,
- IconifyHorizontalIconAlignment,
- IconifyVerticalIconAlignment,
- IconifyIconSize,
+ IconifyStorageFunctions,
+ IconifyBuilderFunctions,
+ IconifyBrowserCacheFunctions,
+ IconifyAPIFunctions,
+ IconifyAPIInternalFunctions,
};
+// JSON stuff
+export { IconifyIcon, IconifyJSON, IconifyIconName };
+
+// Customisations
+export {
+ IconifyIconCustomisations,
+ IconifyIconSize,
+ IconifyHorizontalIconAlignment,
+ IconifyVerticalIconAlignment,
+ IconifyIconProps,
+ IconProps,
+};
+
+// API
+export {
+ IconifyAPIConfig,
+ IconifyIconLoaderCallback,
+ IconifyIconLoaderAbort,
+ IconifyAPIInternalStorage,
+ IconifyAPIModule,
+ GetAPIConfig,
+ IconifyAPIPrepareQuery,
+ IconifyAPISendQuery,
+};
+
+/* Browser cache */
+export { IconifyBrowserCacheType };
+
/**
- * Storage for icons referred by name
+ * Enable and disable browser cache
+ */
+export const enableCache = (storage: IconifyBrowserCacheType) =>
+ toggleBrowserCache(storage, true);
+
+export const disableCache = (storage: IconifyBrowserCacheType) =>
+ toggleBrowserCache(storage, false);
+
+/* Storage functions */
+/**
+ * Check if icon exists
+ */
+export const iconExists = storageFunctions.iconExists;
+
+/**
+ * Get icon data
+ */
+export const getIcon = storageFunctions.getIcon;
+
+/**
+ * List available icons
+ */
+export const listIcons = storageFunctions.listIcons;
+
+/**
+ * Add one icon
+ */
+export const addIcon = storageFunctions.addIcon;
+
+/**
+ * Add icon set
+ */
+export const addCollection = storageFunctions.addCollection;
+
+/* Builder functions */
+/**
+ * Calculate icon size
+ */
+export const calculateSize = builderFunctions.calculateSize;
+
+/**
+ * Replace unique ids in content
+ */
+export const replaceIDs = builderFunctions.replaceIDs;
+
+/* API functions */
+/**
+ * Load icons
+ */
+export const loadIcons = APIFunctions.loadIcons;
+
+/**
+ * Add API provider
+ */
+export const addAPIProvider = APIFunctions.addAPIProvider;
+
+/**
+ * Export internal functions that can be used by third party implementations
+ */
+export const _api = APIInternalFunctions;
+
+/**
+ * Initialise stuff
+ */
+// Enable short names
+allowSimpleNames(true);
+
+// Set API
+coreModules.api = API;
+
+let getAPIModule: GetIconifyAPIModule;
+try {
+ getAPIModule =
+ typeof fetch === 'function' && typeof Promise === 'function'
+ ? getFetchAPIModule
+ : getJSONPAPIModule;
+} catch (err) {
+ getAPIModule = getJSONPAPIModule;
+}
+setAPIModule('', getAPIModule(getAPIConfig));
+
+/**
+ * Enable node-fetch for getting icons on server side
+ */
+export function setNodeFetch(nodeFetch: typeof fetch) {
+ setFetch(nodeFetch);
+ if (getAPIModule !== getFetchAPIModule) {
+ getAPIModule = getFetchAPIModule;
+ setAPIModule('', getAPIModule(getAPIConfig));
+ }
+}
+
+/**
+ * Browser stuff
+ */
+if (typeof document !== 'undefined' && typeof window !== 'undefined') {
+ // Set cache and load existing cache
+ coreModules.cache = storeCache;
+ loadCache();
+
+ const _window = window;
+
+ // Load icons from global "IconifyPreload"
+ interface WindowWithIconifyPreload {
+ IconifyPreload: IconifyJSON[] | IconifyJSON;
+ }
+ if (
+ ((_window as unknown) as WindowWithIconifyPreload).IconifyPreload !==
+ void 0
+ ) {
+ const preload = ((_window as unknown) as WindowWithIconifyPreload)
+ .IconifyPreload;
+ const err = 'Invalid IconifyPreload syntax.';
+ if (typeof preload === 'object' && preload !== null) {
+ (preload instanceof Array ? preload : [preload]).forEach((item) => {
+ try {
+ if (
+ // Check if item is an object and not null/array
+ typeof item !== 'object' ||
+ item === null ||
+ item instanceof Array ||
+ // Check for 'icons' and 'prefix'
+ typeof item.icons !== 'object' ||
+ typeof item.prefix !== 'string' ||
+ // Add icon set
+ !addCollection(item)
+ ) {
+ console.error(err);
+ }
+ } catch (e) {
+ console.error(err);
+ }
+ });
+ }
+ }
+
+ // Set API from global "IconifyProviders"
+ interface WindowWithIconifyProviders {
+ IconifyProviders: Record
;
+ }
+ if (
+ ((_window as unknown) as WindowWithIconifyProviders)
+ .IconifyProviders !== void 0
+ ) {
+ const providers = ((_window as unknown) as WindowWithIconifyProviders)
+ .IconifyProviders;
+ if (typeof providers === 'object' && providers !== null) {
+ for (let key in providers) {
+ const err = 'IconifyProviders[' + key + '] is invalid.';
+ try {
+ const value = providers[key];
+ if (
+ typeof value !== 'object' ||
+ !value ||
+ value.resources === void 0
+ ) {
+ continue;
+ }
+ if (!setAPIConfig(key, value)) {
+ console.error(err);
+ }
+ } catch (e) {
+ console.error(err);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Component
*/
-const storage: Record> = Object.create(null);
/**
* Generate icon
@@ -44,27 +297,32 @@ function component(
inline: boolean,
ref?: IconRef
): JSX.Element {
- // Split properties
- const icon =
- typeof props.icon === 'string'
- ? storage[props.icon]
- : typeof props.icon === 'object'
- ? fullIcon(props.icon)
- : null;
+ const icon = props.icon;
- // Validate icon object
- if (
- icon === null ||
- typeof icon !== 'object' ||
- typeof icon.body !== 'string'
- ) {
- return props.children
- ? (props.children as JSX.Element)
- : React.createElement('span', {});
+ // Check if icon is an object
+ if (typeof icon === 'object' && typeof icon.body === 'string') {
+ return render(fullIcon(icon), props, inline, ref);
}
- // Valid icon: render it
- return render(icon, props, inline, ref);
+ // 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
+ }
+ }
+
+ // Error
+ return props.children
+ ? (props.children as JSX.Element)
+ : React.createElement('span', {});
}
/**
@@ -89,36 +347,3 @@ export const Icon: Component = React.forwardRef(
export const InlineIcon: Component = React.forwardRef(
(props: IconProps, ref?: IconRef) => component(props, true, ref)
);
-
-/**
- * Add icon to storage, allowing to call it by name
- *
- * @param name
- * @param data
- */
-export function addIcon(name: string, data: IconifyIcon): void {
- storage[name] = fullIcon(data);
-}
-
-/**
- * Add collection to storage, allowing to call icons by name
- *
- * @param data Icon set
- * @param prefix Optional prefix to add to icon names, true if prefix from icon set should be used.
- */
-export function addCollection(
- data: IconifyJSON,
- prefix?: string | boolean
-): void {
- const iconPrefix: string =
- typeof prefix === 'string'
- ? prefix
- : prefix !== false && typeof data.prefix === 'string'
- ? data.prefix + ':'
- : '';
- parseIconSet(data, (name, icon) => {
- if (icon !== null) {
- storage[iconPrefix + name] = icon;
- }
- });
-}
diff --git a/packages/react/tests/iconify/10-basic.test.js b/packages/react/tests/iconify/10-basic.test.js
new file mode 100644
index 0000000..beaec32
--- /dev/null
+++ b/packages/react/tests/iconify/10-basic.test.js
@@ -0,0 +1,62 @@
+import React from 'react';
+import { Icon, InlineIcon } from '../../dist/iconify';
+import renderer from 'react-test-renderer';
+
+const iconData = {
+ body:
+ '',
+ width: 24,
+ height: 24,
+};
+
+describe('Creating component using object', () => {
+ test('basic icon', () => {
+ const component = renderer.create();
+ 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,
+ });
+ });
+
+ test('inline icon', () => {
+ const component = renderer.create();
+ 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': {
+ verticalAlign: '-0.125em',
+ },
+ 'dangerouslySetInnerHTML': {
+ __html: iconData.body,
+ },
+ 'width': '1em',
+ 'height': '1em',
+ 'preserveAspectRatio': 'xMidYMid meet',
+ 'viewBox': '0 0 ' + iconData.width + ' ' + iconData.height,
+ },
+ children: null,
+ });
+ });
+});
diff --git a/packages/react/tests/iconify/10-empty.test.js b/packages/react/tests/iconify/10-empty.test.js
new file mode 100644
index 0000000..6216f70
--- /dev/null
+++ b/packages/react/tests/iconify/10-empty.test.js
@@ -0,0 +1,57 @@
+import React from 'react';
+import { Icon } from '../../dist/iconify';
+import renderer from 'react-test-renderer';
+
+describe('Empty icon', () => {
+ test('basic test', () => {
+ const component = renderer.create();
+ const tree = component.toJSON();
+
+ expect(tree).toMatchObject({
+ type: 'span',
+ props: {},
+ children: null,
+ });
+ });
+
+ test('with child node', () => {
+ const component = renderer.create(
+
+
+
+ );
+ const tree = component.toJSON();
+
+ expect(tree).toMatchObject({
+ type: 'i',
+ props: {},
+ children: null,
+ });
+ });
+
+ test('with text child node', () => {
+ const component = renderer.create(icon);
+ const tree = component.toJSON();
+
+ expect(tree).toMatch('icon');
+ });
+
+ test('with multiple childen', () => {
+ const component = renderer.create(
+
+
+ Home
+
+ );
+ const tree = component.toJSON();
+
+ expect(tree).toMatchObject([
+ {
+ type: 'i',
+ props: {},
+ children: null,
+ },
+ 'Home',
+ ]);
+ });
+});
diff --git a/packages/react/tests/iconify/20-attributes.test.js b/packages/react/tests/iconify/20-attributes.test.js
new file mode 100644
index 0000000..f8fec54
--- /dev/null
+++ b/packages/react/tests/iconify/20-attributes.test.js
@@ -0,0 +1,88 @@
+import React from 'react';
+import { Icon, InlineIcon } from '../../dist/iconify';
+import renderer from 'react-test-renderer';
+
+const iconData = {
+ body:
+ '',
+ width: 24,
+ height: 24,
+};
+
+describe('Passing attributes', () => {
+ test('title', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+
+ expect(tree.props.title).toStrictEqual('Icon!');
+ });
+
+ test('aria-hidden', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+
+ expect(tree.props['aria-hidden']).toStrictEqual(void 0);
+ });
+
+ test('ariaHidden', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+
+ expect(tree.props['aria-hidden']).toStrictEqual(void 0);
+ });
+
+ test('style', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+
+ expect(tree.props.style).toMatchObject({
+ verticalAlign: '0',
+ color: 'red',
+ });
+ });
+
+ test('color', () => {
+ const component = renderer.create();
+ const tree = component.toJSON();
+
+ expect(tree.props.style).toMatchObject({
+ color: 'red',
+ });
+ });
+
+ test('color with style', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+
+ expect(tree.props.style).toMatchObject({
+ color: 'red',
+ });
+ });
+
+ test('attributes that cannot change', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+
+ expect(tree.props.viewBox).toStrictEqual('0 0 24 24');
+ expect(tree.props.preserveAspectRatio).toStrictEqual('xMidYMid meet');
+ });
+});
diff --git a/packages/react/tests/iconify/20-dimensions.test.js b/packages/react/tests/iconify/20-dimensions.test.js
new file mode 100644
index 0000000..887ddf9
--- /dev/null
+++ b/packages/react/tests/iconify/20-dimensions.test.js
@@ -0,0 +1,42 @@
+import React from 'react';
+import { InlineIcon } from '../../dist/iconify';
+import renderer from 'react-test-renderer';
+
+const iconData = {
+ body:
+ '',
+ width: 24,
+ height: 24,
+};
+
+describe('Dimensions', () => {
+ test('height', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+
+ expect(tree.props.height).toStrictEqual('48');
+ expect(tree.props.width).toStrictEqual('48');
+ });
+
+ test('width and height', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+
+ expect(tree.props.height).toStrictEqual('48');
+ expect(tree.props.width).toStrictEqual('32');
+ });
+
+ test('auto', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+
+ expect(tree.props.height).toStrictEqual('24');
+ expect(tree.props.width).toStrictEqual('24');
+ });
+});
diff --git a/packages/react/tests/iconify/20-ids.test.js b/packages/react/tests/iconify/20-ids.test.js
new file mode 100644
index 0000000..22100a0
--- /dev/null
+++ b/packages/react/tests/iconify/20-ids.test.js
@@ -0,0 +1,57 @@
+import React from 'react';
+import { Icon } from '../../dist/iconify';
+import renderer from 'react-test-renderer';
+
+const iconDataWithID = {
+ body:
+ '',
+ width: 128,
+ height: 128,
+};
+
+describe('Replacing IDs', () => {
+ test('default behavior', () => {
+ const component = renderer.create();
+ const tree = component.toJSON();
+ const body = tree.props.dangerouslySetInnerHTML.__html;
+
+ expect(body).not.toStrictEqual(iconDataWithID.body);
+ });
+
+ test('custom generator', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+ const body = tree.props.dangerouslySetInnerHTML.__html;
+
+ // Generate expected body
+ let expected = iconDataWithID.body;
+ const replacements = {
+ 'ssvg-id-1st-place-medala': 'test-0',
+ 'ssvg-id-1st-place-medald': 'test-1',
+ 'ssvg-id-1st-place-medalf': 'test-2',
+ 'ssvg-id-1st-place-medalh': 'test-3',
+ 'ssvg-id-1st-place-medalj': 'test-4',
+ 'ssvg-id-1st-place-medalm': 'test-5',
+ 'ssvg-id-1st-place-medalp': 'test-6',
+ 'ssvg-id-1st-place-medalb': 'test-7',
+ 'ssvg-id-1st-place-medalk': 'test-8',
+ 'ssvg-id-1st-place-medalo': 'test-9',
+ 'ssvg-id-1st-place-medalc': 'test-10',
+ 'ssvg-id-1st-place-medale': 'test-11',
+ 'ssvg-id-1st-place-medalg': 'test-12',
+ 'ssvg-id-1st-place-medali': 'test-13',
+ 'ssvg-id-1st-place-medall': 'test-14',
+ 'ssvg-id-1st-place-medaln': 'test-15',
+ };
+ Object.keys(replacements).forEach((search) => {
+ expected = expected.replace(
+ new RegExp(search, 'g'),
+ replacements[search]
+ );
+ });
+
+ expect(body).toStrictEqual(expected);
+ });
+});
diff --git a/packages/react/tests/iconify/20-inline.test.js b/packages/react/tests/iconify/20-inline.test.js
new file mode 100644
index 0000000..9d62fd9
--- /dev/null
+++ b/packages/react/tests/iconify/20-inline.test.js
@@ -0,0 +1,31 @@
+import React from 'react';
+import { Icon } from '../../dist/iconify';
+import renderer from 'react-test-renderer';
+
+const iconData = {
+ body:
+ '',
+ width: 24,
+ height: 24,
+};
+
+describe('Inline attribute', () => {
+ test('string', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+
+ expect(tree.props.style.verticalAlign).toStrictEqual('-0.125em');
+ });
+
+ test('false string', () => {
+ // "false" = true
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+
+ expect(tree.props.style.verticalAlign).toStrictEqual('-0.125em');
+ });
+});
diff --git a/packages/react/tests/iconify/20-transformations.test.js b/packages/react/tests/iconify/20-transformations.test.js
new file mode 100644
index 0000000..020d924
--- /dev/null
+++ b/packages/react/tests/iconify/20-transformations.test.js
@@ -0,0 +1,135 @@
+import React from 'react';
+import { InlineIcon } from '../../dist/iconify';
+import renderer from 'react-test-renderer';
+
+const iconData = {
+ body:
+ '',
+ width: 24,
+ height: 24,
+};
+
+describe('Rotation', () => {
+ test('number', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+ const body = tree.props.dangerouslySetInnerHTML.__html;
+
+ expect(body).not.toStrictEqual(iconData.body);
+ expect(body).toMatch('rotate(90 ');
+ });
+
+ test('string', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+ const body = tree.props.dangerouslySetInnerHTML.__html;
+
+ expect(body).not.toStrictEqual(iconData.body);
+ expect(body).toMatch('rotate(180 ');
+ });
+});
+
+describe('Flip', () => {
+ test('boolean', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+ const body = tree.props.dangerouslySetInnerHTML.__html;
+
+ expect(body).not.toStrictEqual(iconData.body);
+ expect(body).toMatch('scale(-1 1)');
+ });
+
+ test('string', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+ const body = tree.props.dangerouslySetInnerHTML.__html;
+
+ expect(body).not.toStrictEqual(iconData.body);
+ expect(body).toMatch('scale(1 -1)');
+ });
+
+ test('string and boolean', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+ const body = tree.props.dangerouslySetInnerHTML.__html;
+
+ expect(body).not.toStrictEqual(iconData.body);
+ // horizontal + vertical = 180deg rotation
+ expect(body).toMatch('rotate(180 ');
+ });
+
+ test('string for boolean attribute', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+ const body = tree.props.dangerouslySetInnerHTML.__html;
+
+ expect(body).not.toStrictEqual(iconData.body);
+ expect(body).toMatch('scale(-1 1)');
+ });
+
+ test('shorthand and boolean', () => {
+ // 'flip' is processed after 'hFlip', overwriting value
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+ const body = tree.props.dangerouslySetInnerHTML.__html;
+
+ expect(body).not.toStrictEqual(iconData.body);
+ expect(body).toMatch('scale(-1 1)');
+ });
+
+ test('shorthand and boolean as string', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+ const body = tree.props.dangerouslySetInnerHTML.__html;
+
+ expect(body).not.toStrictEqual(iconData.body);
+ // horizontal + vertical = 180deg rotation
+ expect(body).toMatch('rotate(180 ');
+ });
+
+ test('wrong case', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+ const body = tree.props.dangerouslySetInnerHTML.__html;
+
+ expect(body).not.toMatch('scale(');
+ });
+});
+
+describe('Alignment and slice', () => {
+ test('vAlign and slice', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+
+ expect(tree.props.preserveAspectRatio).toStrictEqual('xMidYMin slice');
+ });
+
+ test('string', () => {
+ const component = renderer.create(
+
+ );
+ const tree = component.toJSON();
+
+ expect(tree.props.preserveAspectRatio).toStrictEqual('xMinYMax meet');
+ });
+});
diff --git a/packages/react/tests/offline/10-empty.test.js b/packages/react/tests/offline/10-empty.test.js
index a683dcc..5dd2b83 100644
--- a/packages/react/tests/offline/10-empty.test.js
+++ b/packages/react/tests/offline/10-empty.test.js
@@ -1,14 +1,7 @@
import React from 'react';
-import { Icon, InlineIcon } from '../../dist/offline';
+import { Icon } from '../../dist/offline';
import renderer from 'react-test-renderer';
-const iconData = {
- body:
- '',
- width: 24,
- height: 24,
-};
-
describe('Empty icon', () => {
test('basic test', () => {
const component = renderer.create();
diff --git a/packages/react/tests/offline/20-inline.test.js b/packages/react/tests/offline/20-inline.test.js
index add910a..a476fa3 100644
--- a/packages/react/tests/offline/20-inline.test.js
+++ b/packages/react/tests/offline/20-inline.test.js
@@ -1,5 +1,5 @@
import React from 'react';
-import { Icon, InlineIcon } from '../../dist/offline';
+import { Icon } from '../../dist/offline';
import renderer from 'react-test-renderer';
const iconData = {
diff --git a/packages/react/tests/offline/20-transformations.test.js b/packages/react/tests/offline/20-transformations.test.js
index 40e4bda..f55d85f 100644
--- a/packages/react/tests/offline/20-transformations.test.js
+++ b/packages/react/tests/offline/20-transformations.test.js
@@ -1,5 +1,5 @@
import React from 'react';
-import { Icon, InlineIcon } from '../../dist/offline';
+import { InlineIcon } from '../../dist/offline';
import renderer from 'react-test-renderer';
const iconData = {