2
0
mirror of https://github.com/iconify/iconify.git synced 2024-06-18 14:42:21 +00:00

Move version 2 to a big monorepo

This commit is contained in:
Vjacheslav Trushkin 2020-04-28 12:47:35 +03:00
commit 58d4cf3d49
168 changed files with 70290 additions and 0 deletions

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.DS_Store
node_modules

8
.prettierrc Normal file
View File

@ -0,0 +1,8 @@
{
"trailingComma": "es5",
"singleQuote": true,
"useTabs": true,
"semi": true,
"quoteProps": "consistent",
"endOfLine": "lf"
}

10
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
"spellright.language": [
"en"
],
"spellright.documentTypes": [
"markdown",
"latex",
"plaintext"
]
}

69
README.md Normal file
View File

@ -0,0 +1,69 @@
### What is Iconify?
Iconify is the most versatile icon framework.
- Unified icon framework that can be used with any icon library.
- Out of the box includes 60+ icon sets with 50,000 icons.
- Embed icons in HTML with SVG framework or components for front-end frameworks.
- Embed icons in designs with plug-ins for Figma, Sketch and Adobe XD.
- Add icon search to your applications with Iconify Icon Finder.
For more information visit [https://iconify.design/](https://iconify.design/).
## This repository
This repository is a big monorepo that contains several implementations of Iconify icon framework.
There are two types of Iconify implementations:
- Implementations that rely on icon packages.
- Implementations that rely on Iconify API.
### Implementations: without API
These Iconify implementations require the developer to provide icon data and expect that icon data to be included in the bundle.
Examples: Iconify for React, Iconify for Vue.
They are easy to use and do not require an internet connection to work, similar to other React/Vue components.
### Implementations: with API
Iconify is designed to be easy to use. One of the main features is the Iconify API.
Iconify API provides data for over 50,000 icons! API is hosted on publicly available servers, spread out geographically to make sure visitors from all over the world have the fastest possible connection with redundancies in place to make sure it is always online.
#### Why is API needed?
When you use an icon font, each visitor loads an entire font, even if your page only uses a few icons. This is a major downside of using icon fonts. That limits developers to one or two fonts or icon sets.
Unlike icon fonts, Iconify implementations that use API do not load the entire icon set. Unlike fonts and SVG frameworks, Iconify only loads icons that are used on the current page instead of loading entire icon sets. Iconify API provides icon data to Iconify SVG framework and other implementations that rely on Iconify API.
## Available packages
There are several Iconify implementations included in this repository:
| Implementation | Usage | with API | without API |
| ------------------------------------ | ----- | :------: | :---------: |
| [SVG Framework](./packages/iconify/) | HTML | + | + |
| [React component](./packages/react/) | React | - | + |
| [Vue component](./packages/vue/) | Vue | - | + |
Other packages:
- [Iconify types](./packages/types/) - TypeScript types used by various implementations.
- [Iconify core](./packages/core/) - common files used by various implementations.
- [React demo](./packages/react-demo/) - demo for React component. Run `npm start` to start demo.
- [Vue demo](./packages/vue-demo/) - demo for Vue component. Run `npm serve` to start demo.
- [Browser tests](./packages/browser-tests/) - unit tests for SVG framework. Must be ran in browser.
## License
Iconify is dual-licensed under Apache 2.0 and GPL 2.0 license. You may select, at your option, one of the above-listed licenses.
`SPDX-License-Identifier: Apache-2.0 OR GPL-2.0`
This license does not apply to icons. Icons are released under different licenses, see each icon set for details.
Icons available by default are all licensed under some kind of open-source or free license.
© 2020 Vjacheslav Trushkin

7
lerna.json Normal file
View File

@ -0,0 +1,7 @@
{
"version": "independent",
"npmClient": "npm",
"packages": [
"packages/*"
]
}

6590
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

25
package.json Normal file
View File

@ -0,0 +1,25 @@
{
"name": "iconify",
"private": true,
"description": "The most versatile icon framework",
"author": "Vjacheslav Trushkin <cyberalien@gmail.com> (https://iconify.design)",
"license": "(Apache-2.0 OR GPL-2.0)",
"workspaces": [
"packages/*"
],
"bugs": "https://github.com/iconify/iconify/issues",
"homepage": "https://iconify.design/",
"repository": {
"type": "git",
"url": "git://github.com/iconify/iconify.git"
},
"scripts": {
"bootstrap": "lerna bootstrap --force-local",
"clean": "lerna clean",
"link": "lerna link --force-local",
"setup": "npm run clean && npm run bootstrap"
},
"devDependencies": {
"lerna": "^3.20.2"
}
}

0
packages/.gitignore vendored Normal file
View File

4
packages/browser-tests/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.DS_Store
node_modules
lib
dist

View File

@ -0,0 +1,97 @@
const path = require('path');
const child_process = require('child_process');
// List of commands to run
const commands = [];
// Parse command line
const compile = {
core: false,
iconify: false,
lib: true,
dist: true,
};
process.argv.slice(2).forEach(cmd => {
if (cmd.slice(0, 2) !== '--') {
return;
}
const parts = cmd.slice(2).split('-');
if (parts.length === 2) {
// Parse 2 part commands like --with-lib
const key = parts.pop();
if (compile[key] === void 0) {
return;
}
switch (parts.shift()) {
case 'with':
// enable module
compile[key] = true;
break;
case 'without':
// disable module
compile[key] = false;
break;
case 'only':
// disable other modules
Object.keys(compile).forEach(key2 => {
compile[key2] = key2 === key;
});
break;
}
}
});
// Compile core before compiling this package
if (compile.core) {
commands.push({
cmd: 'npm',
args: ['run', 'build'],
cwd: path.dirname(__dirname) + '/core',
});
}
if (compile.iconify || compile.core) {
commands.push({
cmd: 'npm',
args: ['run', 'build'],
cwd: path.dirname(__dirname) + '/iconify',
});
}
// Compile other packages
Object.keys(compile).forEach(key => {
if (key !== 'core' && key !== 'iconify' && compile[key]) {
commands.push({
cmd: 'npm',
args: ['run', 'build:' + key],
});
}
});
/**
* Run next command
*/
const next = () => {
const item = commands.shift();
if (item === void 0) {
process.exit(0);
}
if (item.cwd === void 0) {
item.cwd = __dirname;
}
const result = child_process.spawnSync(item.cmd, item.args, {
cwd: item.cwd,
stdio: 'inherit',
});
if (result.status === 0) {
process.nextTick(next);
} else {
process.exit(result.status);
}
};
next();

1139
packages/browser-tests/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
{
"name": "@iconify/iconify-browser-tests",
"private": true,
"description": "Browser tests for @iconify/iconify package",
"author": "Vjacheslav Trushkin <cyberalien@gmail.com> (https://iconify.design)",
"version": "2.0.0-dev",
"license": "(Apache-2.0 OR GPL-2.0)",
"bugs": "https://github.com/iconify/iconify/issues",
"homepage": "https://iconify.design/",
"repository": {
"type": "git",
"url": "git://github.com/iconify/iconify.git"
},
"scripts": {
"build": "node build",
"build:lib": "tsc -b",
"build:dist": "rollup -c rollup.config.js"
},
"devDependencies": {
"@cyberalien/redundancy": "^1.0.0",
"@iconify/core": "^1.0.0-beta.0",
"@iconify/iconify": "^2.0.0-beta.0",
"@iconify/types": "^1.0.1",
"@rollup/plugin-buble": "^0.21.1",
"@rollup/plugin-commonjs": "^11.0.2",
"@rollup/plugin-node-resolve": "^7.1.1",
"@types/chai": "^4.2.8",
"@types/mocha": "^5.2.7",
"chai": "^4.2.0",
"mocha": "^6.2.2",
"rollup": "^1.32.0",
"typescript": "^3.8.3"
},
"dependencies": {}
}

View File

@ -0,0 +1,54 @@
import fs from 'fs';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import buble from '@rollup/plugin-buble';
const match = '-test.ts';
const files = fs
.readdirSync('tests')
.sort()
.filter(file => file.slice(0 - match.length) === match)
.map(file => file.slice(0, file.length - match.length));
// Get config files
const tests = [];
const config = files.map(file => {
tests.push(file + '.js');
return {
input: 'lib/' + file + match.replace('.ts', '.js'),
output: {
file: 'dist/' + file + '.js',
format: 'iife',
globals: {
mocha: 'mocha',
chai: 'chai',
},
},
external: ['mocha', 'chai'],
plugins: [
resolve({
browser: true,
extensions: ['.js'],
}),
commonjs(),
buble(),
],
};
});
// Write tests.html
let content = fs.readFileSync(__dirname + '/tests/tests.html', 'utf8');
content = content.replace(
'<!-- tests -->',
tests
.map(file => {
return '<script src="./' + file + '"></script>';
})
.join('')
);
try {
fs.mkdirSync(__dirname + '/dist', 0o755);
} catch (err) {}
fs.writeFileSync(__dirname + '/dist/tests.html', content, 'utf8');
export default config;

View File

@ -0,0 +1,199 @@
import mocha from 'mocha';
import chai from 'chai';
import { FakeData, setFakeData, prepareQuery, sendQuery } from './fake-api';
import { API } from '@iconify/core/lib/api/';
import { setAPIModule } from '@iconify/core/lib/api/modules';
import { setAPIConfig } from '@iconify/core/lib/api/config';
import { coreModules } from '@iconify/core/lib/modules';
const expect = chai.expect;
// Set API
setAPIModule({
prepare: prepareQuery,
send: sendQuery,
});
coreModules.api = API;
let prefixCounter = 0;
function nextPrefix(): string {
return 'fake-api-' + prefixCounter++;
}
describe('Testing fake API', () => {
it('Loading results', done => {
const prefix = nextPrefix();
const data: FakeData = {
icons: ['icon1', 'icon2'],
data: {
prefix,
icons: {
icon1: {
body:
'<path d="M10 20v-6h4v6h5v-8h3L12 3L2 12h3v8h5z" fill="currentColor"/>',
},
icon2: {
body:
'<path d="M12 4a4 4 0 0 1 4 4a4 4 0 0 1-4 4a4 4 0 0 1-4-4a4 4 0 0 1 4-4m0 10c4.42 0 8 1.79 8 4v2H4v-2c0-2.21 3.58-4 8-4z" fill="currentColor"/>',
},
},
width: 24,
height: 24,
},
};
setAPIConfig(
{
resources: ['https://api1.local', 'https://api2.local'],
},
prefix
);
setFakeData(prefix, data);
// Attempt to load icons
API.loadIcons(
[prefix + ':icon1', prefix + ':icon2'],
(loaded, missing, pending) => {
expect(loaded).to.be.eql([
{
prefix,
name: 'icon1',
},
{
prefix,
name: 'icon2',
},
]);
done();
}
);
});
it('Loading results with delay', done => {
const prefix = nextPrefix();
const data: FakeData = {
icons: ['icon1', 'icon2'],
delay: 100,
data: {
prefix,
icons: {
icon1: {
body:
'<path d="M10 20v-6h4v6h5v-8h3L12 3L2 12h3v8h5z" fill="currentColor"/>',
},
icon2: {
body:
'<path d="M12 4a4 4 0 0 1 4 4a4 4 0 0 1-4 4a4 4 0 0 1-4-4a4 4 0 0 1 4-4m0 10c4.42 0 8 1.79 8 4v2H4v-2c0-2.21 3.58-4 8-4z" fill="currentColor"/>',
},
},
width: 24,
height: 24,
},
};
setAPIConfig(
{
resources: ['https://api1.local', 'https://api2.local'],
},
prefix
);
setFakeData(prefix, data);
// Attempt to load icons
const start = Date.now();
API.loadIcons(
[
{
prefix,
name: 'icon1',
},
{
prefix,
name: 'icon2',
},
],
(loaded, missing, pending) => {
expect(loaded).to.be.eql([
{
prefix,
name: 'icon1',
},
{
prefix,
name: 'icon2',
},
]);
const end = Date.now();
expect(end - start).to.be.at.least(50);
expect(end - start).to.be.at.most(150);
done();
}
);
});
it('Loading partial results', done => {
const prefix = nextPrefix();
const data: FakeData = {
icons: ['icon1'],
delay: 20,
data: {
prefix,
icons: {
icon1: {
body:
'<path d="M10 20v-6h4v6h5v-8h3L12 3L2 12h3v8h5z" fill="currentColor"/>',
},
},
width: 24,
height: 24,
},
};
setAPIConfig(
{
resources: ['https://api1.local', 'https://api2.local'],
rotate: 20,
timeout: 100,
limit: 1,
},
prefix
);
setFakeData(prefix, data);
// Attempt to load icons
let counter = 0;
API.loadIcons(
[prefix + ':icon1', prefix + ':icon2'],
(loaded, missing, pending) => {
try {
counter++;
switch (counter) {
case 1:
// Loaded icon1
expect(loaded).to.be.eql([
{
prefix,
name: 'icon1',
},
]);
expect(pending).to.be.eql([
{
prefix,
name: 'icon2',
},
]);
expect(missing).to.be.eql([]);
done();
break;
case 2:
done(
'Callback should not be called ' +
counter +
' times.'
);
}
} catch (err) {
done(err);
}
}
);
});
});

View File

@ -0,0 +1,344 @@
import mocha from 'mocha';
import chai from 'chai';
import { getNode } from './node';
import { finder } from '@iconify/iconify/lib/finders/iconify';
import { IconifyElement } from '@iconify/iconify/lib/element';
import { IconifyIconCustomisations } from '@iconify/core/lib/customisations';
const expect = chai.expect;
describe('Testing Iconify finder', () => {
it('Finding nodes and getting node name', () => {
const node = getNode('iconify-finder');
node.innerHTML =
'<div><p>List of <span>icons</span></p><ul>' +
'<li>Valid icons: <span class="iconify" data-icon="mdi:home"></span><i class="iconify-inline" data-icon="mdi:account"></i></li>' +
'<li>Icon without name: <span class="iconify"></span></li>' +
'<li>Icon with extra classes: <i class="iconify iconify--mdi" data-icon="mdi:home"></i></li>' +
'<li>Icon within icon: <span class="iconify" data-icon="mdi:alert:invalid"><i class="iconify" data-icon="mdi:question">text</i></span></li>' +
'<li>Icon with wrong tag: <p class="iconify" data-icon="mdi:alert"></p></li>' +
'</ul></div>';
// Get icons, convert to array
const results = finder.find(node);
const list: Element[] = Array.prototype.slice.call(results, 0);
// Test valid icons
let element = list.shift();
expect(element).to.not.be.equal(void 0);
expect(element.tagName).to.be.equal('SPAN');
expect(element.getAttribute('data-icon')).to.be.equal('mdi:home');
expect(finder.name(element as IconifyElement)).to.be.equal('mdi:home');
element = list.shift();
expect(element).to.not.be.equal(void 0);
expect(element.tagName).to.be.equal('I');
expect(element.getAttribute('data-icon')).to.be.equal('mdi:account');
expect(finder.name(element as IconifyElement)).to.be.equal(
'mdi:account'
);
// Icon without name
element = list.shift();
expect(element).to.not.be.equal(void 0);
expect(element.tagName).to.be.equal('SPAN');
expect(element.getAttribute('data-icon')).to.be.equal(null);
expect(finder.name(element as IconifyElement)).to.be.equal(null);
// Icon with extra classes
element = list.shift();
expect(element).to.not.be.equal(void 0);
expect(element.tagName).to.be.equal('I');
expect(element.getAttribute('data-icon')).to.be.equal('mdi:home');
expect(finder.name(element as IconifyElement)).to.be.equal('mdi:home');
// Icon within icon
element = list.shift();
expect(element).to.not.be.equal(void 0);
expect(element.tagName).to.be.equal('SPAN');
expect(element.getAttribute('data-icon')).to.be.equal(
'mdi:alert:invalid'
);
expect(finder.name(element as IconifyElement)).to.be.equal(
'mdi:alert:invalid' // Validation is done in finder.ts, not in finder instance
);
element = list.shift();
expect(element).to.not.be.equal(void 0);
expect(element.tagName).to.be.equal('I');
expect(element.getAttribute('data-icon')).to.be.equal('mdi:question');
expect(finder.name(element as IconifyElement)).to.be.equal(
'mdi:question'
);
// No more icons
element = list.shift();
expect(element).to.be.equal(void 0);
});
it('Transformations and inline/block', () => {
const node = getNode('iconify-finder');
node.innerHTML =
'Block icon:' +
' <span class="iconify-inline" data-icon="mdi:home" data-inline="false"></span>' +
'Inline rotated icons:' +
' <span class="iconify-inline" data-icon="mdi:account" data-rotate="90deg"></span>' +
' <span class="iconify iconify-inline" data-icon="mdi:account-circle" data-rotate="2"></span>' +
'Block rotated icons:' +
' <span class="iconify" data-icon="mdi:account-box" data-rotate="175%"></span>' +
// Invalid rotation
' <span class="iconify" data-icon="mdi:user" data-rotate="30%"></span>' +
'Flip:' +
' <span class="iconify" data-icon="ic:baseline-account" data-flip="horizontal,vertical"></span>' +
// Double 'horizontal' flip: second entry should not change anything
' <span class="iconify" data-icon="ic:twotone-account" data-flip="horizontal,vertical,horizontal"></span>' +
' <span class="iconify" data-icon="ic:round-account" data-hFlip="true"></span>' +
' <span class="iconify" data-icon="ic:sharp-account" data-vFlip="true"></span>' +
' <span class="iconify" data-icon="ic:box-account" data-vFlip="false"></span>' +
// Invalid value
' <span class="iconify" data-icon="ic:outline-account" data-hFlip="invalid"></span>' +
'';
// Get icons, convert to array
const results = finder.find(node);
const list: Element[] = Array.prototype.slice.call(results, 0);
function testElement(
name: string,
expected: IconifyIconCustomisations
): void {
let element = list.shift();
expect(element).to.not.be.equal(void 0);
expect(element.getAttribute('data-icon')).to.be.equal(name);
expect(finder.customisations(element as IconifyElement)).to.be.eql(
expected
);
}
// Block icon
let element = list.shift();
expect(element).to.not.be.equal(void 0);
expect(element.tagName).to.be.equal('SPAN');
expect(element.getAttribute('data-icon')).to.be.equal('mdi:home');
const expected: IconifyIconCustomisations = {
inline: false,
};
expect(finder.customisations(element as IconifyElement)).to.be.eql(
expected
);
// Rotated icons
testElement('mdi:account', {
inline: true,
rotate: 1,
});
testElement('mdi:account-circle', {
inline: true,
rotate: 2,
});
testElement('mdi:account-box', {
inline: false,
rotate: 3,
});
testElement('mdi:user', {
inline: false,
// No rotation because 30% is not a valid value
});
// Flip
testElement('ic:baseline-account', {
inline: false,
hFlip: true,
vFlip: true,
});
testElement('ic:twotone-account', {
inline: false,
hFlip: true,
vFlip: true,
});
testElement('ic:round-account', {
inline: false,
hFlip: true,
});
testElement('ic:sharp-account', {
inline: false,
vFlip: true,
});
testElement('ic:box-account', {
inline: false,
vFlip: false,
});
testElement('ic:outline-account', {
inline: false,
});
// No more icons
element = list.shift();
expect(element).to.be.equal(void 0);
});
it('Dimensions', () => {
const node = getNode('iconify-finder');
node.innerHTML =
'Block icon:' +
' <span class="iconify iconify-inline" data-icon="mdi:home" data-inline="false"></span>' +
'Width and height:' +
' <span class="iconify" data-icon="mdi:account" data-width="24" data-height="24"></span>' +
' <span class="iconify" data-icon="mdi:account-box" data-width="100%" data-height="100%"></span>' +
'Width:' +
' <span class="iconify" data-icon="mdi:account-twotone" data-width="32" data-inline="true"></span>' +
' <span class="iconify" data-icon="mdi:account-outline" data-width="auto" data-height=""></span>' +
'Height:' +
' <span class="iconify-inline" data-icon="mdi:account-sharp" data-height="2em" data-inline="false"></span>' +
' <span class="iconify-inline" data-icon="mdi:account-square" data-height="auto" data-width=""></span>' +
'';
// Get icons, convert to array
const results = finder.find(node);
const list: Element[] = Array.prototype.slice.call(results, 0);
function testElement(
name: string,
expected: IconifyIconCustomisations
): void {
let element = list.shift();
expect(element).to.not.be.equal(void 0);
expect(element.getAttribute('data-icon')).to.be.equal(name);
expect(finder.customisations(element as IconifyElement)).to.be.eql(
expected
);
}
// Block icon
let element = list.shift();
expect(element).to.not.be.equal(void 0);
expect(element.tagName).to.be.equal('SPAN');
expect(element.getAttribute('data-icon')).to.be.equal('mdi:home');
const expected: IconifyIconCustomisations = {
inline: false,
};
expect(finder.customisations(element as IconifyElement)).to.be.eql(
expected
);
// Width and height
testElement('mdi:account', {
inline: false,
width: '24',
height: '24',
});
testElement('mdi:account-box', {
inline: false,
width: '100%',
height: '100%',
});
// Width only
testElement('mdi:account-twotone', {
inline: true,
width: '32',
});
testElement('mdi:account-outline', {
inline: false,
width: 'auto',
});
// Height only
testElement('mdi:account-sharp', {
inline: false,
height: '2em',
});
testElement('mdi:account-square', {
inline: true,
height: 'auto',
});
// No more icons
element = list.shift();
expect(element).to.be.equal(void 0);
});
it('Alignment', () => {
const node = getNode('iconify-finder');
node.innerHTML =
'Inline icon:' +
' <i class="iconify" data-icon="mdi:home" data-inline="true"></i>' +
'Alignment:' +
' <i class="iconify" data-icon="mdi:account" data-align="left,top"></i>' +
' <i class="iconify" data-icon="mdi:account-box" data-align="right,slice"></i>' +
// 'bottom' overrides 'top', 'center' overrides 'right', extra comma
' <i class="iconify-inline" data-icon="mdi:account-outline" data-align="top,right,bottom,meet,center,"></i>' +
// spaces instead of comma, 'middle' overrides 'bottom'
' <i class="iconify iconify-inline" data-icon="mdi:account-twotone" data-align="bottom middle"></i>' +
'';
// Get icons, convert to array
const results = finder.find(node);
const list: Element[] = Array.prototype.slice.call(results, 0);
function testElement(
name: string,
expected: IconifyIconCustomisations
): void {
let element = list.shift();
expect(element).to.not.be.equal(void 0);
expect(element.getAttribute('data-icon')).to.be.equal(name);
expect(finder.customisations(element as IconifyElement)).to.be.eql(
expected
);
}
// First icon
let element = list.shift();
expect(element).to.not.be.equal(void 0);
expect(element.tagName).to.be.equal('I');
expect(element.getAttribute('data-icon')).to.be.equal('mdi:home');
const expected: IconifyIconCustomisations = {
inline: true,
};
expect(finder.customisations(element as IconifyElement)).to.be.eql(
expected
);
// Alignment
testElement('mdi:account', {
inline: false,
hAlign: 'left',
vAlign: 'top',
});
testElement('mdi:account-box', {
inline: false,
hAlign: 'right',
slice: true,
});
testElement('mdi:account-outline', {
inline: true,
hAlign: 'center',
vAlign: 'bottom',
slice: false,
});
testElement('mdi:account-twotone', {
inline: true,
vAlign: 'middle',
});
// No more icons
element = list.shift();
expect(element).to.be.equal(void 0);
});
});

View File

@ -0,0 +1,74 @@
import mocha from 'mocha';
import chai from 'chai';
import { getNode } from './node';
import { addFinder, findPlaceholders } from '@iconify/iconify/lib/finder';
import { IconifyFinder } from '@iconify/iconify/lib/interfaces/finder';
import { finder as iconifyFinder } from '@iconify/iconify/lib/finders/iconify-v1';
import { finder as iconifyIconFinder } from '@iconify/iconify/lib/finders/iconify-v1-icon';
import { IconifyIconName } from '@iconify/core/lib/icon/name';
const expect = chai.expect;
// Add finders
addFinder(iconifyFinder);
addFinder(iconifyIconFinder);
describe('Testing legacy finder', () => {
it('Finding nodes', () => {
const node = getNode('finder');
node.innerHTML =
'<div><p>List of <span>icons</span></p><ul>' +
'<li>Valid icons:' +
' <span class="iconify" data-icon="mdi:home"></span>' +
' <i class="iconify" data-icon="mdi:account"></i>' +
'</li>' +
'<li>Icon without name:' +
' <span class="iconify"></span>' +
'</li>' +
'<li>Block icon:' +
' <iconify-icon data-icon="ic:baseline-account"></iconify-icon>' +
'</li>' +
'<li>Icon with wrong tag: <p class="iconify" data-icon="mdi:alert"></p></li>' +
'</ul></div>';
const items = findPlaceholders(node);
function testIcon(
name: IconifyIconName | null,
expectedFinder: IconifyFinder
): void {
const item = items.shift();
expect(item.name).to.be.eql(name);
expect(item.finder).to.be.equal(expectedFinder);
}
// Test all icons
testIcon(
{
prefix: 'mdi',
name: 'home',
},
iconifyFinder
);
testIcon(
{
prefix: 'mdi',
name: 'account',
},
iconifyFinder
);
testIcon(
{
prefix: 'ic',
name: 'baseline-account',
},
iconifyIconFinder
);
// End of list
expect(items.shift()).to.be.equal(void 0);
});
});

View File

@ -0,0 +1,74 @@
import mocha from 'mocha';
import chai from 'chai';
import { getNode } from './node';
import { addFinder, findPlaceholders } from '@iconify/iconify/lib/finder';
import { IconifyFinder } from '@iconify/iconify/lib/interfaces/finder';
import { finder as iconifyFinder } from '@iconify/iconify/lib/finders/iconify';
import { finder as iconifyIconFinder } from '@iconify/iconify/lib/finders/iconify-icon';
import { IconifyIconName } from '@iconify/core/lib/icon/name';
const expect = chai.expect;
// Add finders
addFinder(iconifyFinder);
addFinder(iconifyIconFinder);
describe('Testing finder', () => {
it('Finding nodes', () => {
const node = getNode('finder');
node.innerHTML =
'<div><p>List of <span>icons</span></p><ul>' +
'<li>Valid icons:' +
' <span class="iconify" data-icon="mdi:home"></span>' +
' <i class="iconify" data-icon="mdi:account"></i>' +
'</li>' +
'<li>Icon without name:' +
' <span class="iconify"></span>' +
'</li>' +
'<li>Block icon:' +
' <iconify-icon data-icon="ic:baseline-account"></iconify-icon>' +
'</li>' +
'<li>Icon with wrong tag: <p class="iconify" data-icon="mdi:alert"></p></li>' +
'</ul></div>';
const items = findPlaceholders(node);
function testIcon(
name: IconifyIconName | null,
expectedFinder: IconifyFinder
): void {
const item = items.shift();
expect(item.name).to.be.eql(name);
expect(item.finder).to.be.equal(expectedFinder);
}
// Test all icons
testIcon(
{
prefix: 'mdi',
name: 'home',
},
iconifyFinder
);
testIcon(
{
prefix: 'mdi',
name: 'account',
},
iconifyFinder
);
testIcon(
{
prefix: 'ic',
name: 'baseline-account',
},
iconifyIconFinder
);
// End of list
expect(items.shift()).to.be.equal(void 0);
});
});

View File

@ -0,0 +1,219 @@
import mocha from 'mocha';
import chai from 'chai';
import { getNode } from './node';
const expect = chai.expect;
// Dummy svgAttributes variable
const svgAttributes: Record<string, unknown> = {};
/**
* Copy attributes from placeholder to SVG.
*
* This is similar to code used in render.ts
*
* @param placeholderElement
* @param svg
*/
function copyData(placeholderElement, svg) {
const svgStyle = svg.style;
// Copy attributes from placeholder
const placeholderAttributes = placeholderElement.attributes;
for (let i = 0; i < placeholderAttributes.length; i++) {
const item = placeholderAttributes.item(i);
if (item) {
const name = item.name;
if (
name !== 'class' &&
name !== 'style' &&
svgAttributes[name] === void 0
) {
try {
svg.setAttribute(name, item.value);
} catch (err) {}
}
}
}
// Copy styles from placeholder
const placeholderStyle = placeholderElement.style;
for (let i = 0; i < placeholderStyle.length; i++) {
const attr = placeholderStyle[i];
const value = placeholderStyle[attr];
if (value !== '') {
svgStyle[attr] = value;
}
}
}
describe('Testing copying node data', () => {
it('Inline attributes', () => {
const node = getNode('node-attributes');
node.innerHTML = '<p title="Testing" data-foo="bar">Test</p>';
const source = node.querySelector('p');
const target = document.createElement('span');
copyData(source, target);
// Test title
expect(source.getAttribute('title')).to.be.equal(
'Testing',
'Source title should be set'
);
expect(target.getAttribute('title')).to.be.equal(
'Testing',
'Target title should be set'
);
// Test data-*
expect(source.getAttribute('data-foo')).to.be.equal(
'bar',
'Source data-foo should be set'
);
expect(target.getAttribute('data-foo')).to.be.equal(
'bar',
'Target data-foo should be set'
);
});
it('Inline style', () => {
const node = getNode('node-attributes');
node.innerHTML =
'<p style="color: red; border: 1px solid green; vertical-align: -.1em">Test</p>';
const source = node.querySelector('p');
const target = document.createElement('span');
copyData(source, target);
// Test color
expect(source.style.color).to.be.equal(
'red',
'Source color should be red'
);
expect(target.style.color).to.be.equal(
'red',
'Target color should be red'
);
// Test border width
expect(source.style.borderWidth).to.be.equal(
'1px',
'Source border width should be 1px'
);
expect(target.style.borderWidth).to.be.equal(
'1px',
'Target border width should be 1px'
);
// Test background color
expect(source.style.backgroundColor).to.be.equal(
'',
'Source background color should not be set'
);
expect(target.style.backgroundColor).to.be.equal(
'',
'Target background color should not be set'
);
});
it('DOM style', () => {
const node = getNode('node-attributes');
node.innerHTML = '<p>Test</p>';
const source = node.querySelector('p');
source.style.color = 'green';
source.style.border = '2px solid blue';
const target = document.createElement('span');
copyData(source, target);
// Test color
expect(source.style.color).to.be.equal(
'green',
'Source color should be green'
);
expect(target.style.color).to.be.equal(
'green',
'Target color should be green'
);
// Test border width
expect(source.style.borderWidth).to.be.equal(
'2px',
'Source border width should be 2px'
);
expect(target.style.borderWidth).to.be.equal(
'2px',
'Target border width should be 2px'
);
// Test background color
expect(source.style.backgroundColor).to.be.equal(
'',
'Source background color should not be set'
);
expect(target.style.backgroundColor).to.be.equal(
'',
'Target background color should not be set'
);
});
it('Overwriting source style before copy', () => {
const node = getNode('node-attributes');
node.innerHTML = '<p style="color: blue">Test</p>';
const source = node.querySelector('p');
// Overwrite inline style
source.style.color = 'purple';
const target = document.createElement('span');
copyData(source, target);
// Test color
expect(source.style.color).to.be.equal(
'purple',
'Source color should be purple'
);
expect(target.style.color).to.be.equal(
'purple',
'Target color should be purple'
);
});
it('Overwriting target style during copy', () => {
const node = getNode('node-attributes');
node.innerHTML = '<p style="color: blue">Test</p>';
const source = node.querySelector('p');
const target = document.createElement('span');
// Set target style
target.style.color = 'purple';
target.style.verticalAlign = '-0.2em';
copyData(source, target);
// Test color
expect(source.style.color).to.be.equal(
'blue',
'Source color should be blue'
);
expect(target.style.color).to.be.equal(
'blue',
'Target color should be blue'
);
// Test vertical-align
expect(source.style.verticalAlign).to.be.equal(
'',
'Source vartical-align should not be set'
);
expect(target.style.verticalAlign).to.be.equal(
'-0.2em',
'Target vertical-align should be set'
);
});
});

View File

@ -0,0 +1,40 @@
import mocha from 'mocha';
import chai from 'chai';
import { getNode } from './node';
import { browserModules } from '@iconify/iconify/lib/modules';
import { observer } from '@iconify/iconify/lib/modules/observer';
const expect = chai.expect;
describe('Testing observer creation', () => {
it('Creating observer and triggering event', done => {
const node = getNode('observer-creation');
browserModules.root = node;
let counter = 0;
node.innerHTML = '<div></div><ul><li>test</li><li>test2</li></ul>';
observer.init(root => {
expect(root).to.be.equal(node);
counter++;
// Should be called only once
expect(counter).to.be.equal(1);
expect(observer.isPaused()).to.be.equal(false);
// Pause observer
observer.pause();
expect(observer.isPaused()).to.be.equal(true);
done();
});
// Add few nodes to trigger observer
expect(observer.isPaused()).to.be.equal(false);
node.querySelector('div').innerHTML =
'<span class="test">Some text</span><i>!</i>';
});
});

View File

@ -0,0 +1,110 @@
import mocha from 'mocha';
import chai from 'chai';
import { getNode } from './node';
import { elementFinderProperty } from '@iconify/iconify/lib/element';
import { browserModules } from '@iconify/iconify/lib/modules';
import { observer } from '@iconify/iconify/lib/modules/observer';
const expect = chai.expect;
describe('Testing observer with DOM manipulation', () => {
it('Series of events', (done) => {
const node = getNode('observer-manipulation');
browserModules.root = node;
let counter = 0;
let waitingCallback: string | boolean = true;
node.innerHTML =
'<div></div><ul><li>test</li><li>test2</li></ul><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" role="img" width="1em" height="1em" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg); vertical-align: -0.125em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" data-icon="mdi-home" data-inline="false" class="iconify"><path d="M10 20v-6h4v6h5v-8h3L12 3L2 12h3v8h5z" fill="currentColor"></path></svg>';
observer.init((root) => {
expect(root).to.be.equal(node);
expect(waitingCallback).to.be.equal(true);
counter++;
switch (counter) {
case 1:
// Added few nodes
// Remove few nodes. It should not trigger event listener
waitingCallback = 'removing nodes';
(() => {
const item = node.querySelector('ul > li:last-child');
const parent = item.parentNode;
parent.removeChild(item);
// Set timer for next step to make sure callback is not called
setTimeout(() => {
// Add node. This should trigger callback
const newItem = document.createElement('li');
parent.appendChild(newItem);
waitingCallback = true;
}, 50);
})();
break;
case 2:
// Added one list item
// Pause observer
waitingCallback = 'pause test';
(() => {
const item = node.querySelector('ul > li:last-child');
observer.pause();
item.innerHTML = '<string>Strong</strong> text!';
// Set timer for next step to make sure callback is not called
setTimeout(() => {
// Resume observer and wait a bit. Resuming observer should not trigger update
waitingCallback = 'resume test';
observer.resume();
setTimeout(() => {
// Change text of item: should remove <strong> and add new text node
waitingCallback = true;
item.innerHTML = 'Weak text!';
}, 50);
}, 50);
})();
break;
case 3:
waitingCallback = 'attributes on ul';
(() => {
const item = node.querySelector('ul');
item.setAttribute('data-foo', 'bar');
// Set timer for next step to make sure callback is not called
setTimeout(() => {
waitingCallback = true;
const item = node.querySelector('svg');
item[elementFinderProperty] = true; // Add fake finder property to trigger update
item.setAttribute('data-icon', 'mdi-account');
item.setAttribute('data-rotate', '2');
}, 50);
})();
break;
case 4:
(() => {
// Test removing attribute from SVG
const item = node.querySelector('svg');
item.removeAttribute('data-rotate');
})();
break;
case 5:
done();
break;
default:
done('Unexpected callback call!');
}
});
// Add few nodes to trigger observer
expect(observer.isPaused()).to.be.equal(false);
node.querySelector('div').innerHTML =
'<span class="test">Some text</span><i>!</i>';
});
});

View File

@ -0,0 +1,991 @@
import mocha from 'mocha';
import chai from 'chai';
import { getNode } from './node';
import { addFinder, findPlaceholders } from '@iconify/iconify/lib/finder';
import { finder as iconifyFinder } from '@iconify/iconify/lib/finders/iconify-v1';
import { finder as iconifyIconFinder } from '@iconify/iconify/lib/finders/iconify-v1-icon';
import { getStorage, addIconSet, getIcon } from '@iconify/core/lib/storage';
import { renderIcon } from '@iconify/iconify/lib/render';
import { stringToIcon } from '@iconify/core/lib/icon/name';
const expect = chai.expect;
// Add finders
addFinder(iconifyIconFinder);
addFinder(iconifyFinder);
describe('Testing legacy renderer', () => {
// Add mentioned icons to storage
const storage = getStorage('mdi');
addIconSet(storage, {
prefix: 'mdi',
icons: {
'account-box': {
body:
'<path d="M6 17c0-2 4-3.1 6-3.1s6 1.1 6 3.1v1H6m9-9a3 3 0 0 1-3 3a3 3 0 0 1-3-3a3 3 0 0 1 3-3a3 3 0 0 1 3 3M3 5v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2z" fill="currentColor"/>',
},
'account-cash': {
body:
'<path d="M11 8c0 2.21-1.79 4-4 4s-4-1.79-4-4s1.79-4 4-4s4 1.79 4 4m0 6.72V20H0v-2c0-2.21 3.13-4 7-4c1.5 0 2.87.27 4 .72M24 20H13V3h11v17m-8-8.5a2.5 2.5 0 0 1 5 0a2.5 2.5 0 0 1-5 0M22 7a2 2 0 0 1-2-2h-3c0 1.11-.89 2-2 2v9a2 2 0 0 1 2 2h3c0-1.1.9-2 2-2V7z" fill="currentColor"/>',
},
'account': {
body:
'<path d="M12 4a4 4 0 0 1 4 4a4 4 0 0 1-4 4a4 4 0 0 1-4-4a4 4 0 0 1 4-4m0 10c4.42 0 8 1.79 8 4v2H4v-2c0-2.21 3.58-4 8-4z" fill="currentColor"/>',
},
'home': {
body:
'<path d="M10 20v-6h4v6h5v-8h3L12 3L2 12h3v8h5z" fill="currentColor"/>',
},
},
width: 24,
height: 24,
});
it('Convert placeholders to SVG', () => {
const node = getNode('renderer');
node.innerHTML =
'<div><p>Testing renderer v1</p><ul>' +
'<li>Inline icons:<br />' +
' Red icon with red border: <span class="iconify" data-icon="mdi:home" style="color: red; border: 1px solid red;"></span><br />' +
' No vertical-align, green border: <i class="iconify test-icon iconify--mdi-account" data-icon="mdi:account" style="vertical-align: 0;" data-flip="horizontal" aria-hidden="false"></i>' +
'</li>' +
'<li>Block icons:' +
' <iconify-icon data-icon="mdi-account-cash" title="&lt;Cash&gt;!"></iconify-icon>' +
' <span class="iconify-icon" data-icon="mdi:account" data-flip="vertical" data-width="auto"></span>' +
'</li>' +
'<li>Changed by attribute:' +
' <iconify-icon data-icon="mdi:account-box" data-inline="true" data-rotate="2" data-width="42"></iconify-icon>' +
'</li>' +
'<li>Mix of classes:' +
' <i class="iconify iconify-icon should-be-block" data-icon="mdi:home"></i>' +
'</li>' +
'</ul></div>';
// Get items
const items = findPlaceholders(node);
expect(items.length).to.be.equal(6);
// Test finders to make sure icons are in correct order
expect(items[0].finder).to.be.equal(iconifyIconFinder);
expect(items[1].finder).to.be.equal(iconifyIconFinder)